Updated TODO.
[mp-5.x.git] / mp_tui.mpsl
blobac5aff914ac940fe4960845d1f4717a50d4f9e38
1 /*
3     Minimum Profit 5.x
4     A Programmer's Text Editor
6     Text User Interface.
8     Copyright (C) 1991-2011 Angel Ortega <angel@triptico.com>
10     This program is free software; you can redistribute it and/or
11     modify it under the terms of the GNU General Public License
12     as published by the Free Software Foundation; either version 2
13     of the License, or (at your option) any later version.
15     This program is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18     GNU General Public License for more details.
20     You should have received a copy of the GNU General Public License
21     along with this program; if not, write to the Free Software
22     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24     http://www.triptico.com
28 /* main TUI namespace */
30 mp.tui = {};
32 /** colors **/
34 mp.colors.menu = {
35     text:   [ 'white', 'blue' ],
36     flags:  [ 'bright' ]
39 /** code **/
41 sub mp.tui.prompt(prompt, y)
42 /* draw a prompt on screen */
44         /* no y? default to last line */
45         if (y == NULL)
46                 y = mp.window.ty - 1;
48         /* delete all possible newlines */
49         prompt = sregex(prompt, "/\n/g", ' ');
51         mp.tui.attr(mp.colors.menu.attr);
52         mp.tui.move(0, y, 1);
53         mp.tui.addstr(prompt);
54         mp.tui.attr(mp.colors.normal.attr);
55         mp.tui.refresh();
59 sub mp.tui.readline(prompt, history, default, flags)
60 /* the readline function, with special functionality in 'flags' */
62         local c, r, h, i, x;
64         mp.tui.prompt(prompt ~ ' ', flags.y);
65         c = mp.tui.getxy();
66         r = default || '';
68         /* get the history stack */
69         h = mp.get_history(history);
71         i = 0;
72         x = size(r);
74         /* store in c[2] the usable size */
75         push(c, mp.window.tx - c[0] - 1);
77         while (1) {
78                 local s = r;
79                 local v = 0;
81                 /* is the string bigger than the usable size? */
82                 if (size(r) >= c[2]) {
83                         /* if x is beyond the usable size,
84                            cut from the start */
85                         if (x >= c[2])
86                                 v = x - c[2];
88                         s = splice(s, NULL, v, c[2]);
89                         s = s[1];
90                 }
92                 /* if it's a password, change everything to asterisks */
93                 if (flags.password)
94                         s = sregex(s, '/./g', flags.password);
96                 /* draws the string */
97                 mp.tui.move(c[0], c[1], 1);
98                 mp.tui.addstr(s);
99                 mp.tui.move(c[0] + x - v, c[1]);
101                 local k = mp.tui.getkey();
103                 if (k eq 'space')
104                         k = ' ';
106                 if (k eq 'enter')
107                         break;
108                 else
109                 if (k eq 'escape') {
110                         r = NULL;
111                         break;
112                 }
113                 else
114                 if (k eq 'backspace' && x > 0) {
115                         x--;
116                         r = splice(r, NULL, x, 1);
117                         r = r[0];
118                 }
119                 else
120                 if(k eq 'delete') {
121                         r = splice(r, NULL, x, 1);
122                         r = r[0];
123                 }
124                 else
125                 if (k eq 'ctrl-u') {
126                         x = 0;
127                         r = '';
128                 }
129                 else
130                 if (k eq 'ctrl-k') {
131                         r = splice(r, NULL, x, -1);
132                         r = r[0];
133                 }
134                 else
135                 if (k eq 'cursor-up' && size(h)) {
136                         i--;
137                         r = h[i % size(h)];
138                         x = size(r);
139                 }
140                 else
141                 if (k eq 'cursor-down' && size(h)) {
142                         i++;
143                         r = h[i % size(h)];
144                         x = size(r);
145                 }
146                 else
147                 if (k eq 'cursor-left' && x > 0) {
148                         x--;
149                 }
150                 else
151                 if (k eq 'cursor-right' && x < size(r)) {
152                         x++;
153                 }
154                 else
155                 if (k eq 'home') {
156                         x = 0;
157                 }
158                 else
159                 if (k eq 'end') {
160                         x = size(r);
161                 }
162                 else
163                 if (k eq 'tab' && flags.file) {
164                         local done = 0;
166                         while (!done) {
167                                 local l = glob(r ~ '*');
169                                 if (size(l)) {
170                                         ins(l, './', 0);
171                                         ins(l, '../', 1);
173                                         local p = mp.tui.list(prompt, l, 0);
175                                         if (p == NULL) {
176                                                 r = NULL;
177                                                 done = 1;
178                                         }
179                                         else {
180                                                 r = l[p];
182                                                 /* if it's not a directory, retry */
183                                                 if (regex(r, '@/$@') == NULL)
184                                                         done = 1;
185                                         }
186                                 }
187                 else
188                     break;
189                         }
191             if (done)
192                 break;
193                 }
194                 else
195                 if (size(k) == 1) {
196                         r = splice(r, k, x, 0);
197                         r = r[0];
198                         x++;
199                 }
200         }
202         /* if a string was accepted, store in the history */
203         if (h != NULL && size(r) && h[-1] ne r)
204                 push(h, r);
206         return r;
210 sub mp.tui.list(prompt, data, pos)
211 /* select from a list */
213         local vy, ty, r;
215         mp.tui.attr(mp.colors.menu.attr);
216         mp.tui.move(0, 0, 1);
217         mp.tui.addstr(prompt);
218         mp.tui.attr(mp.colors.normal.attr);
220         vy = 0;
221         ty = mp.window.ty - 1;
223         /* clipping regex */
224         r = '/^.{1,' ~ (mp.window.tx) ~ '}/';
226         if (pos == NULL)
227                 pos = 0;
229         while (1) {
230                 local k, n;
232                 /* limits for pos */
233                 if (pos < 0)
234                         pos = 0;
235                 if (pos >= size(data))
236                         pos = size(data) - 1;
238                 /* limits for vy */
239                 if (pos < vy)
240                         vy = pos;
241                 if (vy + ty <= pos)
242                         vy = pos - ty + 1;
244                 /* draw all the lines */
245                 n = 0;
246                 while (n < ty) {
247                         local l = data[n + vy];
249                         /* no more data? */
250                         if (l == NULL)
251                                 break;
253                         mp.tui.move(0, n + 1, 1);
255                         if (n + vy == pos)
256                                 mp.tui.attr(mp.colors.cursor.attr);
257                         else
258                                 mp.tui.attr(mp.colors.normal.attr);
260                         mp.tui.addstr(regex(sprintf("%-" ~ mp.window.tx ~ "s", l), r));
262                         n++;
263                 }
265                 /* clean the rest of lines */
266                 mp.tui.attr(mp.colors.normal.attr);
267                 while (n < ty) {
268                         mp.tui.move(0, n + 1, 1);
269                         n++;
270                 }
272                 k = mp.tui.getkey();
274                 if (k eq 'cursor-up')
275                         pos--;
276                 else
277                 if (k eq 'cursor-down')
278                         pos++;
279                 else
280                 if (k eq 'page-up')
281                         pos -= ty;
282                 else
283                 if (k eq 'page-down')
284                         pos += ty;
285                 else
286                 if (k eq 'home')
287                         pos = 0;
288                 else
289                 if (k eq 'end')
290                         pos = size(data) - 1;
291                 else
292                 if (k eq 'enter')
293                         break;
294                 else
295                 if (k eq 'escape') {
296                         pos = NULL;
297                         break;
298                 }
299         else
300         if ((ord(k) >= ord('a') && ord(k) <= ord('z')) ||
301             (ord(k) >= ord('A') && ord(k) <= ord('Z'))) {
302             /* search the first item >= k */
303             pos = 0;
305             while (pos < size(data) - 1) {
306                 local c = regex(data[pos], '/^./');
308                 if (ord(c) >= ord(k))
309                     break;
311                 pos++;
312             }
313         }
314         }
316         return pos;
320 sub mp.tui.confirm(msg, def, ypos)
322         local y, n;
323         local ret = NULL;
325         /* get the initials for localized 'Yes' and 'No' */
326         y = regex(L("Yes"), '/^./');
327         n = regex(L("No"), '/^./');
329         /* add options */
330         msg = msg ~ ' (' ~ y ~ '/' ~ n ~ ')';
332         if (def != NULL) {
333                 /* a default option? add to prompt */
334                 msg = msg ~ ' [' ~ (def && y || n) ~ ']';
335         }
337         mp.tui.prompt(msg, ypos);
339         while (ret == NULL) {
340                 local k = mp.tui.getkey();
342                 if (regex(k, '/^' ~ y ~ '$/i'))
343                         ret = 1;
344                 if (regex(k, '/^' ~ n ~ '$/i'))
345                         ret = 2;
346                 if (k eq 'escape')
347                         ret = 0;
348                 if (k eq 'enter')
349                         ret = (def && 1 || 2);
350         }
352         return ret;
356 sub mp.tui.draw(doc)
358         /* draw the document part */
359         mp.tui.doc_draw(doc);
361         /* draw the status line */
362         mp.tui.attr(mp.colors.normal.attr);
363         mp.tui.move(0, mp.window.ty - 1, 1);
364         mp.tui.addstr(mp.build_status_line());
366         /* draw the 'menu' hint */
367         local t = "ctrl-a: " ~ L("Menu");
368         mp.tui.move(mp.window.tx - size(t), mp.window.ty - 1);
369         mp.tui.addstr(t);
371         /* if a hardware cursor is desired, set it */
372         if (mp.config.hw_cursor) {
373                 mp.tui.move(
374                         mp.x2vx(doc.txt.lines[doc.txt.y],
375                                 doc.txt.x - doc.txt.vx),
376                         doc.txt.y - doc.txt.vy
377                 );
378         }
380         mp.tui.refresh();
384 /** interface **/
386 sub mp.drv.alert(msg)
388         mp.tui.prompt(msg ~ L(" [ENTER]"));
390         while (mp.tui.getkey() ne 'enter');
394 sub mp.drv.openfile(prompt)
396         mp.tui.readline(prompt, 'openfile', NULL, { 'file' => 1 } );
400 sub mp.drv.savefile(prompt)
402         mp.tui.readline(prompt, 'savefile', NULL, { 'file' => 1 } );
406 sub mp.drv.confirm(msg, def)
408         mp.tui.confirm(msg, def);
412 sub mp.drv.form(widgets)
414         local r = [];
415         local pos = mp.window.ty - size(widgets);
416         local y = pos;
418         /* print first all prompts */
419         foreach (w, widgets) {
420                 if (w.type ne 'list')
421                         mp.tui.prompt(w.label, y++);
422         }
424         y = pos;
426         /* now iterate widgets */
427         foreach (w, widgets) {
428                 local r1 = NULL;
430                 if (w.type eq 'text')
431                         r1 = mp.tui.readline(w.label, w.history, w.value,
432                                 { 'y' => y } );
433                 else
434                 if (w.type eq 'password')
435                         r1 = mp.tui.readline(w.label, NULL, NULL,
436                                 { 'password' => '*', 'y' => y });
437                 else
438                 if (w.type eq 'checkbox') {
439                         /* return value conversion */
440                         local c = [ NULL, 1, 0 ];
442                         r1 = c[mp.tui.confirm(w.label, w.value, y)];
443                 }
444                 else
445                 if (w.type eq 'list')
446                         r1 = mp.tui.list(w.label, w.list, w.value);
448                 /* cancellation? */
449                 if (r1 == NULL) {
450                         r = NULL;
451                         break;
452                 }
454                 /* store value */
455                 push(r, r1);
456                 y++;
457         }
459         return r;
463 sub mp.drv.menu()
465         local mx = 0;
466         local action = NULL;
467         local key = NULL;
469         while (action == NULL && key ne 'escape') {
470                 local pos, mo, my, vy;
471                 local n = 0;
473                 /* wrap */
474                 if (mx < 0)
475                         mx = size(mp.menu) - 1;
476                 if (mx >= size(mp.menu))
477                         mx = 0;
479                 /* draw the menu bar */
480                 mp.tui.attr(mp.colors.menu.attr);
481                 mp.tui.move(0, 0, 1);
483                 while (n < size(mp.menu)) {
484                         /* get the label */
485                         local l = L(mp.menu[n][0]);
487                         /* strip (by now) the & */
488                         l = sregex(l, '/&/g', NULL);
490                         mp.tui.attr(mp.colors.menu.attr);
491                         mp.tui.addstr('   ');
493                         if (n == mx) {
494                                 pos = mp.tui.getxy();
495                                 mp.tui.attr(mp.colors.cursor.attr);
496                         }
498                         mp.tui.addstr(l);
500                         n++;
501                 }
503                 /* get the menu options */
504                 mo = mp.menu[mx][1];
506                 /* calculate panel optimal dimensions */
507                 pos[2] = 0;
509                 foreach (i, mo) {
510                         local l = mp.menu_label(i);
512                         if (size(l) > pos[2])
513                                 pos[2] = size(l);
514                 }
516                 /* if the panel will surpass the right margin,
517                    move to the left */
518                 if (pos[0] + pos[2] > mp.window.tx - 2)
519                         pos[0] = mp.window.tx - pos[2] - 2;
521                 local pty = size(mo);
523                 if (pty > mp.window.ty - 3)
524                         pty = mp.window.ty - 3;
526                 mp.tui.refresh();
527                 mp.tui.attr(mp.colors.menu.attr);
528                 mp.tui.openpanel(pos[0], 1, pos[2] + 2, pty + 2);
530                 my = 0;
531                 vy = 0;
533                 while (key ne 'escape') {
534                         /* draw the options */
535                         if (my < vy) vy = my;
536                         if (my > vy + (pty - 1)) vy = my - (pty - 1);
538                         n = 0;
540                         while (n < pty) {
541                                 local l = mp.menu_label(mo[n + vy]);
543                                 /* set color */
544                                 if (n == my - vy)
545                                         mp.tui.attr(mp.colors.cursor.attr);
546                                 else
547                                         mp.tui.attr(mp.colors.menu.attr);
549                                 mp.tui.move(1, 1 + n);
550                                 mp.tui.addstr(sprintf("%-" ~ pos[2] ~ "s", l || ''));
552                                 n++;
553                         }
555                         /* add visual cues that the menu is bigger */
556                         if (size(mo) > pty) {
557                                 mp.tui.attr(mp.colors.menu.attr);
559                                 if (vy) {
560                                         mp.tui.move(pos[2], 1);
561                                         mp.tui.addstr('+');
562                                 }
564                                 if (pty + vy < size(mo)) {
565                                         mp.tui.move(pos[2], pty);
566                                         mp.tui.addstr('+');
567                                 }
568                         }
570                         /* move the hw cursor to the selected option */
571                         mp.tui.move(1, 1 + my - vy);
573                         mp.tui.refresh();
575                         key = mp.tui.getkey();
577                         if (key eq 'cursor-up') {
578                                 /* move up avoiding separators */
579                                 while (1) {
580                                         if (--my < 0)
581                                                 my = size(mo) - 1;
583                                         if (mo[my] ne '-')
584                                                 break;
585                                 }
586                         }
587                         else
588                         if (key eq 'cursor-down') {
589                                 /* move down avoiding separators */
590                                 while (1) {
591                                         if (++my >= size(mo))
592                                                 my = 0;
594                                         if (mo[my] ne '-')
595                                                 break;
596                                 }
597                         }
598                         else
599                         if (key eq 'cursor-right') {
600                                 mx++;
601                                 break;
602                         }
603                         else
604                         if (key eq 'cursor-left') {
605                                 mx--;
606                                 break;
607                         }
608                         else
609                         if (key eq 'enter') {
610                                 action = mo[my];
611                                 break;
612                         }
613                 }
615                 mp.tui.closepanel();
616         }
618         mp.tui.attr(mp.color.normal.attr);
620         if (action != NULL)
621                 mp.process_action(action);
623         return NULL;
627 sub mp.drv.busy(onoff)
629         mp.tui.prompt(onoff && L("Please, wait...") || '');
633 sub mp.drv.main_loop()
635         while (!mp.exit_requested()) {
636                 mp.tui.draw(mp.active());
638                 mp.process_event(mp.tui.getkey());
639         }
643 /* returns the main namespace */
644 mp.tui;