ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-IRC3/samples/CursesChatMainwindow.pm
Revision: 1.3
Committed: Mon Dec 25 21:54:46 2006 UTC (19 years, 9 months ago) by elmex
Branch: MAIN
Changes since 1.2: +56 -6 lines
Log Message:
improved the json chat client: fixed scrolling a bit, implemented
completion and fixed other weird behaviour

File Contents

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