Kurs GTK+ – rozdział 11

11

11. GtkTextView

11.1. Wstęp

W tym rozdziale dowiesz się, czym jest kontrolka GtkTextView, do czego służy oraz jak ją utworzyć i używać.

GtkTextView to zaawansowany widżet służący do wyświetlania wieloliniowego tekstu i ewentualnej jego modyfikacji, również przez użytkownika. Posiada ogromne możliwości. Można formatować w nim tekst za pomocą tagów, można dostać się do znaków za pomocą iteratorów, a sam tekst wyświetlany przez ten widżet dodaje się do bufora.

11.2. Utworzenie GtkTextView i dodawanie czystego tekstu

GtkTextView można utworzyć za pomocą funkcji:

GtkWidget* gtk_text_view_new (void);

Warto wspomnieć, że istnieje jeszcze podobna bardzo funkcja do powyższej, a jest nią:

GtkWidget* gtk_text_view_new_with_buffer (GtkTextBuffer *buffer);

Czym one się różnią? Otóż pierwsza tworzy nowy widżet GtkTextView wraz z buforem. W przypadku drugiej funkcji bufor ten należy utworzyć samemu. Warto również wiedzieć o dwóch użytecznych funkcjach:

GtkTextBuffer* gtk_text_view_get_buffer (GtkTextView *text_view);
void gtk_text_view_set_buffer (GtkTextView *text_view, GtkTextBuffer *buffer);

Pierwsza z nich jest szczególnie użyteczna przy tworzeniu GtkTextView za pomocą funkcji gtk_text_view_new(). Zwraca ona bowiem wskaźnik do utworzonego bufora. Druga natomiast ustawia nowy bufor dla widżetu.

Ok, wiesz już jak utworzyć kontrolkę GtkTextView wraz z buforem oraz jak się do tego bufora dostać. Jak jednak dodać do niego jakiś tekst?

Do tego celu posłuży nam funkcja:

void gtk_text_buffer_insert (GtkTextBuffer *buffer, GtkTextIter *iter, const gchar *text, gint len);

W pierwszym argumencie przyjmuje ona wkaźnik do bufora tekstowego, w następnym do iteratora (o iteratorach za chwilę), w następnym żądany tekst, a w ostatnim – długość tego tekstu. W ostatnim argumencie można podać -1 – wtedy jednak trzeba zadbać o to, aby ciąg znaków kończył się znakiem NULL – 0.

Wspomniałem o iteratorach. Wyznaczają one pozycję umieszczenia ciągu znaków. Aby otrzymać taki iterator, można się posłużyć na przykład funkcją:

void gtk_text_buffer_get_iter_at_offset (GtkTextBuffer *buffer, GtkTextIter *iter, gint char_offset);

W pierwszym argumencie należy podać referencję do bufora tekstowego, w następnym do iteratora, a w ostatnim należy podać żądaną pozycję.

Z taką wiedzą można już utworzyć widżet GtkTextView i dodać do niego tekst. Warto również przeanalizować poniższy kod źródłowy:

#include <gtk/gtk.h>

int main( int argc, char *argv[])
{
    GtkWidget *okno;
    GtkWidget *textView;
    GtkTextBuffer *bufor;
    GtkTextIter iter;

    gtk_init(&argc, &argv);

    okno = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(okno), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(okno), 250, 200);
    gtk_window_set_title(GTK_WINDOW(okno), "Kurs GTK+");
    gtk_container_set_border_width(GTK_CONTAINER(okno), 10);

    textView = gtk_text_view_new();
    bufor = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textView));
    gtk_text_buffer_get_iter_at_offset(bufor, &iter, 0);
    gtk_text_buffer_insert(bufor, &iter, "Hello world!\n", -1);

    gtk_container_add(GTK_CONTAINER(okno), textView);

    g_signal_connect(G_OBJECT(okno), "destroy", G_CALLBACK(gtk_main_quit), NULL);

    gtk_widget_show_all(okno);

    gtk_main();

    return 0;
}

Działanie tego kodu można zobaczyć poniżej.

11.3. Tagi

Napisałem we wstępie, że w GtkTextView można formatować tekst za pomocą tagów. Nie są to co prawda takie tagi jak np. w HTMLu, jednak również są bardzo użyteczne. Do tworzenia tagów służy funkcja:

GtkTextTag* gtk_text_buffer_create_tag (GtkTextBuffer *buffer, const gchar *tag_name, const gchar *first_property_name, ...);

W pierwszym argumencie przyjmuje ona wskaźnik do bufora tekstowego, w następnym nazwę tagu, natomiast w kolejnych należy podać kolejno nazwę właściwości i żądaną jej wartość. Powyższa funkcja przyjmuje nieograniczoną liczbę argumentów, a ostatnim argumentem musi być NULL. Listę właściwości i wartości, które można im ustawić można znaleźć w dokumentacji GTK+.

