ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/kgsueme/kgsueme/chat.pl
(Generate patch)

Comparing kgsueme/kgsueme/chat.pl (file contents):
Revision 1.1 by pcg, Fri Jul 25 03:50:33 2003 UTC vs.
Revision 1.15 by root, Fri Jun 11 21:03:26 2004 UTC

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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines