Sync to Wine-0_9_5:
[reactos.git] / rosapps / mc / src / menu.c
1 /* Pulldown menu code.
2 Copyright (C) 1994 Miguel de Icaza.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
17
18 #include <config.h>
19 #include "tty.h"
20 #include <string.h>
21 #include <stdarg.h>
22 #include <sys/types.h>
23 #include <ctype.h>
24 #include <malloc.h>
25 #include "mad.h"
26 #include "util.h"
27 #include "menu.h"
28 #include "dialog.h"
29 #include "global.h"
30 #include "color.h"
31 #include "main.h"
32 #include "mouse.h"
33 #include "win.h"
34 #include "key.h" /* For mi_getch() */
35
36 /* "$Id$" */
37
38 extern int is_right;
39 int menubar_visible = 1; /* This is the new default */
40
41 Menu create_menu (char *name, menu_entry *entries, int count)
42 {
43 Menu menu;
44
45 menu = (Menu) xmalloc (sizeof (*menu), "create_menu");
46 menu->count = count;
47 menu->max_entry_len = 0;
48 menu->entries = entries;
49
50 #ifdef ENABLE_NLS
51 if (entries != (menu_entry*) 0)
52 {
53 register menu_entry* mp;
54 for (mp = entries; count--; mp++)
55 {
56 if (mp->text[0] == '\0')
57 continue;
58
59 mp->text = _(mp->text);
60 }
61 }
62 #endif /* ENABLE_NLS */
63
64 menu->name = _(name);
65 menu->start_x = 0;
66 return menu;
67 }
68
69 static void menubar_drop_compute (WMenu *menubar)
70 {
71 const Menu menu = menubar->menu [menubar->selected];
72 int max_entry_len = 0;
73 int i;
74
75 for (i = 0; i < menu->count; i++)
76 max_entry_len = max (max_entry_len, strlen (menu->entries [i].text));
77 menubar->max_entry_len = max_entry_len = max (max_entry_len, 20);
78 }
79
80 static void menubar_paint_idx (WMenu *menubar, int idx, int color)
81 {
82 const Menu menu = menubar->menu [menubar->selected];
83 const int y = 2 + idx;
84 int x = menubar-> menu[menubar->selected]->start_x;
85
86 if (x + menubar->max_entry_len + 3 > menubar->widget.cols)
87 x = menubar->widget.cols - menubar->max_entry_len - 3;
88
89 widget_move (&menubar->widget, y, x);
90 attrset (color);
91 hline (' ', menubar->max_entry_len+2);
92 if (!*menu->entries [idx].text){
93 attrset (SELECTED_COLOR);
94 widget_move (&menubar->widget, y, x + 1);
95 hline (slow_terminal ? ' ' : ACS_HLINE, menubar->max_entry_len);
96 } else {
97 char *text = menu->entries [idx].text;
98
99 addch(menu->entries [idx].first_letter);
100 for (text = menu->entries [idx].text; *text; text++)
101 {
102 if (*text == '&')
103 {
104 ++text;
105 menu->entries [idx].hot_key = tolower(*text);
106 attrset (color == MENU_SELECTED_COLOR ?
107 MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
108 addch(*text);
109 attrset(color);
110 continue;
111 }
112 addch(*text);
113 }
114 }
115 widget_move (&menubar->widget, y, x + 1);
116 }
117
118 static INLINE void menubar_draw_drop (WMenu *menubar)
119 {
120 const int count = (menubar->menu [menubar->selected])->count;
121 int i;
122 int sel = menubar->subsel;
123 int column = menubar-> menu[menubar->selected]->start_x - 1;
124
125 if (column + menubar->max_entry_len + 4 > menubar->widget.cols)
126 column = menubar->widget.cols - menubar->max_entry_len - 4;
127
128 attrset (SELECTED_COLOR);
129 draw_box (menubar->widget.parent,
130 menubar->widget.y+1, menubar->widget.x + column,
131 count+2, menubar->max_entry_len + 4);
132
133 column++;
134 for (i = 0; i < count; i++){
135 if (i == sel)
136 continue;
137 menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
138 }
139 menubar_paint_idx (menubar, sel, MENU_SELECTED_COLOR);
140 }
141
142 static void menubar_draw (WMenu *menubar)
143 {
144 const int items = menubar->items;
145 int i;
146
147 /* First draw the complete menubar */
148 attrset (SELECTED_COLOR);
149 widget_move (&menubar->widget, 0, 0);
150
151 /* ncurses bug: it should work with hline but it does not */
152 for (i = menubar->widget.cols; i; i--)
153 addch (' ');
154
155 attrset (SELECTED_COLOR);
156 /* Now each one of the entries */
157 for (i = 0; i < items; i++){
158 if (menubar->active)
159 attrset(i == menubar->selected?MENU_SELECTED_COLOR:SELECTED_COLOR);
160 widget_move (&menubar->widget, 0, menubar->menu [i]->start_x);
161 printw ("%s", menubar->menu [i]->name);
162 }
163
164 if (menubar->dropped)
165 menubar_draw_drop (menubar);
166 else
167 widget_move (&menubar->widget, 0,
168 menubar-> menu[menubar->selected]->start_x);
169 }
170
171 static INLINE void menubar_remove (WMenu *menubar)
172 {
173 menubar->subsel = 0;
174 if (menubar->dropped){
175 menubar->dropped = 0;
176 do_refresh ();
177 menubar->dropped = 1;
178 }
179 }
180
181 static void menubar_left (WMenu *menu)
182 {
183 menubar_remove (menu);
184 menu->selected = (menu->selected - 1) % menu->items;
185 if (menu->selected < 0)
186 menu->selected = menu->items -1;
187 menubar_drop_compute (menu);
188 menubar_draw (menu);
189 }
190
191 static void menubar_right (WMenu *menu)
192 {
193 menubar_remove (menu);
194 menu->selected = (menu->selected + 1) % menu->items;
195 menubar_drop_compute (menu);
196 menubar_draw (menu);
197 }
198
199 static void menubar_finish (WMenu *menubar)
200 {
201 menubar->dropped = 0;
202 menubar->active = 0;
203 menubar->widget.lines = 1;
204 widget_want_hotkey (menubar->widget, 0);
205 dlg_select_nth_widget (menubar->widget.parent,
206 menubar->previous_selection);
207 do_refresh ();
208 }
209
210 static void menubar_drop (WMenu *menubar, int selected)
211 {
212 menubar->dropped = 1;
213 menubar->selected = selected;
214 menubar->subsel = 0;
215 menubar_drop_compute (menubar);
216 menubar_draw (menubar);
217 }
218
219 static void menubar_execute (WMenu *menubar, int entry)
220 {
221 const Menu menu = menubar->menu [menubar->selected];
222
223 is_right = menubar->selected != 0;
224 (*menu->entries [entry].call_back)(0);
225 menubar_finish (menubar);
226 }
227
228 static void menubar_move (WMenu *menubar, int step)
229 {
230 const Menu menu = menubar->menu [menubar->selected];
231
232 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
233 do {
234 menubar->subsel += step;
235 if (menubar->subsel < 0)
236 menubar->subsel = menu->count - 1;
237
238 menubar->subsel %= menu->count;
239 } while (!menu->entries [menubar->subsel].call_back);
240 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
241 }
242
243 static int menubar_handle_key (WMenu *menubar, int key)
244 {
245 int i;
246
247 /* Lowercase */
248 if (key < 256 && isalpha (key)) /* Linux libc.so.5.x.x bug fix */
249 key = tolower (key);
250
251 if (is_abort_char (key)){
252 menubar_finish (menubar);
253 return 1;
254 }
255
256 if (key == KEY_LEFT || key == XCTRL('b')){
257 menubar_left (menubar);
258 return 1;
259 } else if (key == KEY_RIGHT || key == XCTRL ('f')){
260 menubar_right (menubar);
261 return 1;
262 }
263
264 /* .ado: NT Alpha can not allow CTRL in Menubar */
265 #if defined(_OS_NT)
266 if (!key)
267 return 0;
268 #endif
269
270 if (!menubar->dropped){
271 const int items = menubar->items;
272 for (i = 0; i < items; i++){
273 const Menu menu = menubar->menu [i];
274
275 /* Hack, we should check for the upper case letter */
276 if (tolower (menu->name [1]) == key){
277 menubar_drop (menubar, i);
278 return 1;
279 }
280 }
281 if (key == KEY_ENTER || key == XCTRL ('n') || key == KEY_DOWN
282 || key == '\n'){
283 menubar_drop (menubar, menubar->selected);
284 return 1;
285 }
286 return 1;
287 } else {
288 const int selected = menubar->selected;
289 const Menu menu = menubar->menu [selected];
290 const int items = menu->count;
291
292 for (i = 0; i < items; i++){
293 if (!menu->entries [i].call_back)
294 continue;
295
296 if (key != menu->entries [i].hot_key)
297 continue;
298
299 menubar_execute (menubar, i);
300 return 1;
301 }
302
303 if (key == KEY_ENTER || key == '\n'){
304 menubar_execute (menubar, menubar->subsel);
305 return 1;
306 }
307
308
309 if (key == KEY_DOWN || key == XCTRL ('n'))
310 menubar_move (menubar, 1);
311
312 if (key == KEY_UP || key == XCTRL ('p'))
313 menubar_move (menubar, -1);
314 }
315 return 0;
316 }
317
318 static int menubar_callback (Dlg_head *h, WMenu *menubar, int msg, int par)
319 {
320 switch (msg){
321 /* We do not want the focus unless we have been activated */
322 case WIDGET_FOCUS:
323 if (menubar->active){
324 widget_want_cursor (menubar->widget, 1);
325
326 /* Trick to get all the mouse events */
327 menubar->widget.lines = LINES;
328
329 /* Trick to get all of the hotkeys */
330 widget_want_hotkey (menubar->widget, 1);
331 menubar->subsel = 0;
332 menubar_drop_compute (menubar);
333 menubar_draw (menubar);
334 return 1;
335 } else
336 return 0;
337
338 /* We don't want the buttonbar to activate while using the menubar */
339 case WIDGET_HOTKEY:
340 case WIDGET_KEY:
341 if (menubar->active){
342 menubar_handle_key (menubar, par);
343 return 1;
344 } else
345 return 0;
346
347 case WIDGET_CURSOR:
348 /* Put the cursor in a suitable place */
349 return 0;
350
351 case WIDGET_UNFOCUS:
352 if (menubar->active)
353 return 0;
354 else {
355 widget_want_cursor (menubar->widget, 0);
356 return 1;
357 }
358
359 case WIDGET_DRAW:
360 if (menubar_visible)
361 menubar_draw (menubar);
362 }
363 return default_proc (h, msg, par);
364 }
365
366 int
367 menubar_event (Gpm_Event *event, WMenu *menubar)
368 {
369 int was_active;
370 int new_selection;
371 int left_x, right_x, bottom_y;
372
373 if (!(event->type & (GPM_UP|GPM_DOWN|GPM_DRAG)))
374 return MOU_NORMAL;
375
376 if (!menubar->dropped){
377 menubar->previous_selection = dlg_item_number(menubar->widget.parent);
378 menubar->active = 1;
379 menubar->dropped = 1;
380 was_active = 0;
381 } else
382 was_active = 1;
383
384 /* Mouse operations on the menubar */
385 if (event->y == 1 || !was_active){
386 if (event->type & GPM_UP)
387 return MOU_NORMAL;
388
389 new_selection = 0;
390 while (new_selection < menubar->items
391 && event->x > menubar->menu[new_selection]->start_x
392 )
393 new_selection++;
394
395 if (new_selection) /* Don't set the invalid value -1 */
396 --new_selection;
397
398 if (!was_active){
399 menubar->selected = new_selection;
400 dlg_select_widget (menubar->widget.parent, menubar);
401 menubar_drop_compute (menubar);
402 menubar_draw (menubar);
403 return MOU_NORMAL;
404 }
405
406 menubar_remove (menubar);
407
408 menubar->selected = new_selection;
409
410 menubar_drop_compute (menubar);
411 menubar_draw (menubar);
412 return MOU_NORMAL;
413 }
414
415 if (!menubar->dropped)
416 return MOU_NORMAL;
417
418 /* Ignore the events on anything below the third line */
419 if (event->y <= 2)
420 return MOU_NORMAL;
421
422 /* Else, the mouse operation is on the menus or it is not */
423 left_x = menubar->menu[menubar->selected]->start_x;
424 right_x = left_x + menubar->max_entry_len + 4;
425 if (right_x > menubar->widget.cols)
426 {
427 left_x = menubar->widget.cols - menubar->max_entry_len - 3;
428 right_x = menubar->widget.cols - 1;
429 }
430
431 bottom_y = (menubar->menu [menubar->selected])->count + 3;
432
433 if ((event->x > left_x) && (event->x < right_x) && (event->y < bottom_y)){
434 int pos = event->y - 3;
435
436 if (!menubar->menu [menubar->selected]->entries [pos].call_back)
437 return MOU_NORMAL;
438
439 menubar_paint_idx (menubar, menubar->subsel, MENU_ENTRY_COLOR);
440 menubar->subsel = pos;
441 menubar_paint_idx (menubar, menubar->subsel, MENU_SELECTED_COLOR);
442
443 if (event->type & GPM_UP)
444 menubar_execute (menubar, pos);
445 } else
446 if (event->type & GPM_DOWN)
447 menubar_finish (menubar);
448
449 return MOU_NORMAL;
450 }
451
452 static void menubar_destroy (WMenu *menubar)
453 {
454 }
455
456 /*
457 * Properly space menubar items. Should be called when menubar is created
458 * and also when widget width is changed (i.e. upon xterm resize).
459 */
460 void
461 menubar_arrange(WMenu* menubar)
462 {
463 register int i, start_x = 1;
464 int items = menubar->items;
465
466 #ifndef RESIZABLE_MENUBAR
467 int gap = 3;
468
469 for (i = 0; i < items; i++)
470 {
471 int len = strlen(menubar->menu[i]->name);
472 menubar->menu[i]->start_x = start_x;
473 start_x += len + gap;
474 }
475
476 #else /* RESIZABLE_MENUBAR */
477
478 int gap = menubar->widget.cols - 2;
479
480 /* First, calculate gap between items... */
481 for (i = 0; i < items; i++)
482 {
483 /* preserve length here, to be used below */
484 gap -= (menubar->menu[i]->start_x = strlen(menubar->menu[i]->name));
485 }
486
487 gap /= (items - 1);
488
489 if (gap <= 0)
490 {
491 /* We are out of luck - window is too narrow... */
492 gap = 1;
493 }
494
495 /* ...and now fix start positions of menubar items */
496 for (i = 0; i < items; i++)
497 {
498 int len = menubar->menu[i]->start_x;
499 menubar->menu[i]->start_x = start_x;
500 start_x += len + gap;
501 }
502 #endif /* RESIZABLE_MENUBAR */
503 }
504
505 void
506 destroy_menu (Menu menu)
507 {
508 free (menu);
509 }
510
511 WMenu *menubar_new (int y, int x, int cols, Menu menu [], int items)
512 {
513 WMenu *menubar = (WMenu *) xmalloc (sizeof (WMenu), "menubar_new");
514
515 memset(menubar, 0, sizeof(*menubar)); /* FIXME: subsel used w/o being set */
516 init_widget (&menubar->widget, y, x, 1, cols,
517 (callback_fn) menubar_callback,
518 (destroy_fn) menubar_destroy,
519 (mouse_h) menubar_event, NULL);
520 menubar->menu = menu;
521 menubar->active = 0;
522 menubar->dropped = 0;
523 menubar->items = items;
524 menubar->selected = 0;
525 widget_want_cursor (menubar->widget, 0);
526 menubar_arrange(menubar);
527
528 return menubar;
529 }