Fixed file permissions.
[mp-5.x.git] / mp_tui.mpsl
blob06a07f74742ae711409768d7b98ea1651161159e
1 /*
3     Minimum Profit 5.x
4     A Programmer's Text Editor
6     Text User Interface.
8     Copyright (C) 1991-2010 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 namspace */
30 mp.tui = {};
32 /** colors **/
34 mp.colors.menu = { 'text' => [ 'white', 'blue' ], 'flags' => [ 'bright' ] };
36 /** code **/
38 sub mp.tui.prompt(prompt, y)
39 /* draw a prompt on screen */
41         /* no y? default to last line */
42         if (y == NULL)
43                 y = mp.window.ty - 1;
45         /* delete all possible newlines */
46         prompt = sregex("/\n/g", prompt, ' ');
48         mp.tui.attr(mp.colors.menu.attr);
49         mp.tui.move(0, y, 1);
50         mp.tui.addstr(prompt);
51         mp.tui.attr(mp.colors.normal.attr);
52         mp.tui.refresh();
56 sub mp.tui.readline(prompt, history, default, flags)
57 /* the readline function, with special functionality in 'flags' */
59         local c, r, h, i, x;
61         mp.tui.prompt(prompt ~ ' ', flags.y);
62         c = mp.tui.getxy();
63         r = default || '';
65         /* get the history stack */
66         h = mp.get_history(history);
68         i = 0;
69         x = size(r);
71         /* store in c[2] the usable size */
72         push(c, mp.window.tx - c[0] - 1);
74         while (1) {
75                 local s = r;
76                 local v = 0;
78                 /* is the string bigger than the usable size? */
79                 if (size(r) >= c[2]) {
80                         /* if x is beyond the usable size,
81                            cut from the start */
82                         if (x >= c[2])
83                                 v = x - c[2];
85                         s = splice(s, NULL, v, c[2]);
86                         s = s[1];
87                 }
89                 /* if it's a password, change everything to asterisks */
90                 if (flags.password)
91                         s = sregex('/./g', s, flags.password);
93                 /* draws the string */
94                 mp.tui.move(c[0], c[1], 1);
95                 mp.tui.addstr(s);
96                 mp.tui.move(c[0] + x - v, c[1]);
98                 local k = mp.tui.getkey();
100                 if (k eq 'space')
101                         k = ' ';
103                 if (k eq 'enter')
104                         break;
105                 else
106                 if (k eq 'escape') {
107                         r = NULL;
108                         break;
109                 }
110                 else
111                 if (k eq 'backspace' && x > 0) {
112                         x--;
113                         r = splice(r, NULL, x, 1);
114                         r = r[0];
115                 }
116                 else
117                 if(k eq 'delete') {
118                         r = splice(r, NULL, x, 1);
119                         r = r[0];
120                 }
121                 else
122                 if (k eq 'ctrl-u') {
123                         x = 0;
124                         r = '';
125                 }
126                 else
127                 if (k eq 'ctrl-k') {
128                         r = splice(r, NULL, x, -1);
129                         r = r[0];
130                 }
131                 else
132                 if (k eq 'cursor-up' && size(h)) {
133                         i--;
134                         r = h[i % size(h)];
135                         x = size(r);
136                 }
137                 else
138                 if (k eq 'cursor-down' && size(h)) {
139                         i++;
140                         r = h[i % size(h)];
141                         x = size(r);
142                 }
143                 else
144                 if (k eq 'cursor-left' && x > 0) {
145                         x--;
146                 }
147                 else
148                 if (k eq 'cursor-right' && x < size(r)) {
149                         x++;
150                 }
151                 else
152                 if (k eq 'home') {
153                         x = 0;
154                 }
155                 else
156                 if (k eq 'end') {
157                         x = size(r);
158                 }
159                 else
160                 if (k eq 'tab' && flags.file) {
161                         local done = 0;
163                         while (!done) {
164                                 local l = glob(r ~ '*');
166                                 if (size(l)) {
167                                         ins(l, '../', 0);
169                                         local p = mp.tui.list(prompt, l, 0);
171                                         if (p == NULL) {
172                                                 r = NULL;
173                                                 done = 1;
174                                         }
175                                         else {
176                                                 r = l[p];
178                                                 /* if it's not a directory, retry */
179                                                 if (regex('@/$@', r) == NULL)
180                                                         done = 1;
181                                         }
182                                 }
183                         }
185                         break;
186                 }
187                 else
188                 if (size(k) == 1) {
189                         r = splice(r, k, x, 0);
190                         r = r[0];
191                         x++;
192                 }
193         }
195         /* if a string was accepted, store in the history */
196         if (h != NULL && size(r) && h[-1] ne r)
197                 push(h, r);
199         return r;
203 sub mp.tui.list(prompt, data, pos)
204 /* select from a list */
206         local vy, ty, r;
208         mp.tui.attr(mp.colors.menu.attr);
209         mp.tui.move(0, 0, 1);
210         mp.tui.addstr(prompt);
211         mp.tui.attr(mp.colors.normal.attr);
213         vy = 0;
214         ty = mp.window.ty - 1;
216         /* clipping regex */
217         r = '/^.{1,' ~ (mp.window.tx) ~ '}/';
219         if (pos == NULL)
220                 pos = 0;
222         while (1) {
223                 local k, n;
225                 /* limits for pos */
226                 if (pos < 0)
227                         pos = 0;
228                 if (pos >= size(data))
229                         pos = size(data) - 1;
231                 /* limits for vy */
232                 if (pos < vy)
233                         vy = pos;
234                 if (vy + ty <= pos)
235                         vy = pos - ty + 1;
237                 /* draw all the lines */
238                 n = 0;
239                 while (n < ty) {
240                         local l = data[n + vy];
242                         /* no more data? */
243                         if (l == NULL)
244                                 break;
246                         mp.tui.move(0, n + 1, 1);
248                         if (n + vy == pos)
249                                 mp.tui.attr(mp.colors.cursor.attr);
250                         else
251                                 mp.tui.attr(mp.colors.normal.attr);
253                         mp.tui.addstr(regex(r,
254                                 sprintf("%-" ~ mp.window.tx ~ "s", l)));
256                         n++;
257                 }
259                 /* clean the rest of lines */
260                 mp.tui.attr(mp.colors.normal.attr);
261                 while (n < ty) {
262                         mp.tui.move(0, n + 1, 1);
263                         n++;
264                 }
266                 k = mp.tui.getkey();
268                 if (k eq 'cursor-up')
269                         pos--;
270                 else
271                 if (k eq 'cursor-down')
272                         pos++;
273                 else
274                 if (k eq 'page-up')
275                         pos -= ty;
276                 else
277                 if (k eq 'page-down')
278                         pos += ty;
279                 else
280                 if (k eq 'home')
281                         pos = 0;
282                 else
283                 if (k eq 'end')
284                         pos = size(data) - 1;
285                 else
286                 if (k eq 'enter')
287                         break;
288                 else
289                 if (k eq 'escape') {
290                         pos = NULL;
291                         break;
292                 }
293         }
295         return pos;
299 sub mp.tui.confirm(msg, def, ypos)
301         local y, n;
302         local ret = NULL;
304         /* get the initials for localized 'Yes' and 'No' */
305         y = regex('/^./', L("Yes"));
306         n = regex('/^./', L("No"));
308         /* add options */
309         msg = msg ~ ' (' ~ y ~ '/' ~ n ~ ')';
311         if (def != NULL) {
312                 /* a default option? add to prompt */
313                 msg = msg ~ ' [' ~ (def && y || n) ~ ']';
314         }
316         mp.tui.prompt(msg, ypos);
318         while (ret == NULL) {
319                 local k = mp.tui.getkey();
321                 if (regex('/^' ~ y ~ '$/i', k))
322                         ret = 1;
323                 if (regex('/^' ~ n ~ '$/i', k))
324                         ret = 2;
325                 if (k eq 'escape')
326                         ret = 0;
327                 if (k eq 'enter')
328                         ret = (def && 1 || 2);
329         }
331         return ret;
335 sub mp.tui.draw(doc)
337         /* draw the document part */
338         mp.tui.doc_draw(doc);
340         /* draw the status line */
341         mp.tui.attr(mp.colors.normal.attr);
342         mp.tui.move(0, mp.window.ty - 1, 1);
343         mp.tui.addstr(mp.build_status_line());
345         /* draw the 'menu' hint */
346         local t = "ctrl-a: " ~ L("Menu");
347         mp.tui.move(mp.window.tx - size(t), mp.window.ty - 1);
348         mp.tui.addstr(t);
350         /* if a hardware cursor is desired, set it */
351         if (mp.config.hw_cursor) {
352                 mp.tui.move(
353                         mp.x2vx(doc.txt.lines[doc.txt.y],
354                                 doc.txt.x - doc.txt.vx),
355                         doc.txt.y - doc.txt.vy
356                 );
357         }
359         mp.tui.refresh();
363 /** interface **/
365 sub mp.drv.alert(msg)
367         mp.tui.prompt(msg ~ L(" [ENTER]"));
369         while (mp.tui.getkey() ne 'enter');
373 sub mp.drv.openfile(prompt)
375         mp.tui.readline(prompt, 'openfile', NULL, { 'file' => 1 } );
379 sub mp.drv.savefile(prompt)
381         mp.tui.readline(prompt, 'savefile', NULL, { 'file' => 1 } );
385 sub mp.drv.confirm(msg, def)
387         mp.tui.confirm(msg, def);
391 sub mp.drv.form(widgets)
393         local r = [];
394         local pos = mp.window.ty - size(widgets);
395         local y = pos;
397         /* print first all prompts */
398         foreach (w, widgets) {
399                 if (w.type ne 'list')
400                         mp.tui.prompt(w.label, y++);
401         }
403         y = pos;
405         /* now iterate widgets */
406         foreach (w, widgets) {
407                 local r1 = NULL;
409                 if (w.type eq 'text')
410                         r1 = mp.tui.readline(w.label, w.history, w.value,
411                                 { 'y' => y } );
412                 else
413                 if (w.type eq 'password')
414                         r1 = mp.tui.readline(w.label, NULL, NULL,
415                                 { 'password' => '*', 'y' => y });
416                 else
417                 if (w.type eq 'checkbox') {
418                         /* return value conversion */
419                         local c = [ NULL, 1, 0 ];
421                         r1 = c[mp.tui.confirm(w.label, w.value, y)];
422                 }
423                 else
424                 if (w.type eq 'list')
425                         r1 = mp.tui.list(w.label, w.list, w.value);
427                 /* cancellation? */
428                 if (r1 == NULL) {
429                         r = NULL;
430                         break;
431                 }
433                 /* store value */
434                 push(r, r1);
435                 y++;
436         }
438         return r;
442 sub mp.drv.menu()
444         local mx = 0;
445         local action = NULL;
446         local key = NULL;
448         while (action == NULL && key ne 'escape') {
449                 local pos, mo, my, vy;
450                 local n = 0;
452                 /* wrap */
453                 if (mx < 0)
454                         mx = size(mp.menu) - 1;
455                 if (mx >= size(mp.menu))
456                         mx = 0;
458                 /* draw the menu bar */
459                 mp.tui.attr(mp.colors.menu.attr);
460                 mp.tui.move(0, 0, 1);
462                 while (n < size(mp.menu)) {
463                         /* get the label */
464                         local l = L(mp.menu[n][0]);
466                         /* strip (by now) the & */
467                         l = sregex('/&/g', l, NULL);
469                         mp.tui.attr(mp.colors.menu.attr);
470                         mp.tui.addstr('   ');
472                         if (n == mx) {
473                                 pos = mp.tui.getxy();
474                                 mp.tui.attr(mp.colors.cursor.attr);
475                         }
477                         mp.tui.addstr(l);
479                         n++;
480                 }
482                 /* get the menu options */
483                 mo = mp.menu[mx][1];
485                 /* calculate panel optimal dimensions */
486                 pos[2] = 0;
488                 foreach (i, mo) {
489                         local l = mp.menu_label(i);
491                         if (size(l) > pos[2])
492                                 pos[2] = size(l);
493                 }
495                 /* if the panel will surpass the right margin,
496                    move to the left */
497                 if (pos[0] + pos[2] > mp.window.tx - 2)
498                         pos[0] = mp.window.tx - pos[2] - 2;
500                 local pty = size(mo);
502                 if (pty > mp.window.ty - 3)
503                         pty = mp.window.ty - 3;
505                 mp.tui.refresh();
506                 mp.tui.attr(mp.colors.menu.attr);
507                 mp.tui.openpanel(pos[0], 1, pos[2] + 2, pty + 2);
509                 my = 0;
510                 vy = 0;
512                 while (key ne 'escape') {
513                         /* draw the options */
514                         if (my < vy) vy = my;
515                         if (my > vy + (pty - 1)) vy = my - (pty - 1);
517                         n = 0;
519                         while (n < pty) {
520                                 local l = mp.menu_label(mo[n + vy]);
522                                 /* set color */
523                                 if (n == my - vy)
524                                         mp.tui.attr(mp.colors.cursor.attr);
525                                 else
526                                         mp.tui.attr(mp.colors.menu.attr);
528                                 mp.tui.move(1, 1 + n);
529                                 mp.tui.addstr(sprintf("%-" ~ pos[2] ~ "s", l || ''));
531                                 n++;
532                         }
534                         /* add visual cues that the menu is bigger */
535                         if (size(mo) > pty) {
536                                 mp.tui.attr(mp.colors.menu.attr);
538                                 if (vy) {
539                                         mp.tui.move(pos[2], 1);
540                                         mp.tui.addstr('+');
541                                 }
543                                 if (pty + vy < size(mo)) {
544                                         mp.tui.move(pos[2], pty);
545                                         mp.tui.addstr('+');
546                                 }
547                         }
549                         /* move the hw cursor to the selected option */
550                         mp.tui.move(1, 1 + my - vy);
552                         mp.tui.refresh();
554                         key = mp.tui.getkey();
556                         if (key eq 'cursor-up') {
557                                 /* move up avoiding separators */
558                                 while (1) {
559                                         if (--my < 0)
560                                                 my = size(mo) - 1;
562                                         if (mo[my] ne '-')
563                                                 break;
564                                 }
565                         }
566                         else
567                         if (key eq 'cursor-down') {
568                                 /* move down avoiding separators */
569                                 while (1) {
570                                         if (++my >= size(mo))
571                                                 my = 0;
573                                         if (mo[my] ne '-')
574                                                 break;
575                                 }
576                         }
577                         else
578                         if (key eq 'cursor-right') {
579                                 mx++;
580                                 break;
581                         }
582                         else
583                         if (key eq 'cursor-left') {
584                                 mx--;
585                                 break;
586                         }
587                         else
588                         if (key eq 'enter') {
589                                 action = mo[my];
590                                 break;
591                         }
592                 }
594                 mp.tui.closepanel();
595         }
597         mp.tui.attr(mp.color.normal.attr);
599         if (action != NULL)
600                 mp.process_action(action);
602         return NULL;
606 sub mp.drv.busy(onoff)
608         mp.tui.prompt(onoff && L("Please, wait...") || '');
612 sub mp.drv.main_loop()
614         while (!mp.exit_requested()) {
615                 mp.tui.draw(mp.active());
617                 mp.process_event(mp.tui.getkey());
618         }
622 /* returns the main namespace */
623 mp.tui;