|
|
1 | use utf8; |
|
|
2 | |
1 | package chat; |
3 | package chat; |
|
|
4 | |
|
|
5 | # waaay cool widget. well... maybe at one point in the future |
2 | |
6 | |
3 | use Gtk2; |
7 | use Gtk2; |
4 | |
8 | |
5 | use Glib::Object::Subclass |
9 | use Glib::Object::Subclass |
6 | Gtk2::VBox, |
10 | Gtk2::VBox, |
… | |
… | |
10 | return_type => undef, # void return |
14 | return_type => undef, # void return |
11 | param_types => [Glib::Scalar, Glib::Scalar], |
15 | param_types => [Glib::Scalar, Glib::Scalar], |
12 | }, |
16 | }, |
13 | }; |
17 | }; |
14 | |
18 | |
15 | my $tagtable = new Gtk2::TextTagTable; |
|
|
16 | |
|
|
17 | { |
|
|
18 | my %tags = ( |
|
|
19 | default => { foreground => "black" }, |
|
|
20 | node => { foreground => "#0000b0", event => 1 }, |
|
|
21 | move => { foreground => "#0000b0", event => 1 }, |
|
|
22 | user => { foreground => "#0000b0", event => 1 }, |
|
|
23 | coord => { foreground => "#0000b0", event => 1 }, |
|
|
24 | error => { foreground => "#ff0000", event => 1 }, |
|
|
25 | header => { weight => 800, pixels_above_lines => 6 }, |
|
|
26 | description => { weight => 800, foreground => "blue" }, |
|
|
27 | infoblock => { weight => 700, foreground => "blue" }, |
|
|
28 | ); |
|
|
29 | |
|
|
30 | while (my ($k, $v) = each %tags) { |
|
|
31 | my $tag = new Gtk2::TextTag $k; |
|
|
32 | if (delete $v->{event}) { |
|
|
33 | ### |
|
|
34 | } |
|
|
35 | $tag->set (%$v); |
|
|
36 | $tagtable->add ($tag); |
|
|
37 | } |
|
|
38 | } |
|
|
39 | |
|
|
40 | sub INIT_INSTANCE { |
19 | sub INIT_INSTANCE { |
41 | my $self = shift; |
20 | my $self = shift; |
|
|
21 | |
|
|
22 | my $tagtable = new Gtk2::TextTagTable; |
|
|
23 | |
|
|
24 | { |
|
|
25 | my @tags = ( |
|
|
26 | [default => { foreground => "black" }], |
|
|
27 | [node => { foreground => "#0000b0", event => 1 }], |
|
|
28 | [move => { foreground => "#0000b0", event => 1 }], |
|
|
29 | [user => { foreground => "#0000b0", event => 1 }], |
|
|
30 | [coord => { foreground => "#0000b0", event => 1 }], |
|
|
31 | [score => { foreground => "#0000b0", event => 1 }], |
|
|
32 | [error => { foreground => "#ff0000", event => 1 }], |
|
|
33 | [header => { weight => 800, pixels_above_lines => 6 }], |
|
|
34 | [undo => { foreground => "#ffff00", background => "#ff0000", weight => 800, pixels_above_lines => 6 }], |
|
|
35 | [challenge => { weight => 800, pixels_above_lines => 6, background => "#ffffb0" }], |
|
|
36 | [description => { weight => 800, foreground => "blue" }], |
|
|
37 | [infoblock => { weight => 700, foreground => "blue" }], |
|
|
38 | ); |
|
|
39 | |
|
|
40 | for (@tags) { |
|
|
41 | my ($k, $v) = @$_; |
|
|
42 | my $tag = new Gtk2::TextTag $k; |
|
|
43 | if (delete $v->{event}) { |
|
|
44 | ### |
|
|
45 | } |
|
|
46 | $tag->set (%$v); |
|
|
47 | $tagtable->add ($tag); |
|
|
48 | } |
|
|
49 | } |
|
|
50 | |
|
|
51 | $self->{tagtable} = $tagtable; |
42 | |
52 | |
43 | $self->signal_connect (destroy => sub { |
53 | $self->signal_connect (destroy => sub { |
44 | remove Glib::Source delete $self->{idle} if $self->{idle}; |
54 | remove Glib::Source delete $self->{idle} if $self->{idle}; |
45 | %{$_[0]} = (); |
55 | %{$_[0]} = (); |
46 | }); |
56 | }); |
47 | |
57 | |
48 | $self->{buffer} = new Gtk2::TextBuffer $tagtable; |
58 | $self->{buffer} = new Gtk2::TextBuffer $self->{tagtable}; |
49 | |
59 | |
50 | $self->{widget} = new Gtk2::ScrolledWindow; |
60 | $self->{widget} = new Gtk2::ScrolledWindow; |
51 | $self->{widget}->set_policy("never", "always"); |
61 | $self->{widget}->set_policy("never", "always"); |
52 | $self->pack_start ($self->{widget}, 1, 1, 0); |
62 | $self->pack_start ($self->{widget}, 1, 1, 0); |
53 | |
63 | |
… | |
… | |
81 | 0; |
91 | 0; |
82 | }); |
92 | }); |
83 | |
93 | |
84 | $self->pack_start (($self->{entry} = new Gtk2::Entry), 0, 1, 0); |
94 | $self->pack_start (($self->{entry} = new Gtk2::Entry), 0, 1, 0); |
85 | |
95 | |
86 | $self->{entry}->signal_connect(activate => sub { |
96 | $self->{entry}->signal_connect (activate => sub { |
87 | my ($entry) = @_; |
97 | my ($entry) = @_; |
88 | my $text = $entry->get_text; |
98 | my $text = $entry->get_text; |
89 | $entry->set_text(""); |
99 | $entry->set_text(""); |
90 | |
100 | |
91 | my ($cmd, $arg); |
101 | my ($cmd, $arg); |
… | |
… | |
97 | } |
107 | } |
98 | |
108 | |
99 | $self->signal_emit (command => $cmd, $arg); |
109 | $self->signal_emit (command => $cmd, $arg); |
100 | }); |
110 | }); |
101 | |
111 | |
|
|
112 | #$self->{end} = $self->{buffer}->create_mark (undef, $self->{buffer}->get_end_iter, 0);#d##todo# use this one for gtk-1.050+ |
|
|
113 | $self->{end} = $self->{buffer}->create_mark (++$USELESSNAME, $self->{buffer}->get_end_iter, 0); # workaround for gtk-perl bug |
102 | |
114 | |
103 | $self->set_end; |
115 | $self->set_end; |
104 | } |
116 | } |
105 | |
117 | |
106 | sub do_command { |
118 | sub do_command { |
… | |
… | |
127 | } |
139 | } |
128 | |
140 | |
129 | sub append_text { |
141 | sub append_text { |
130 | my ($self, $text) = @_; |
142 | my ($self, $text) = @_; |
131 | |
143 | |
|
|
144 | $self->_append_text ($self->{end}, $text); |
|
|
145 | } |
|
|
146 | |
|
|
147 | sub _append_text { |
|
|
148 | my ($self, $mark, $text) = @_; |
|
|
149 | |
132 | my $at_end = $self->at_end; |
150 | my $at_end = $self->at_end; |
133 | |
151 | |
|
|
152 | $text = "<default>$text</default>"; |
|
|
153 | |
134 | my @tag; |
154 | my @tag; |
135 | $text = "<default>$text</default>"; |
|
|
136 | |
|
|
137 | # pseudo-simplistic-xml-parser |
155 | # pseudo-simplistic-xml-parser |
138 | for (;;) { |
156 | for (;;) { |
139 | $text =~ /\G<([^>]+)>/gc or last; |
157 | $text =~ /\G<([^>]+)>/gc or last; |
140 | my $tag = $1; |
158 | my $tag = $1; |
141 | if ($tag =~ s/^\///) { |
159 | if ($tag =~ s/^\///) { |
… | |
… | |
143 | } else { |
161 | } else { |
144 | push @tag, $tag; |
162 | push @tag, $tag; |
145 | } |
163 | } |
146 | |
164 | |
147 | $text =~ /\G([^<]*)/gc or last; |
165 | $text =~ /\G([^<]*)/gc or last; |
148 | $self->{buffer}->insert_with_tags_by_name ($self->{buffer}->get_end_iter, util::xmlto $1, $tag[-1]) |
166 | $self->{buffer}->insert_with_tags_by_name ($self->{buffer}->get_iter_at_mark ($mark), util::xmlto $1, @tag) |
149 | if length $1; |
167 | if length $1; |
150 | } |
168 | } |
151 | |
169 | |
152 | $self->set_end if $at_end; |
170 | $self->set_end if $at_end; |
153 | } |
171 | } |
… | |
… | |
161 | $self->append_text ($text); |
179 | $self->append_text ($text); |
162 | |
180 | |
163 | $self->set_end if $at_end; |
181 | $self->set_end if $at_end; |
164 | } |
182 | } |
165 | |
183 | |
|
|
184 | sub new_eventtag { |
|
|
185 | my ($self, $cb) = @_; |
|
|
186 | |
|
|
187 | my $tag = new Gtk2::TextTag; |
|
|
188 | $tag->signal_connect (event => $cb); |
|
|
189 | $self->{tagtable}->add ($tag); |
|
|
190 | |
|
|
191 | $tag |
|
|
192 | } |
|
|
193 | |
|
|
194 | # create a new "subbuffer" |
|
|
195 | sub new_inlay { |
|
|
196 | my ($self) = @_; |
|
|
197 | |
|
|
198 | my $end = $self->{buffer}->get_end_iter; |
|
|
199 | |
|
|
200 | my $self = bless { |
|
|
201 | buffer => $self->{buffer}, |
|
|
202 | parent => $self, |
|
|
203 | }, superchat::inlay; |
|
|
204 | |
|
|
205 | # $USELESSNAME is a Gtk-perl < 1.042 workaround |
|
|
206 | $self->{l} = $self->{buffer}->create_mark (++$USELESSNAME, $end, 1); |
|
|
207 | $self->{buffer}->insert ($end, "\x{200d}"); |
|
|
208 | $self->{r} = $self->{buffer}->create_mark (++$USELESSNAME, $self->{buffer}->get_iter_at_mark ($self->{l}), 0); |
|
|
209 | |
|
|
210 | Scalar::Util::weaken $self->{buffer}; |
|
|
211 | Scalar::Util::weaken $self->{parent}; |
|
|
212 | $self; |
|
|
213 | } |
|
|
214 | |
|
|
215 | sub new_switchable_inlay { |
|
|
216 | my ($self, $header, $cb, $visible) = @_; |
|
|
217 | |
|
|
218 | my $inlay; |
|
|
219 | |
|
|
220 | my $tag = $self->new_eventtag (sub { |
|
|
221 | my ($tag, $view, $event, $iter) = @_; |
|
|
222 | |
|
|
223 | if ($event->type eq "button-press") { |
|
|
224 | $inlay->set_visible (!$inlay->{visible}); |
|
|
225 | return 1; |
|
|
226 | } |
|
|
227 | |
|
|
228 | 0; |
|
|
229 | }); |
|
|
230 | |
|
|
231 | $tag->set (background => "#e0e0ff"); |
|
|
232 | |
|
|
233 | $inlay = $self->new_inlay; |
|
|
234 | |
|
|
235 | $inlay->{visible} = $visible; |
|
|
236 | $inlay->{header} = $header; |
|
|
237 | $inlay->{tag} = $tag; |
|
|
238 | $inlay->{cb} = $cb; |
|
|
239 | |
|
|
240 | Scalar::Util::weaken $inlay->{tag}; |
|
|
241 | |
|
|
242 | $inlay->refresh; |
|
|
243 | |
|
|
244 | $inlay; |
|
|
245 | } |
|
|
246 | |
|
|
247 | package superchat::inlay; |
|
|
248 | |
|
|
249 | sub liter { $_[0]{buffer}->get_iter_at_mark ($_[0]{l}) } |
|
|
250 | sub riter { $_[0]{buffer}->get_iter_at_mark ($_[0]{r}) } |
|
|
251 | |
|
|
252 | sub clear { |
|
|
253 | my ($self) = @_; |
|
|
254 | $self->{buffer}->delete ($self->liter, $self->riter); |
|
|
255 | } |
|
|
256 | |
|
|
257 | sub append_text { |
|
|
258 | my ($self, $text) = @_; |
|
|
259 | |
|
|
260 | $self->{parent}->_append_text ($self->{r}, $text); |
|
|
261 | } |
|
|
262 | |
|
|
263 | sub append_widget { |
|
|
264 | my ($self, $widget) = @_; |
|
|
265 | |
|
|
266 | $widget->show_all; |
|
|
267 | |
|
|
268 | my $anchor = $self->{buffer}->create_child_anchor ($self->riter); |
|
|
269 | $self->{parent}{view}->add_child_at_anchor ($widget, $anchor); |
|
|
270 | } |
|
|
271 | |
|
|
272 | sub append_optionmenu { |
|
|
273 | my ($self, $ref, @entry) = @_; |
|
|
274 | |
|
|
275 | my @vals; |
|
|
276 | |
|
|
277 | my $widget = new Gtk2::OptionMenu; |
|
|
278 | $widget->set (menu => my $menu = new Gtk2::Menu); |
|
|
279 | |
|
|
280 | my $idx = 0; |
|
|
281 | |
|
|
282 | while (@entry >= 2) { |
|
|
283 | my $value = shift @entry; |
|
|
284 | my $label = shift @entry; |
|
|
285 | |
|
|
286 | $menu->append (new Gtk2::MenuItem $label); |
|
|
287 | push @vals, $value; |
|
|
288 | |
|
|
289 | if ($value eq $$ref && $idx >= 0) { |
|
|
290 | $widget->set_history ($idx); |
|
|
291 | $idx = -1e6; |
|
|
292 | } |
|
|
293 | $idx++; |
|
|
294 | } |
|
|
295 | |
|
|
296 | my $cb = shift @entry; |
|
|
297 | |
|
|
298 | $widget->signal_connect (changed => sub { |
|
|
299 | my $new = $vals[$_[0]->get_history]; |
|
|
300 | |
|
|
301 | if ($new ne $$ref) { |
|
|
302 | $$ref = $new; |
|
|
303 | $cb->($new) if $cb; |
|
|
304 | } |
|
|
305 | }); |
|
|
306 | |
|
|
307 | $self->append_widget ($widget); |
|
|
308 | |
|
|
309 | $widget; |
|
|
310 | } |
|
|
311 | |
|
|
312 | sub append_entry { |
|
|
313 | my ($self, $ref, $width, $cb) = @_; |
|
|
314 | |
|
|
315 | my $widget = new Gtk2::Entry; |
|
|
316 | $widget->set (text => $$ref, width_chars => $width); |
|
|
317 | eval { $widget->set (xalign => 1) }; # workaround für 2.2 |
|
|
318 | $widget->signal_connect (changed => sub { |
|
|
319 | $$ref = $_[0]->get_text; |
|
|
320 | $cb->($$ref) if $cb; |
|
|
321 | }); |
|
|
322 | |
|
|
323 | $self->append_widget ($widget); |
|
|
324 | $widget; |
|
|
325 | } |
|
|
326 | |
|
|
327 | sub append_button { |
|
|
328 | my ($self, $label, $cb) = @_; |
|
|
329 | |
|
|
330 | my $widget = new_with_label Gtk2::Button $label; |
|
|
331 | $widget->signal_connect (clicked => sub { $cb->() if $cb }); |
|
|
332 | |
|
|
333 | $self->append_widget ($widget); |
|
|
334 | $widget; |
|
|
335 | } |
|
|
336 | |
|
|
337 | sub visible { $_[0]{visible} } |
|
|
338 | |
|
|
339 | sub set_visible { |
|
|
340 | my ($self, $visible) = @_; |
|
|
341 | |
|
|
342 | return if $self->{visible} == $visible; |
|
|
343 | $self->{visible} = $visible; |
|
|
344 | |
|
|
345 | $self->refresh; |
|
|
346 | } |
|
|
347 | |
|
|
348 | sub refresh { |
|
|
349 | my ($self) = @_; |
|
|
350 | |
|
|
351 | $self->clear; |
|
|
352 | |
|
|
353 | my $arrow = $self->{visible} ? "⊟" : "⊞"; |
|
|
354 | |
|
|
355 | $self->{buffer}->insert ($self->riter, "\n"); |
|
|
356 | $self->{buffer}->insert_with_tags ($self->riter, util::xmlto "$arrow $self->{header}", $self->{tag}); |
|
|
357 | |
|
|
358 | return unless $self->{visible}; |
|
|
359 | |
|
|
360 | $self->{cb}->($self); |
|
|
361 | } |
|
|
362 | |
|
|
363 | sub destroy { |
|
|
364 | my ($self) = @_; |
|
|
365 | |
|
|
366 | return if !$self->{l} || !$self->{buffer} || $self->{l}->get_deleted; |
|
|
367 | |
|
|
368 | $self->clear if $self->{buffer}; |
|
|
369 | |
|
|
370 | delete $self->{parent}; |
|
|
371 | delete $self->{buffer}; |
|
|
372 | delete $self->{l}; |
|
|
373 | delete $self->{r}; |
|
|
374 | } |
|
|
375 | |
|
|
376 | sub DESTROY { |
|
|
377 | my $self = shift; |
|
|
378 | |
|
|
379 | $self->{parent}{tagtable}->remove (delete $self->{tag}) if $self->{tag} && $self->{parent}; |
|
|
380 | #&destroy; |
|
|
381 | } |
|
|
382 | |
166 | 1; |
383 | 1; |
167 | |
384 | |