ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-IRC3/samples/CursesChatMainwindow.pm
Revision: 1.5
Committed: Tue Jan 16 19:39:17 2007 UTC (19 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.4: +44 -2 lines
Log Message:
Net::IRC3:
        - fixed case handling with channels
        - added functionality to change the nick automatically
          when it is already taken when registering an IRC connection.
          (Net::IRC3::Client::Connection)
        - added reply number <=> reply name mapping to Net::IRC3::Util
          accessible through rfc_code_to_name
        - added error event to Net::IRC3::Client::Connection
json chat framework:
        - client history
        - nick listing more correct
        - improved completion (added nick completion)
        - further improvement of the protocol
        - finally got the id handling correct
        - added logging to jsonsrv
        - many other changes i forgot.

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