Aby teraz do bufora dodać otagowany tekst, można użyć funkcji:

void gtk_text_buffer_insert_with_tags_by_name (GtkTextBuffer *buffer, GtkTextIter *iter, const gchar *text, gint len, const gchar *first_tag_name, ...);

Jak nietrudno zauważyć, funkcja ta jest bardzo podobna do omawianej w poprzednim podrozdziale gtk_text_buffer_insert(), tak więc nie ma sensu omawiać jej pierwszych czterech argumentów ponownie, jednak – podobnie jak powyższa – ta funkcja może przyjąć nieskończoną liczbę argumentów. Należy podać bowiem nazwy tagów, które chcesz dodać do dodawanego tekstu. Ostatnim argumentem musi być oczywiście NULL.

Zachęcam do przeanalizowania poniższego kodu źródłowego. Może on pomóc w zrozumieniu działania tagów.

#include <gtk/gtk.h>

int main( int argc, char *argv[])
{
    GtkWidget *okno;

    GtkWidget *textView;
    GtkTextBuffer *bufor;
    GtkTextIter iter;

    gtk_init(&argc, &argv);

    okno = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(okno), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(okno), 250, 200);
    gtk_window_set_title(GTK_WINDOW(okno), "Kurs GTK+");
    gtk_container_set_border_width(GTK_CONTAINER(okno), 5);
    GTK_WINDOW(okno)->allow_shrink = TRUE;

    textView = gtk_text_view_new();

    bufor = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textView));

    gtk_text_buffer_create_tag(bufor, "margines", "left_margin", 10, NULL);
    gtk_text_buffer_create_tag(bufor, "niebieska_czcionka", "foreground", "blue", NULL);
    gtk_text_buffer_create_tag(bufor, "zielone_tlo", "background", "green", NULL);
    gtk_text_buffer_create_tag(bufor, "pochylenie", "style", PANGO_STYLE_ITALIC, NULL);
    gtk_text_buffer_create_tag(bufor, "pogrubienie", "weight", PANGO_WEIGHT_BOLD, NULL);

    gtk_text_buffer_get_iter_at_offset(bufor, &iter, 0);

    gtk_text_buffer_insert(bufor, &iter, "Czysty tekst\n", -1);
    gtk_text_buffer_insert_with_tags_by_name(bufor, &iter, "Kolorowy tekst!\n", -1, "niebieska_czcionka",  NULL);
    gtk_text_buffer_insert_with_tags_by_name (bufor, &iter, "Margines\n", -1, "margines", NULL);
    gtk_text_buffer_insert_with_tags_by_name (bufor, &iter, "Tekst z kolorowym tłem\n", -1, "zielone_tlo", NULL);
    gtk_text_buffer_insert_with_tags_by_name (bufor, &iter, "Pochylony tekst\n", -1, "pochylenie", NULL);
    gtk_text_buffer_insert_with_tags_by_name (bufor, &iter, "Pogrubiony tekst\n", -1, "pogrubienie", NULL);
    gtk_text_buffer_insert_with_tags_by_name (bufor, &iter, "Wszystko razem!\n", -1, "margines", "niebieska_czcionka", "zielone_tlo", "pochylenie", "pogrubienie", NULL);

    gtk_container_add(GTK_CONTAINER(okno), textView);

    g_signal_connect(G_OBJECT(okno), "destroy", G_CALLBACK(gtk_main_quit), NULL);

    gtk_widget_show_all(okno);

    gtk_main();

    return 0;
}

Działania powyższego kodu źródłowego możesz zobaczyć poniżej.

11.4. Linie i kolumny

Aby pobrać numer aktualnej linii i kolumny należy posłużyć się iteratorem. Aby natomiast taki iterator – który wkazuje na aktualne położenie kursora – otrzymać, można posłużyć się funkcją:

void gtk_text_buffer_get_iter_at_mark (GtkTextBuffer *buffer, GtkTextIter *iter, GtkTextMark *mark);

W pierwszym argumencie należy podać oczywiście wskaźnik do bufora, w drugim do iteratora, natomiast w trzecim należy podać wskaźnik do GtkTextMark. GtkTextMark to pozycja w buforze, którą można otrzymać np. poprzez funkcję:

GtkTextMark* gtk_text_buffer_get_selection_bound (GtkTextBuffer *buffer);

Powyższa funkcja przyjmuje jako argument referencję do bufora i zwraca GtkTextMark w miejscu aktualnego położenia kursora.

Ok, tak więc mamy już nasz iterator. Teraz przydadzą nam się dwie funkcje:

gint gtk_text_iter_get_line (const GtkTextIter *iter);
gint gtk_text_iter_get_line_offset (const GtkTextIter *iter);

Obie przyjmują tylko jeden argument – jest nim wskaźnik do iteratora. Pierwsza z tych dwóch funkcji zwraca numer linii, natomiast druga numer znaku (kolumny).

Pozostała jeszcze jedna kwestia – jak przechwycić zmodyfikowanie bufora lub przesunięcie kursora?

Jest to możliwe dzięki sygnałom changed oraz mark_set. Można je podpiąć do bufora za pomocą poznanej już funkcji g_signal_connect(). Przy czym warto wiedzieć, jakiej konstrukcji wywoływanej funkcji sygnały te wymagają, aby potem uniknąć ewentualnych błędów.

Dla sygnału changed jest to:

void user_function (GtkTextBuffer *textbuffer, gpointer user_data)

I tutaj pierwszym argumentem jest oczywiście bufor tekstowy, a drugim – wskaźnik do ewentualnych innych danych czy zmiennych, natomiast dla sygnału mark_set:

void user_function (GtkTextBuffer *textbuffer, GtkTextIter *location, GtkTextMark *mark, gpointer user_data)

Pierwszym argumentem jest tutaj wskaźnik do bufora tekstowego, następnym iterator dla GtkTextMarka, do którego wskaźnik jest przekazywany w następnym argumencie, no a czwarty argument to oczywiście dodatkowe dane.

Ze zdobytą wiedzą możesz już utworzyć widżet GtkTextView i np. pasek statusu, w którym podasz aktualny wiersz i kolumnę. Jeżeli wciąż czegoś nie rozumiesz, możesz spróbować przeanalizować poniższy kod źródłowy.

#include <gtk/gtk.h>

void zaaktualizuj_pasek(GtkTextBuffer *bufor, GtkStatusbar *pasek)
{
    gchar *wiadomosc;
    gint wrsz, kol;
    GtkTextIter iter;

    gtk_statusbar_pop(pasek, 0);

    gtk_text_buffer_get_iter_at_mark(bufor, &iter, gtk_text_buffer_get_insert(bufor));
    wrsz = gtk_text_iter_get_line(&iter);
    kol = gtk_text_iter_get_line_offset(&iter);

    wiadomosc = g_strdup_printf("Wrsz %d, Kol %d", wrsz+1, kol+1);

    gtk_statusbar_push(pasek, 0, wiadomosc);

    g_free(wiadomosc);
}

void zmiana_polozenia_kursora(GtkTextBuffer *bufor, const GtkTextIter *nowaLokalizacja, GtkTextMark *mark, gpointer pasek)
{
    zaaktualizuj_pasek(bufor, GTK_STATUSBAR(pasek));
}

int main( int argc, char *argv[])
{
    GtkWidget *okno;
    GtkWidget *vbox;

    GtkWidget *textView;
    GtkWidget *pasek;
    GtkTextBuffer *bufor;
    GtkTextIter iter;

    gtk_init(&argc, &argv);

    okno = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(okno), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(okno), 250, 200);
    gtk_window_set_title(GTK_WINDOW(okno), "Kurs GTK+");

    vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(okno), vbox);

    textView = gtk_text_view_new();
    gtk_box_pack_start(GTK_BOX(vbox), textView, TRUE, TRUE, 0);
    bufor = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textView));
    gtk_text_buffer_get_iter_at_offset(bufor, &iter, 0);
    gtk_text_buffer_insert(bufor, &iter, "Hello world!", -1);

    pasek = gtk_statusbar_new();
    gtk_box_pack_start(GTK_BOX(vbox), pasek, FALSE, FALSE, 0);

    g_signal_connect(bufor, "changed", G_CALLBACK(zaaktualizuj_pasek), pasek);
    g_signal_connect(bufor, "mark_set", G_CALLBACK(zmiana_polozenia_kursora), pasek);
    g_signal_connect(G_OBJECT(okno), "destroy", G_CALLBACK(gtk_main_quit), NULL);

    gtk_widget_show_all(okno);

    zaaktualizuj_pasek(bufor, GTK_STATUSBAR (pasek));

    gtk_main();

    return 0;
}

W powyższym kodzie możesz znaleźć jeszcze dwie nowe funkcje i są to:

gchar* g_strdup_printf (const gchar *format, ...);
void gtk_statusbar_pop (GtkStatusbar *statusbar, guint context_id);

Pierwsza z nich jest odpowiednikiem standardowego sprintf(), jednak bezpieczniejszym i przystosowanym do GTK+. Natomiast druga usuwa wiadomość z paska statusu. Warto zauważyć, że nie posługujemy się funkcją gtk_statusbar_get_context_id() omówioną w rozdziale 7, ponieważ nie musimy tutaj dodawać kilku wiadomości o różnej treści.

Działanie powyższego kodu źródłowego można zobaczyć poniżej.

