ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/kgsueme/kgsueme/chat.pl
Revision: 1.13
Committed: Tue Jun 8 17:50:12 2004 UTC (19 years, 11 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.12: +33 -6 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.4 use utf8;
2    
3 pcg 1.1 package chat;
4    
5 root 1.4 # waaay cool widget. well... maybe at one point in the future
6    
7 pcg 1.1 use Gtk2;
8 root 1.12 use Gtk2::Pango;
9 pcg 1.1
10     use Glib::Object::Subclass
11     Gtk2::VBox,
12     signals => {
13     command => {
14 root 1.13 flags => [qw/run-last/],
15 root 1.12 return_type => undef,
16     param_types => [Glib::Scalar, Glib::Scalar],
17     class_closure => sub { },
18     },
19     tag_event => {
20 root 1.13 flags => [qw/run-last/],
21 root 1.12 return_type => undef,
22     # tag, event, content
23     param_types => [Glib::String, Gtk2::Gdk::Event, Glib::String],
24 root 1.13 class_closure => \&tag_event,
25 root 1.12 },
26     enter_tag => {
27 root 1.13 flags => [qw/run-last/],
28 root 1.12 return_type => undef,
29     # tag, content
30     param_types => [Glib::String, Glib::String],
31     class_closure => sub { },
32     },
33     leave_tag => {
34 root 1.13 flags => [qw/run-last/],
35 root 1.12 return_type => undef,
36 root 1.13 # tag, content
37 root 1.12 param_types => [Glib::String, Glib::String],
38     class_closure => sub { },
39 pcg 1.1 },
40     };
41    
42 root 1.13 sub new {
43     my ($self, %arg) = @_;
44    
45     $self = $self->Glib::Object::new;
46     $self->{$_} = delete $arg{$_} for keys %arg;
47    
48     $self;
49     }
50    
51 root 1.4 sub INIT_INSTANCE {
52     my $self = shift;
53 pcg 1.1
54 root 1.4 my $tagtable = new Gtk2::TextTagTable;
55    
56     {
57     my @tags = (
58 root 1.12 [default => { foreground => "black", wrap_mode => "word-char" }],
59 root 1.4 [node => { foreground => "#0000b0", event => 1 }],
60     [move => { foreground => "#0000b0", event => 1 }],
61 root 1.11 [user => { foreground => "#0000b0", event => 1 }],
62 root 1.4 [coord => { foreground => "#0000b0", event => 1 }],
63     [score => { foreground => "#0000b0", event => 1 }],
64     [error => { foreground => "#ff0000", event => 1 }],
65 root 1.9 [leader => { weight => 800, pixels_above_lines => 6 }],
66 root 1.4 [header => { weight => 800, pixels_above_lines => 6 }],
67     [undo => { foreground => "#ffff00", background => "#ff0000", weight => 800, pixels_above_lines => 6 }],
68     [challenge => { weight => 800, pixels_above_lines => 6, background => "#ffffb0" }],
69     [description => { weight => 800, foreground => "blue" }],
70     [infoblock => { weight => 700, foreground => "blue" }],
71     );
72    
73     for (@tags) {
74     my ($k, $v) = @$_;
75     my $tag = new Gtk2::TextTag $k;
76     if (delete $v->{event}) {
77 root 1.12 $tag->signal_connect (event => sub {
78     my ($tag, $view, $event, $iter) = @_;
79 root 1.13
80     return 0 if $event->type eq "motion-notify";
81    
82 root 1.12 my ($a, $b) = ($iter, $iter->copy);
83     $a->backward_to_tag_toggle ($tag) unless $a->begins_tag ($tag);
84     $b->forward_to_tag_toggle ($tag) unless $b->ends_tag ($tag);
85    
86     $self->signal_emit (tag_event => $k, $event, $a->get_text ($b));
87 root 1.13
88     1;
89 root 1.12 });
90 root 1.4 }
91     $tag->set (%$v);
92     $tagtable->add ($tag);
93 pcg 1.1 }
94     }
95    
96 root 1.4 $self->{tagtable} = $tagtable;
97 pcg 1.1
98 pcg 1.3 $self->signal_connect (destroy => sub {
99     remove Glib::Source delete $self->{idle} if $self->{idle};
100     %{$_[0]} = ();
101     });
102    
103 root 1.4 $self->{buffer} = new Gtk2::TextBuffer $self->{tagtable};
104 pcg 1.1
105     $self->{widget} = new Gtk2::ScrolledWindow;
106 root 1.11 $self->{widget}->set_policy ("automatic", "always");
107 pcg 1.1 $self->pack_start ($self->{widget}, 1, 1, 0);
108    
109     $self->{widget}->add ($self->{view} = new_with_buffer Gtk2::TextView $self->{buffer});
110 root 1.12 $self->{view}->set (
111     wrap_mode => "word-char",
112     cursor_visible => 0,
113     editable => 0,
114     tabs =>
115     (new Gtk2::Pango::TabArray 1, 0, left => 125000), # arbitrary... pango is underfeatured
116     );
117 pcg 1.1
118     $self->{view}->signal_connect (motion_notify_event => sub {
119     my ($widget, $event) = @_;
120    
121     my $window = $widget->get_window ("text");
122     if ($event->window == $window) {
123     my ($win, $x, $y, $mask) = $window->get_pointer;
124 root 1.12 ($x, $y) = $self->{view}->window_to_buffer_coords ("text", $x, $y);
125     my ($iter) = $self->{view}->get_iter_at_location ($x, $y);
126    
127     my $tag = ($iter->get_tags)[01];
128    
129     if ($tag) {
130     my ($a, $b) = ($iter, $iter->copy);
131     $a->backward_to_tag_toggle ($tag) unless $a->begins_tag ($tag);
132     $b->forward_to_tag_toggle ($tag) unless $b->ends_tag ($tag);
133    
134     $self->tag_enterleave ($tag->get ("name"), $a->get_text ($b));
135     } else {
136     $self->tag_enterleave ();
137     }
138    
139     1;
140 pcg 1.1 }
141     0;
142     });
143    
144 root 1.12 $self->{view}->signal_connect (leave_notify_event => sub {
145     $self->tag_enterleave ();
146     0;
147     });
148    
149     $self->{view}->add_events (qw(leave_notify_mask));
150    
151 pcg 1.1 $self->pack_start (($self->{entry} = new Gtk2::Entry), 0, 1, 0);
152    
153 root 1.4 $self->{entry}->signal_connect (activate => sub {
154 pcg 1.1 my ($entry) = @_;
155     my $text = $entry->get_text;
156     $entry->set_text("");
157    
158     my ($cmd, $arg);
159    
160     if ($text =~ /^\/(\S+)\s*(.*)$/) {
161     ($cmd, $arg) = ($1, $2);
162     } else {
163 pcg 1.2 ($cmd, $arg) = ("say", $text);
164 pcg 1.1 }
165    
166     $self->signal_emit (command => $cmd, $arg);
167     });
168    
169 root 1.4 #$self->{end} = $self->{buffer}->create_mark (undef, $self->{buffer}->get_end_iter, 0);#d##todo# use this one for gtk-1.050+
170     $self->{end} = $self->{buffer}->create_mark (++$USELESSNAME, $self->{buffer}->get_end_iter, 0); # workaround for gtk-perl bug
171 pcg 1.1
172     $self->set_end;
173     }
174    
175 root 1.12 sub tag_enterleave {
176     my ($self, $tag, $content) = @_;
177    
178     my $cur = $self->{current_tag};
179    
180     if ($cur->[0] != $tag || $cur->[1] ne $content) {
181     $self->signal_emit (leave_tag => @$cur) if $cur->[0];
182     $self->{current_tag} = $cur = [$tag, $content];
183     $self->signal_emit (enter_tag => @$cur) if $cur->[0];
184 root 1.13 }
185     }
186    
187     sub tag_event {
188     my ($self, $tag, $event, $content) = @_;
189    
190     return unless $self->{app};
191    
192     if ($tag eq "user" && $event->type eq "button-release") {
193     if ($event->button == 1) {
194     $content =~ /^(\S+)/ or return;
195     $self->{app}->open_user (name => $1);
196     }
197 root 1.12 }
198 pcg 1.1 }
199    
200     sub set_end {
201     my ($self) = @_;
202    
203     # this is probably also a hack...
204     $self->{idle} ||= add Glib::Idle sub {
205     $self->{view}->scroll_to_iter ($self->{buffer}->get_end_iter, 0, 0, 0, 0)
206     if $self->{view};
207     delete $self->{idle};
208     };
209     }
210    
211     sub at_end {
212     my ($self) = @_;
213    
214     # this is, maybe, a bad hack :/
215     my $adj = $self->{widget}->get_vadjustment;
216     $adj->value + $adj->page_size >= $adj->upper - 0.5;
217     }
218    
219     sub append_text {
220     my ($self, $text) = @_;
221    
222 root 1.4 $self->_append_text ($self->{end}, $text);
223     }
224    
225     sub _append_text {
226     my ($self, $mark, $text) = @_;
227    
228 pcg 1.1 my $at_end = $self->at_end;
229    
230     $text = "<default>$text</default>";
231    
232 root 1.4 my @tag;
233 pcg 1.1 # pseudo-simplistic-xml-parser
234     for (;;) {
235     $text =~ /\G<([^>]+)>/gc or last;
236     my $tag = $1;
237     if ($tag =~ s/^\///) {
238     pop @tag;
239     } else {
240     push @tag, $tag;
241     }
242    
243     $text =~ /\G([^<]*)/gc or last;
244 root 1.4 $self->{buffer}->insert_with_tags_by_name ($self->{buffer}->get_iter_at_mark ($mark), util::xmlto $1, @tag)
245 pcg 1.1 if length $1;
246     }
247    
248     $self->set_end if $at_end;
249     }
250    
251     sub set_text {
252     my ($self, $text) = @_;
253    
254     my $at_end = $self->at_end;
255    
256     $self->{buffer}->set_text ("");
257     $self->append_text ($text);
258    
259     $self->set_end if $at_end;
260 root 1.4 }
261    
262     sub new_eventtag {
263     my ($self, $cb) = @_;
264    
265     my $tag = new Gtk2::TextTag;
266     $tag->signal_connect (event => $cb);
267     $self->{tagtable}->add ($tag);
268    
269     $tag
270     }
271    
272     # create a new "subbuffer"
273     sub new_inlay {
274     my ($self) = @_;
275    
276     my $end = $self->{buffer}->get_end_iter;
277    
278     my $self = bless {
279     buffer => $self->{buffer},
280     parent => $self,
281     }, superchat::inlay;
282    
283     # $USELESSNAME is a Gtk-perl < 1.042 workaround
284     $self->{l} = $self->{buffer}->create_mark (++$USELESSNAME, $end, 1);
285     $self->{buffer}->insert ($end, "\x{200d}");
286     $self->{r} = $self->{buffer}->create_mark (++$USELESSNAME, $self->{buffer}->get_iter_at_mark ($self->{l}), 0);
287    
288     Scalar::Util::weaken $self->{buffer};
289     Scalar::Util::weaken $self->{parent};
290     $self;
291     }
292    
293     sub new_switchable_inlay {
294     my ($self, $header, $cb, $visible) = @_;
295    
296     my $inlay;
297    
298     my $tag = $self->new_eventtag (sub {
299     my ($tag, $view, $event, $iter) = @_;
300    
301     if ($event->type eq "button-press") {
302     $inlay->set_visible (!$inlay->{visible});
303     return 1;
304     }
305    
306     0;
307     });
308    
309     $tag->set (background => "#e0e0ff");
310    
311     $inlay = $self->new_inlay;
312    
313     $inlay->{visible} = $visible;
314     $inlay->{header} = $header;
315     $inlay->{tag} = $tag;
316     $inlay->{cb} = $cb;
317    
318     Scalar::Util::weaken $inlay->{tag};
319    
320     $inlay->refresh;
321    
322     $inlay;
323     }
324    
325     package superchat::inlay;
326    
327     sub liter { $_[0]{buffer}->get_iter_at_mark ($_[0]{l}) }
328     sub riter { $_[0]{buffer}->get_iter_at_mark ($_[0]{r}) }
329    
330     sub clear {
331     my ($self) = @_;
332     $self->{buffer}->delete ($self->liter, $self->riter);
333     }
334    
335     sub append_text {
336     my ($self, $text) = @_;
337    
338     $self->{parent}->_append_text ($self->{r}, $text);
339     }
340    
341     sub append_widget {
342     my ($self, $widget) = @_;
343    
344     $widget->show_all;
345    
346     my $anchor = $self->{buffer}->create_child_anchor ($self->riter);
347     $self->{parent}{view}->add_child_at_anchor ($widget, $anchor);
348 root 1.10
349     $widget;
350 root 1.4 }
351    
352     sub append_optionmenu {
353     my ($self, $ref, @entry) = @_;
354    
355 root 1.10 $self->append_widget (gtk::optionmenu $ref, @entry);
356 root 1.4 }
357    
358     sub append_button {
359     my ($self, $label, $cb) = @_;
360    
361 root 1.10 $self->append_widget (gtk::button $label, $cb);
362 root 1.4 }
363    
364     sub visible { $_[0]{visible} }
365    
366     sub set_visible {
367     my ($self, $visible) = @_;
368    
369     return if $self->{visible} == $visible;
370     $self->{visible} = $visible;
371    
372     $self->refresh;
373     }
374    
375     sub refresh {
376     my ($self) = @_;
377    
378     $self->clear;
379    
380     my $arrow = $self->{visible} ? "⊟" : "⊞";
381    
382     $self->{buffer}->insert ($self->riter, "\n");
383     $self->{buffer}->insert_with_tags ($self->riter, util::xmlto "$arrow $self->{header}", $self->{tag});
384    
385     return unless $self->{visible};
386    
387     $self->{cb}->($self);
388     }
389    
390     sub destroy {
391     my ($self) = @_;
392    
393     return if !$self->{l} || !$self->{buffer} || $self->{l}->get_deleted;
394    
395     $self->clear if $self->{buffer};
396    
397     delete $self->{parent};
398     delete $self->{buffer};
399     delete $self->{l};
400     delete $self->{r};
401     }
402    
403     sub DESTROY {
404     my $self = shift;
405    
406     $self->{parent}{tagtable}->remove (delete $self->{tag}) if $self->{tag} && $self->{parent};
407     #&destroy;
408 pcg 1.1 }
409    
410     1;
411