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