To by było na tyle w tym rozdziale. Opisuje on bardzo niewielką część możliwości GtkTextView, jednak zachęcam do samodzielnego głębszego poznawania tego widżetu. Następny rozdział opisuje użyteczny widżet GtkTreeView.

11 komentarzy

  1. Lukines

    Skończyłeś pisać kolejne rozdziały ?

    Odpowiedz
  2. L120

    W jaki sposób można podświetlić aktualna linię?
    Albo w jaki sposób dodać numery wierszy po lewej stronie. Albo klamry jak w Mathematica?
    Jest jakiś sposób by zmienić wyświetlanie pojedyńczej linijki?

    Odpowiedz
    • m4tx

      admin

      Prościej pewnie będzie użyć GtkSourceView niż próbować to implementować samemu. GtkSourceView jest widżetem stworzonym właśnie do edycji kodu źródłowego (używany np. w edytorze gedit).

      Odpowiedz
      • ssrfew

        jakis przykład użycia?

        a jak w Twoim przykładzie zrobic enter? powiedzmy jak w terminalu enter staje się wykonaniem instrukcji

        Odpowiedz
      • oo

        Jest szansa bys się odezwał na e-mail lub irc, jabber ?

        Odpowiedz
  3. wetewt

    gtk_widget_add_accelerator Jak dodać do tego edytora skrót?
    oczywiście można dodać w menu, ale nie chce by to było widoczne. Musiałbym tych skrótów dodac ogromną ilośc np. PgDwn+Alt
    Shift+Home itd.

    BTW(napisz może do mnie, mam jeszcze jedno pytanie)

    Odpowiedz
    • m4tx

      admin

      Można podpiąć się pod key-press-event. Czyli z grubsza:

      gboolean key_pressed(GtkWidget *window, GdkEventKey *event,
      		GtkTextBuffer *buffer) {    
      	if (event->type == GDK_KEY_PRESS && event->state & GDK_MOD1_MASK
      			&& event->keyval == GDK_Page_Down) {
      		// akcja po wciśnięciu
      	}
      
      	return FALSE;
      }
      
      /* ... */
      	gtk_widget_add_events(view, GDK_BUTTON_PRESS_MASK);
        	g_signal_connect(G_OBJECT(window), "key-press-event",
      		G_CALLBACK(key_pressed), buffer);
      

      (Na GTK+ 3 te stałe mają nieco inne nazwy, m.in. GDK_KEY_Page_Down)

      Odpowiedz
      • wetewt

        Bardzo dziękuję za odpowiedź. Kod działa. Niestety nie wiem jak obsłużyć prawy alt (czasem linux mapuje) anie jak obsluzyc prawy i lewy control. Sprawdzilem wszystkie MOD?_ wszystko co znalazlem w GDK_ alt_R Super Hiper i podobne klawisze. To może przydać się innym.

        Odpowiedz
  4. Geralt

    #include

    int main(int argc, char** argv)
    {
    GtkWidget *okno;
    GtkWidget *textView;
    GtkTextBuffer *bufor;
    GtkTextIter *iter;

    gtk_init(&argc, &argv);

    okno = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(okno), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(okno), 250, 200);
    gtk_window_set_title(GTK_WINDOW(okno), "Kurs GTK+");
    gtk_container_border_width(GTK_CONTAINER(okno), 10);

    textView = gtk_text_view_new();
    bufor = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textView));
    gtk_text_buffer_get_iter_at_offset(bufor, &iter, 0);
    gtk_text_buffer_insert(bufor, &iter, "Hello world!\n", -1);

    gtk_container_add(GTK_CONTAINER(okno), textView);

    g_signal_connect(G_OBJECT(okno), "destroy", G_CALLBACK(gtk_main_quit), NULL);

    gtk_widget_show_all(okno);

    gtk_main();

    return 0;
    }

    Postanowiłem przepisywać i analizować to. Niestety jakimś zbiegiem okoliczności w 20 i 21 linii cały czas wychodzi coś w rodzaju „błędu”, gdyż skompilowany program się wywala. Może to jest po prostu poradnik dla linuxa? Pozostaje dalej aplikacja konsolowa, po oknie ani widu ani słychu, nie no słychu – error message.

    Chciałem się nauczyć chociaż kilku poleceń, ale zawsze w moim przypadku z C++ mam tak że wywala mi błędy w (jak myślę) poprawnym kodzie. Nie kopiuję tego, tylko przepisuję żeby mi się wryło w pamięć, ale aplikacja cały czas wywala crasha.

    Odpowiedz
    • Geralt

      Mniejsza o to, złapałem się na typowy błąd – jeden znak w te czy wewte i program leży. Teraz mam pytanko – bo ten kod faktycznie tworzy okienko, ale przy tym zostaje uruchomione okno konsoli. Jak się tego okienka CMD pozbyć?

      Odpowiedz

Zostaw komentarz

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>