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.2 by pcg, Mon Aug 4 00:30:10 2003 UTC vs.
Revision 1.14 by root, Tue Jun 8 21:33:08 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);
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
125} 217}
126 218
127sub append_text { 219sub append_text {
128 my ($self, $text) = @_; 220 my ($self, $text) = @_;
129 221
222 $self->_append_text ($self->{end}, $text);
223}
224
225sub _append_text {
226 my ($self, $mark, $text) = @_;
227
130 my $at_end = $self->at_end; 228 my $at_end = $self->at_end;
131 229
230 $text = "<default>$text</default>";
231
132 my @tag; 232 my @tag;
133 $text = "<default>$text</default>";
134
135 # pseudo-simplistic-xml-parser 233 # pseudo-simplistic-xml-parser
136 for (;;) { 234 for (;;) {
137 $text =~ /\G<([^>]+)>/gc or last; 235 $text =~ /\G<([^>]+)>/gc or last;
138 my $tag = $1; 236 my $tag = $1;
139 if ($tag =~ s/^\///) { 237 if ($tag =~ s/^\///) {
141 } else { 239 } else {
142 push @tag, $tag; 240 push @tag, $tag;
143 } 241 }
144 242
145 $text =~ /\G([^<]*)/gc or last; 243 $text =~ /\G([^<]*)/gc or last;
146 $self->{buffer}->insert_with_tags_by_name ($self->{buffer}->get_end_iter, util::xmlto $1, $tag[-1]) 244 $self->{buffer}->insert_with_tags_by_name ($self->{buffer}->get_iter_at_mark ($mark), util::xmlto $1, @tag)
147 if length $1; 245 if length $1;
148 } 246 }
149 247
150 $self->set_end if $at_end; 248 $self->set_end if $at_end;
151} 249}
159 $self->append_text ($text); 257 $self->append_text ($text);
160 258
161 $self->set_end if $at_end; 259 $self->set_end if $at_end;
162} 260}
163 261
262sub 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"
273sub 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
293sub 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
325package superchat::inlay;
326
327sub liter { $_[0]{buffer}->get_iter_at_mark ($_[0]{l}) }
328sub riter { $_[0]{buffer}->get_iter_at_mark ($_[0]{r}) }
329
330sub clear {
331 my ($self) = @_;
332 $self->{buffer}->delete ($self->liter, $self->riter);
333}
334
335sub append_text {
336 my ($self, $text) = @_;
337
338 $self->{parent}->_append_text ($self->{r}, $text);
339}
340
341sub 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
349 $widget;
350}
351
352sub append_optionmenu {
353 my ($self, $ref, @entry) = @_;
354
355 $self->append_widget (gtk::optionmenu $ref, @entry);
356}
357
358sub append_button {
359 my ($self, $label, $cb) = @_;
360
361 $self->append_widget (gtk::button $label, $cb);
362}
363
364sub visible { $_[0]{visible} }
365
366sub set_visible {
367 my ($self, $visible) = @_;
368
369 return if $self->{visible} == $visible;
370 $self->{visible} = $visible;
371
372 $self->refresh;
373}
374
375sub 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
390sub 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
403sub DESTROY {
404 my $self = shift;
405
406 $self->{parent}{tagtable}->remove (delete $self->{tag}) if $self->{tag} && $self->{parent};
407 #&destroy;
408}
409
1641; 4101;
165 411

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines