Perl (126) - Gtk2 - textové okno a práce s pozicemi

Perl 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 10781×

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.

MetodaVýznam
set_wrap_modemetoda zalamování; možné hodnoty jsou none, char nebo word
set_editablefalse pro read-only, true pro editaci
set_cursor_visibleviditelnost kurzoru (kurzor bychom měli schovat vždy, když používáme read-only režim)
set_justificationzarovnání textu; možné hodnoty jsou left, right, center
set_left_margin, set_right_marginokraje
set_indentodsazení

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.

Vlastnosti TextBufferu

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.

MetodaVýznam
set_textnastaví 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_cursorvloží 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ů

Práce s pozicemi

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říkazKde 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.

MetodaVý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_positionzjišťování informací o pozici
in_range($zacatek, $konec)jsme v daném rozmezí?
Zjišťování absolutní pozice
get_offsetvrátí číslo znaku
get_linevrátí číslo řádku
get_line_offsetvrá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_endpohyb 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_marksvrátí seznam objektů Gtk2::TextMark
get_tagsvrátí seznam tagů, tj. objektů typu Gtk2::TextTag
get_pixbufvrátí seznam objektů Gtk2::PixBuf
get_child_anchorvrátí seznam objektů Gtk2::TextChildAnchor
has_tag, begins_tag, ends_tagdetekce 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_attributesvrá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říkazKde se vytvoří TextMark?
$textmark = $textbuffer->get_insertaktuální pozice kurzoru
$textmark = $textbuffer->get_selection_bounddruhý 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.

Tagy - formátování textu

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-gdkbarva pozadí pomocí řetězce, resp, Gtk2::Gdk::Color
foreground, foreground-gdkbarva popředí pomocí řetězce, resp, Gtk2::Gdk::Color
background-stipple, foreground-stipplepoužije se na pozadí / popředí maska (bitmapa)
fontpísmo (například Times 12)
size, size-pointsvelikost písma v Pango bodech, resp. v bodech
scalerelativní velikost písma oproti okolí
font-descstyl písma (jako objekt typu Gtk2::Pango::FontDescription)
familynapříklad Times
underlinepodtržené písmo
style, variant, weight, stretchhodnoty jsou v dokumentaci
pixels-above-lines, pixels-below-linesmezera v pixelech nad / pod řádkem
wrap-modejak zalamovat (none, word, char)
justificationleft, right, center
directionsměr toku textu (right-to-left, left-to-right)
left-margin, right-marginokraje
indentodsazení odstavce
strikethroughdělení
riseposunutí nahoru (dolů při záporném parametru)
background-full-heightmá-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

Vkládání widgetů

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

Příklad - zvýraznění textu pod kurzorem myši

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

tag pod kurzorem myši

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=1782