ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-IRC3/samples/CursesChatMainwindow.pm
Revision: 1.4
Committed: Fri Jan 5 09:51:54 2007 UTC (19 years, 9 months ago) by elmex
Branch: MAIN
Changes since 1.3: +11 -3 lines
Log Message:
added another status line to the top of the window

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