ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-IRC3/samples/CursesChatMainwindow.pm
Revision: 1.6
Committed: Wed Jan 17 09:29:41 2007 UTC (19 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.5: +24 -27 lines
Log Message:
changed completion to timeout the displayed partial-completion helpers.
added 'temporary' lines, which can be cleared from the buffers.
improved completion of many strings.
changed uri scheme again to jsirc, as jsirc is a special kind of IRC.
the client handles generally the 'json chat protocol' which works with any
kind of uris.

File Contents

# User Rev Content
1 elmex 1.1 package CursesChatMainwindow;
2     use strict;
3     use Curses;
4     use Term::ReadKey;
5     use Exporter;
6     our @ISA = qw/Exporter/;
7 elmex 1.6 our @EXPORT = qw/printline select_buffer current_buffer list_buffers clear_buffer clear_buffer_temporaries/;
8 elmex 1.1
9 elmex 1.4 our $wins;
10    
11 elmex 1.1 our $HEIGHT;
12     our $WIDTH;
13     our $MAINWIN;
14     our $stdiowatcher;
15 elmex 1.4 our $winchwatcher;
16 elmex 1.1
17     our $current_buffer_scroll = 0;
18     our $current_buffer = "status";
19     our $winbuffers = {};
20    
21 elmex 1.3 our $complete_cb = sub {};
22 elmex 1.1 our $input_cb = sub {};
23    
24 elmex 1.3 our %completions;
25 elmex 1.1 our @history;
26 elmex 1.5 our $hist_ptr = -1;
27 elmex 1.1 our $inputbuffer;
28     our $inputoffset = 0;
29     our $cursor = 0;
30     our $statusline;
31 elmex 1.4 our $msgline;
32 elmex 1.1
33 elmex 1.4 sub handle_resize {
34 elmex 1.1 my ($wc, $hc, $wpx, $hpx) = GetTerminalSize ();
35     resizeterm ($hc, $wc);
36     $MAINWIN->resize ($hc, $wc);
37     $HEIGHT = $hc;
38     $WIDTH = $wc;
39     $MAINWIN->erase;
40     refresh ();
41 elmex 1.4 }
42 elmex 1.1
43 elmex 1.3 sub complete {
44     my ($line) = @_;
45     my @words;
46     while ($line ne '') {
47     $line =~ s/^(\S+)//
48     or $line =~ s/^(\s+)//;
49     push @words, $1;
50     }
51     my $widx = @words - 1;
52     unless (@words) { $words[0] = ''; $widx = 0; }
53    
54 elmex 1.6 my $word = $complete_cb->($words[$widx], $widx, @words);
55 elmex 1.3 $words[$widx] = defined $word ? $word : $words[$widx];
56    
57     return join '', @words;
58     }
59    
60 elmex 1.1 sub color2attr {
61     my $c = $_[0];
62     my $attr;
63     if ($c <= 63) {
64     $attr = COLOR_PAIR($c);
65     } elsif ($c <= 127) {
66     $c -= 64;
67     $attr = COLOR_PAIR($c) | A_BOLD;
68     } elsif ($c <= 191) {
69     $c -= 128;
70     $attr = COLOR_PAIR($c) | A_UNDERLINE;
71     } else {
72     $c -= 192;
73     $attr = COLOR_PAIR($c) | A_UNDERLINE | A_BOLD;
74     }
75     $attr
76     }
77    
78     sub register_input_cb {
79     my ($cb) = @_;
80     $input_cb = $cb;
81     }
82    
83 elmex 1.3 sub register_complete_cb {
84     my ($cb) = @_;
85     $complete_cb = $cb;
86     }
87    
88 elmex 1.1 sub _draw_attr_line_at {
89     my ($line, $attrline) = @_;
90    
91     my @lcont = @$attrline;
92     my $li = 0;
93    
94     while (@lcont > 0) {
95     my ($color, $str) = (shift @lcont, shift @lcont);
96 elmex 1.6 while ($color eq 'f' or $color eq 't') {
97     if ($color eq 'f') {
98     ($color, $str) = ($str, shift @lcont);
99     $str .= " " x ($WIDTH - length ($str));
100     } elsif ($color eq 't') {
101     ($color, $str) = ($str, shift @lcont);
102     }
103 elmex 1.5 }
104 elmex 1.1
105     $MAINWIN->attron (color2attr ($color));
106    
107     my $sl = length $str;
108     if (($li + $sl) > $WIDTH) {
109     $sl = $WIDTH - $li;
110     last if $sl < 0;
111     }
112    
113     $MAINWIN->addnstr ($line, $li, (substr $str, 0, $sl), $sl);
114    
115     $li += $sl;
116    
117     $MAINWIN->attroff (color2attr ($color));
118     }
119     }
120    
121     sub clog {
122     open LOG, ">>/tmp/debuglog";
123     print LOG @_;
124     close LOG;
125     }
126    
127     sub wrap_attr_lines {
128     my ($width, @lines) = @_;
129    
130     my @outlines;
131    
132     for my $line (@lines) {
133     my $wrap_padding = 0;
134     my @cline = @$line;
135     my @oline;
136    
137     my $outc = 0;
138    
139     while (@cline) {
140     my ($color, $str) = (shift @cline, shift @cline);
141 elmex 1.6 while ($color =~ m/^p/ or $color eq 't') {
142     if ($color =~ m/^p\s*(\d+)\s*$/) {
143     $wrap_padding = $1;
144     ($color, $str) = ($str, shift @cline);
145     } elsif ($color eq 't') {
146     ($color, $str) = ($str, shift @cline);
147     }
148 elmex 1.1 }
149    
150     my $strlen = length $str;
151    
152     if (($outc + $strlen) > $width) {
153     my $thisl = $width - $outc;
154     push @oline, ($color, substr $str, 0, $thisl);
155     push @outlines, [@oline];
156     @oline = ();
157     unshift @cline, ($color, substr $str, $thisl);
158 elmex 1.6 if ($wrap_padding && ($wrap_padding + 7) < $width) { # 7 too few cols to display a sentence nicely
159 elmex 1.1 unshift @cline, (0, ' ' x $wrap_padding);
160     }
161     $outc = 0;
162     } else {
163     push @oline, ($color, $str);
164     $outc += $strlen;
165     }
166     }
167    
168     push @outlines, [@oline];
169     }
170    
171     # my $i = 0;
172     # for my $line (@outlines) {
173     # clog ("LINE$i: ".join ('|',@$line)."\n");
174     # $i++;
175     # }
176    
177     @outlines;
178     }
179    
180     sub refresh_lines {
181     $MAINWIN->erase;
182    
183 elmex 1.3 # TODO: cache the wrapped lines until something in the buffer
184     # or the window size changes
185     # NOTE: cache doesn't have to be invalidated for adding new lines!
186 elmex 1.1 my @revwin = @{$winbuffers->{$current_buffer} || []};
187     my (@out_lines) = reverse wrap_attr_lines ($WIDTH, @revwin);
188    
189     if ($current_buffer_scroll > 0) {
190     if ($current_buffer_scroll > scalar (@out_lines)) {
191     $current_buffer_scroll = scalar (@out_lines);
192     }
193     @out_lines = splice @out_lines, $current_buffer_scroll;
194     clog ("SPLICE$current_buffer_scroll [".(scalar @out_lines)."]\n");
195     }
196    
197 elmex 1.4 _draw_attr_line_at (0, $msgline || [0, 'nothing']);
198 elmex 1.1 _draw_attr_line_at ($HEIGHT - 2, $statusline || [0, 'nothing']);
199    
200     my $line = $HEIGHT - 3;
201 elmex 1.4 while ($line >= 1) {
202 elmex 1.1 my $l = shift @out_lines;
203     last unless defined $l;
204     _draw_attr_line_at ($line--, $l);
205     }
206     }
207    
208     sub refresh {
209     my ($onlyinput) = @_;
210    
211     refresh_lines unless $onlyinput;
212    
213     $MAINWIN->move ($HEIGHT - 1, 0);
214     $MAINWIN->clrtoeol ();
215    
216     my $padding = $WIDTH - int ($WIDTH / 1.2);
217    
218     if ($cursor >= ($WIDTH - $padding)) {
219     my $iobuf = $inputbuffer;
220     my $il = length $iobuf;
221     my $ncursor = $cursor;
222     my $lcursor = $cursor - int ($WIDTH / 1.2);
223     $ncursor -= $lcursor;
224     substr $iobuf, 0, $lcursor, '';
225     $MAINWIN->addstr ($HEIGHT - 1, 0, (substr $iobuf, 0, $WIDTH));
226     $MAINWIN->move ($HEIGHT - 1, $ncursor);
227     } else {
228     $MAINWIN->addstr ($HEIGHT - 1, 0, (substr $inputbuffer, 0, $WIDTH));
229     $MAINWIN->move ($HEIGHT - 1, $cursor);
230     }
231     $MAINWIN->refresh;
232     }
233    
234     sub select_buffer {
235     $current_buffer = $_[0];
236     $current_buffer_scroll = 0;
237     refresh ();
238     }
239    
240 elmex 1.2 sub current_buffer {
241     $current_buffer
242     }
243    
244 elmex 1.5 sub clear_buffer {
245     my ($buffer) = @_;
246     delete $winbuffers->{$buffer};
247     }
248    
249 elmex 1.6 sub clear_buffer_temporaries {
250     my ($buffer) = @_;
251     @{$winbuffers->{$buffer}} = grep { $_->[0] ne 't' } @{$winbuffers->{$buffer}};
252     refresh () if $buffer eq current_buffer ();
253     }
254    
255 elmex 1.3 sub list_buffers {
256     keys %$winbuffers;
257     }
258    
259 elmex 1.1 sub printline {
260     if ($_[0] eq 'statusline') {
261     $statusline = $_[1];
262 elmex 1.4 } elsif ($_[0] eq 'msgline') {
263     $msgline = $_[1];
264 elmex 1.1 } else {
265 elmex 1.3 push @{$winbuffers->{(defined $_[0] ? $_[0] : $current_buffer)}}, $_[1];
266 elmex 1.1 }
267     refresh ();
268     }
269    
270     sub input {
271     my $c = $MAINWIN->getch;
272 elmex 1.3
273 elmex 1.1 if ($c == KEY_BACKSPACE) {
274     if ($cursor > 0) {
275     substr $inputbuffer, --$cursor, 1, '';
276     }
277     } elsif ($c == KEY_DC) {
278     substr $inputbuffer, $cursor, 1, '';
279     } elsif ($c == KEY_LEFT) {
280     $cursor-- if $cursor > 0;
281     } elsif ($c == KEY_RIGHT) {
282     $cursor++ if (($cursor + 1) <= (length $inputbuffer));
283     } elsif ($c == KEY_PPAGE) {
284     $current_buffer_scroll += int ($HEIGHT / 2);
285     } elsif ($c == KEY_NPAGE) {
286     $current_buffer_scroll -= int ($HEIGHT / 2);
287     $current_buffer_scroll = 0 if $current_buffer_scroll < 0;
288 elmex 1.3 } elsif ($c eq "\t") {
289     my $compl_line = substr $inputbuffer, 0, $cursor;
290     $compl_line = complete ($compl_line);
291     substr $inputbuffer, 0, $cursor, $compl_line;
292     $cursor = length $compl_line;
293 elmex 1.1 } elsif ($c eq "\n") {
294 elmex 1.5 unshift @history, $inputbuffer;
295 elmex 1.1 $input_cb->($inputbuffer);
296     $inputbuffer = "";
297     $cursor = 0;
298 elmex 1.5 $hist_ptr = -1;
299     } elsif ($c == KEY_UP) {
300     if (($hist_ptr + 1) < scalar (@history)) {
301     if ($hist_ptr < 0 or $history[$hist_ptr] ne $inputbuffer) {
302     if ($inputbuffer =~ /\S/) {
303     unshift @history, $inputbuffer;
304     $hist_ptr++;
305     }
306     }
307    
308     $hist_ptr++;
309    
310     $inputbuffer = $history[$hist_ptr];
311     $cursor = length $inputbuffer;
312     }
313     } elsif ($c == KEY_DOWN) {
314     if ($hist_ptr - 1 <= -1) {
315     $inputbuffer = "";
316     $cursor = 0;
317     $hist_ptr = -1;
318    
319     } elsif (($hist_ptr - 1) >= 0) {
320     if ($hist_ptr < 0 or $history[$hist_ptr] ne $inputbuffer) {
321     if ($inputbuffer =~ /\S/) {
322     unshift @history, $inputbuffer;
323     $hist_ptr++;
324     }
325     }
326    
327     $inputbuffer = $history[--$hist_ptr];
328     $cursor = length $inputbuffer;
329     }
330 elmex 1.1 } else {
331     if ($c > 255) {
332 elmex 1.6 #d# printline (status => [0, "SPECIAL[$c]"]);
333 elmex 1.1 } else {
334 elmex 1.2 if (ord ($c) == 27) {
335     my $nxt = $MAINWIN->getch;
336     $input_cb->(undef, $nxt);
337     return;
338     } else {
339     substr $inputbuffer, $cursor++, 0, $c;
340     }
341 elmex 1.1 }
342     }
343     refresh ();
344     }
345    
346     sub init {
347     my $win = $MAINWIN = Curses->new;
348     cbreak;
349     noecho;
350     clear;
351     $win->keypad (1);
352     ($HEIGHT, $WIDTH) = ($LINES, $COLS);
353     if (has_colors ()) {
354     start_color ();
355     use_default_colors ();
356     my $colors = "";
357     my (@ansi_tab) = qw/0 4 2 6 1 5 3 7/;
358     for (my $i = 1; $i < $COLOR_PAIRS; $i++) {
359     init_pair ($i, $ansi_tab[$i & 7], $i <= 7 ? -1 : $ansi_tab[$i >> 3]);
360     }
361     }
362     $win->attron (A_NORMAL);
363 elmex 1.4 $winchwatcher = AnyEvent->signal (signal => 'WINCH', cb => sub { handle_resize });
364 elmex 1.1 $stdiowatcher = AnyEvent->io (fh => \*STDIN, poll => "r", cb => sub {
365     input;
366     });
367     }
368    
369     sub end {
370     endwin;
371     }
372    
373     sub print_colors {
374     my $l = [];
375     for (my $i = 0; $i <= 256; $i++) {
376     push @$l, $i, (sprintf "[%3d]", $i);
377     if (($i + 1) % 8 == 0) {
378     printline ($current_buffer => $l);
379     $l = [];
380     }
381     }
382     printline ($current_buffer => $l);
383     }
384     #printline ("TESTE!!!");
385    
386     1;