 Základním widgetem pro zobrazování a editaci textu v Gtk2 je textové pole. Umí řadu zajímavých věcí - mimo očekávaných funkcionalit také formátování textu a vkládání widgetů. Na to vše je potřeba dobře rozumět reprezentaci pozic, což jsou objekty určující místa v textovém poli.
Základním widgetem pro zobrazování a editaci textu v Gtk2 je textové pole. Umí řadu zajímavých věcí - mimo očekávaných funkcionalit také formátování textu a vkládání widgetů. Na to vše je potřeba dobře rozumět reprezentaci pozic, což jsou objekty určující místa v textovém poli.
			27.1.2011 00:00 | Jiří Václavík | přečteno 12122×
			
		
Gtk2::TextView je často používaný widget na zobrazování a editaci textu. Tento widget má ještě podstatně více různých funkcí a nastavení, než textová pole v Tk a Wx, která jsme si již představovali.
Na úvod se podívejme na základní použití.
$text = Gtk2::TextView->new;
$buffer = $text->get_buffer();
$buffer->set_text("zkus sem neco napsat");
Právě jsme vytvořili textové pole a napsali do něj text. Uživatel ho může libovolně editovat.
. 
Ukázka Gtk2::TextView
Na první pohled je kód zdánlivě zbytečně složitý (Proč nepoužít něco jako Gtk2::TextView->set_text? Proč to musíme řešit přes buffer?). Uvidíme, že to zas o tolik složitější není a že lze takto s textem pohodlně manipulovat. Text (společně s informací, jak má být zobrazen) je zde reprezentován speciálním widgetem Gtk2::TextBuffer a až ten můžeme nastavit pomocí set_buffer.
Podívejme se na úvod jen ve stručnosti na některé základní metody Gtk2::TextView.
| Metoda | Význam | 
|---|---|
| set_wrap_mode | metoda zalamování; možné hodnoty jsou none, char nebo word | 
| set_editable | false pro read-only, true pro editaci | 
| set_cursor_visible | viditelnost kurzoru (kurzor bychom měli schovat vždy, když používáme read-only režim) | 
| set_justification | zarovnání textu; možné hodnoty jsou left, right, center | 
| set_left_margin, set_right_margin | okraje | 
| set_indent | odsazení | 
Existuje-li metoda s názvem set_něco, pak také často existuje metoda get_něco, která detekuje aktuální nastavení.
Objekt typu Gtk2::TextIter reprezentuje pozici v bufferu. Pozice je platná vždy jen do okamžiku, kdy se obsah bufferu (tj. text v Gtk2::TextView) změní. Protože často chceme pozice zachovat i po změně obsahu bufferu, byly vytvořeny objekty typu Gtk2::TextMark, což jsou hýbající se pozice.
Gtk2::TextBuffer je vytvořen vždy, když vytvoříme Gtk2::TextView. Samozřejmě si ale můžeme standardní cestou vytvořit TextBuffer vlastní.
$textbuffer = Gtk2::TextBuffer->new;
Podívejme se na metody, kterými lze s textem v TextBufferu manipulovat.
| Metoda | Význam | 
|---|---|
| set_text | nastaví text | 
| insert($textiter, $text) | vloží $text na pozici $textiter (jak víme, pozice je objekt typu Gtk2::TextIter; více o tom dále) | 
| insert_at_cursor | vloží text na aktuální pozici | 
| insert_range($kam, $zacatek, $konec) | výsek určený dle $zacatek, $konec se uloží na pozici $kam | 
| get_text($zacatek, $konec, $ukazat_skryte) | vrátí výsek | 
| get_slice($zacatek, $konec, $ukazat_skryte) | vrátí výsek, obrázky budou vráceny jako znak 0xFFFC; více o tom dále | 
| get_line_count, get_char_count | zjistí počet řádků, resp. znaků | 
Již víme, že pozici v TextBufferu uchovává objekt Gtk2::TextIter. Podívejme se na to, jak s pozicemi pracovat. Pozici v bufferu definujeme jednou z následujících metod zavolaných nad TextBufferem.
| Příkaz | Kde se vytvoří TextIter? | 
|---|---|
| $textiter=$textbuffer->get_start_iter; | před prvním znakem | 
| $textiter=$textbuffer->get_end_iter; | za posledním znakem | 
| $textiter=$textbuffer->get_iter_at_offset(50); | před padesátým znakem | 
| $textiter=$textbuffer->get_iter_at_line($radek); | před prvním znakem řádku č. $radek | 
| $textiter=$textbuffer->get_iter_at_line_offset(5, 10); | před 10. znak na 5. řádku | 
| $textiter=$textbuffer->get_iter_at_mark($mark); | na pozici existujícícho Gtk2::TextMark | 
| ($ti1,$ti2)=$buffer->get_selection_bounds; | na hranice vybrané oblasti | 
Nad objekty typu Gtk2::TextIter lze pak volat obrovské množství dalších metod. Podívejme se na seznam těch, které se mohou hodit.
| Metoda | Význam | 
|---|---|
| Detekce pozice v kontextu slov a řádků | |
| starts_word, ends_word, inside_word, starts_sentence, ends_sentence, inside_sentence, starts_line, ends_line, is_end, is_start, is_cursor_position | zjišťování informací o pozici | 
| in_range($zacatek, $konec) | jsme v daném rozmezí? | 
| Zjišťování absolutní pozice | |
| get_offset | vrátí číslo znaku | 
| get_line | vrátí číslo řádku | 
| get_line_offset | vrátí číslo znaku na řádku | 
| Pohyb TextIterů | |
| forward_char, backward_char, forward_word_end, backward_word_start, forward_sentence_end, backward_sentence_start, forward_line, backward_line, forward_to_line_end, forward_cursor_position, backward_cursor_position, forward_to_end | pohyb na pozici v kontextu slov a řádků | 
| forward_chars($kolik), backward_chars($kolik), forward_word_ends($kolik), backward_word_starts($kolik), forward_sentence_ends($kolik), backward_sentence_starts($kolik), forward_lines($kolik), backward_lines($kolik), forward_cursor_positions($kolik), backward_cursor_positions($kolik) | to samé vícenásobně | 
| set_offset($offset) | nastaví na danou pozici | 
| set_line($radek) | nastaví na daný řádek | 
| set_line_offset($offset_na_radku) | nastaví na pozici na aktuálním řádku | 
| Získávání textu z TextBufferu | |
| get_char | vrátí znak | 
| get_text($do) | vrátí úsek textu | 
| get_slice($do) | vrátí úsek textu | 
| Detekce dalších objektů na dané pozici | |
| get_marks | vrátí seznam objektů Gtk2::TextMark | 
| get_tags | vrátí seznam tagů, tj. objektů typu Gtk2::TextTag | 
| get_pixbuf | vrátí seznam objektů Gtk2::PixBuf | 
| get_child_anchor | vrátí seznam objektů Gtk2::TextChildAnchor | 
| has_tag, begins_tag, ends_tag | detekce tagů | 
| Hledání v textu | |
| forward_search($retezec, 'text-only', $kam_az) | vrátí dvouprvkový seznam s počáteční a koncovou pozicí typu Gtk2::TextIter, hledání dopředu; druhý parametr je typu Gtk2::TextSearchFlags | 
| backward_search($retezec, 'text-only', $kam_az) | vrátí dvouprvkový seznam s počáteční a koncovou pozicí typu Gtk2::TextIter, hledání dozadu; druhý parametr je typu Gtk2::TextSearchFlags | 
| Ostatní | |
| get_attributes | vrátí objekt typu Gtk2::TextAttributes | 
Gtk2::TextMark je podobný jako Gtk2::TextIter, avšak zachovává pozice při změnách TextBufferu.
Podívejme se opět, jak lze vytvořit objekt typu Gtk2::TextMark.
| Příkaz | Kde se vytvoří TextMark? | 
|---|---|
| $textmark = $textbuffer->get_insert | aktuální pozice kurzoru | 
| $textmark = $textbuffer->get_selection_bound | druhý konec výběru (prvním koncem je pozice kurzoru) | 
| $textmark = $textbuffer->create_mark($nazev, $textiter, $zleva) | Gtk2::TextMark s názvem $nazev se vytvoří na pozici $textiter s tím, že při vkládání na tuto pozici se posouvá/neposouvá doleva | 
TextMarky lze ručně posouvat pomocí TextIterů jedním z následujících příkazů.
$textbuffer->move_mark($mark, $kam);
$textbuffer->move_mark_by_name($nazev, $kam); 
Metodou get_mark získáme objekt typu Gtk2::TextMark na základě svého názvu.
Objekty typu Gtk2::TextTag jsou dalším obsahem TextBufferů. Obsahují informace o formátování. Každý tag funguje na daných pozicích a má nějaký svůj efekt (například zvětšuje písmo na druhém řádku).
Společně s bufferem se vždy vytvoří i objekt typu Gtk2::TextTagTable. Zde je uchováván seznam tagů.
Tag vytvoříme metodou create_tag zavolanou nad TextBufferem.
$tag = $textbuffer->create_tag($nazev, %vlastnosti); 
Poté jsou dvě možnosti, jak tagy aplikovat. Buď přímo nebo opět podle názvu.
$textbuffer->apply_tag($tag, $zacatek, $konec); 
$textbuffer->apply_tag_by_name($nazev, $zacatek, $konec);
Stejně tak můžeme tagy odstraňovat.
$textbuffer->remove_tag($tag, $zacatek, $konec); 
$textbuffer->remove_tag_by_name($nazev, $zacatek, $konec);
Vlastnosti lze nastavovat metodou set_property.
Pomocí set_priority nastavujeme pro tag prioritu mezi 0 a velikostí Gtk2::TextTagTable.
Podívejme se na hash parametrů, které ovlivňují chování tagu. Jaké všechny paramety můžeme používat?
| Klíč | Význam a hodnoty | 
|---|---|
| background, background-gdk | barva pozadí pomocí řetězce, resp, Gtk2::Gdk::Color | 
| foreground, foreground-gdk | barva popředí pomocí řetězce, resp, Gtk2::Gdk::Color | 
| background-stipple, foreground-stipple | použije se na pozadí / popředí maska (bitmapa) | 
| font | písmo (například Times 12) | 
| size, size-points | velikost písma v Pango bodech, resp. v bodech | 
| scale | relativní velikost písma oproti okolí | 
| font-desc | styl písma (jako objekt typu Gtk2::Pango::FontDescription) | 
| family | například Times | 
| underline | podtržené písmo | 
| style, variant, weight, stretch | hodnoty jsou v dokumentaci | 
| pixels-above-lines, pixels-below-lines | mezera v pixelech nad / pod řádkem | 
| wrap-mode | jak zalamovat (none, word, char) | 
| justification | left, right, center | 
| direction | směr toku textu (right-to-left, left-to-right) | 
| left-margin, right-margin | okraje | 
| indent | odsazení odstavce | 
| strikethrough | dělení | 
| rise | posunutí nahoru (dolů při záporném parametru) | 
| background-full-height | má-li tag ovlivnit celý řádek ve smyslu barvy pozadí | 
Zkusíme si vytvořit nějaký tag.
$tag = $buffer->create_tag("zvyrazneny_text",
    "font" => "Helvetica 30",
    "underline" => PANGO_UNDERLINE_DOUBLE,
    "foreground" => "darkgreen",
    "background-gdk" => Gtk2::Gdk::Color->new(60, 0, 200)); 
$buffer->apply_tag($tag, $buffer->get_iter_at_offset(3), $buffer->get_iter_at_offset(7)); 
Výsledek spatříme po spuštění aplikace.
 
TextView s otagovaným úsekem textu
Do textového pole lze vkládat také obrázky nebo rovnou celé widgety. Obrázky reprezentované jako Gtk2::PixBuf můžeme vkládat metodou insert_pixbuf na danou pozici. Takový obrázek se bude chovat jako unicodový znak 0xFFFC.
$textbuffer->insert_pixbuf($textiter, $pixbuf) 
S widgety je to trochu složitější, ale v zásadě podobné. Vytvoříme objekt typu Gtk2::TextChildAnchor na místě, kde chceme, aby byl widget. Poté na toto místo widget vložíme.
$anchor = Gtk2::TextChildAnchor->new;
$textbuffer->insert_child_anchor($iter, $anchor);
$textview->add_child_at_anchor($widget, $anchor);
Ukážeme si konkrétnější příklad - TextView, do kterého vložíme text, obrázek a tlačítko.
$textview = Gtk2::TextView->new;
$buffer = $textview->get_buffer();
$buffer->set_text("nejaky text");
$tlacitko = Gtk2::Button->new("tlacitko");
$anchor = Gtk2::TextChildAnchor->new;
$buffer->insert_child_anchor($buffer->get_start_iter, $anchor);
$textview->add_child_at_anchor($tlacitko, $anchor);
$buffer->insert_pixbuf($buffer->get_start_iter, Gtk2::Gdk::Pixbuf->new_from_file("./Ghost.png"));
Text pak můžeme samozřejmě libovolně editovat. Vložené objekty se chovají jako znaky.
 
TextView s widgetem a obrázkem
Ukážeme si, jak lze zachytávat pozici myši a manipulovat s textem (popřípadě tagem) na jejím aktuálním místě.
Díky signálu motion_notify_event, který je emitován při změně pozice myši nad daným widgetem můžeme vyvolat akci. Ta bude například zvýrazňovat místo, kde je aktuálně myš.
$textview->signal_connect(motion_notify_event => \&akce);
Díky metodě window_to_buffer_coords zjistíme pixelové souřadnice. Ty nám metoda get_iter_at_location převede na pozici v textu, tj. objekt typu Gtk2::TextIter. Pak není nic jednoduššího, než s danou pozicí manipulovat.
Vytvoříme tedy tag, kterým budeme zvýrazňovat aktuální znak.
my $tag = $buffer->create_tag(undef, "foreground" => "white", "background" => "black");
Aplikujeme ho. Na to ovšem musíme mít dva TextItery (potřebujeme začátek i konec tagu). Vytvoříme si tedy kopii našeho TextIteru a posuneme ho o pozici vpřed. TextIter nemá vhodný konstruktor na kopírování objektů a poradíme si tak trochu oklikou.
my $textiter2 = $buffer->get_iter_at_offset($textiter->get_offset);
$textiter->forward_char;
$buffer->apply_tag($tag, $textiter2, $textiter); 
Ještě bychom měli uchovávat starý tag a mazat ho po opuštění pozice. Zavedeme si tedy proměnnou $kurzor_tag, která bude reprezentovat týž objekt jako $tag do doby, než $tag zapomeneme. Tag pak smažeme přes tabulku tagu, kterou získáme z bufferu.
my $table = $buffer->get_tag_table;
$table->remove($kurzor_tag) if defined $kurzor_tag;
Podívejme se na celý zdrojový kód naší akce.
sub akce {
    my($textview, $udalost) = @_;
    my $textiter = $textview->get_iter_at_location(
$textview->window_to_buffer_coords("widget",$udalost->x, $udalost->y));
    my $textiter2 = $buffer->get_iter_at_offset($textiter->get_offset);
    $textiter->forward_char;
    my $tag = $buffer->create_tag(undef, "foreground" => "white", "background" => "black");
    my $table = $buffer->get_tag_table;
    $table->remove($kurzor_tag) if defined $kurzor_tag;
    $buffer->apply_tag($tag, $textiter2, $textiter); 
    $kurzor_tag=$tag;
}
Vidíme, že na místě kurzoru se aplikoval náš tag