mc/src/menu.c

718 lines
18 KiB
C
Raw Normal View History

/* Copyright (C) 1994, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
2007, 2009 Free Software Foundation, Inc.
1998-02-27 07:54:42 +03:00
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
1998-02-27 07:54:42 +03:00
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
1998-02-27 07:54:42 +03:00
/** \file menu.c
* \brief Source: pulldown menu code
*/
1998-02-27 07:54:42 +03:00
#include <config.h>
2005-02-08 12:04:03 +03:00
#include <ctype.h>
1998-02-27 07:54:42 +03:00
#include <stdarg.h>
2005-02-08 12:04:03 +03:00
#include <string.h>
1998-02-27 07:54:42 +03:00
#include <sys/types.h>
2005-02-08 12:04:03 +03:00
#include "global.h"
#include "../src/tty/tty.h"
#include "../src/skin/skin.h"
#include "../src/tty/mouse.h"
2009-05-26 19:01:58 +04:00
#include "../src/tty/key.h" /* key macros */
1998-02-27 07:54:42 +03:00
#include "menu.h"
#include "help.h"
1998-02-27 07:54:42 +03:00
#include "dialog.h"
#include "widget.h"
#include "main.h" /* is_right */
#include "strutil.h"
1998-02-27 07:54:42 +03:00
int menubar_visible = 1; /* This is the new default */
menu_entry_t *
menu_entry_create (const char *name, menu_exec_fn cmd)
1998-02-27 07:54:42 +03:00
{
menu_entry_t *entry;
1998-02-27 07:54:42 +03:00
entry = g_new (menu_entry_t, 1);
entry->first_letter = ' ';
entry->text = parse_hotkey (name);
entry->callback = cmd;
return entry;
}
void
menu_entry_free (menu_entry_t *entry)
{
if (entry != NULL) {
release_hotkey (entry->text);
g_free (entry);
}
}
static void
menu_arrange (Menu* menu)
{
if (menu != NULL) {
unsigned int i;
for (i = 0; i < menu->entries->len; i++) {
menu_entry_t *entry;
size_t len;
entry = g_ptr_array_index (menu->entries, i);
if (entry != NULL) {
len = (size_t) hotkey_width (entry->text);
menu->max_entry_len = max (menu->max_entry_len, len);
}
}
}
}
Menu *
create_menu (const char *name, GPtrArray *entries, const char *help_node)
{
Menu *menu;
menu = g_new (Menu, 1);
menu->start_x = 0;
menu->text = parse_hotkey (name);
menu->entries = entries;
menu->max_entry_len = 1;
menu->selected = 0;
menu->help_node = g_strdup (help_node);
menu_arrange (menu);
1998-02-27 07:54:42 +03:00
return menu;
}
void
destroy_menu (Menu *menu)
{
release_hotkey (menu->text);
g_ptr_array_foreach (menu->entries, (GFunc) menu_entry_free, NULL);
g_ptr_array_free (menu->entries, TRUE);
g_free (menu->help_node);
g_free (menu);
}
void
menu_add_entry (Menu *menu, const char *name, menu_exec_fn cmd)
{
if (menu->entries == NULL)
menu->entries = g_ptr_array_new ();
g_ptr_array_add (menu->entries,
menu_entry_create (name, cmd));
menu_arrange (menu);
}
void
menu_add_separator (Menu *menu)
1998-02-27 07:54:42 +03:00
{
if (menu->entries == NULL)
menu->entries = g_ptr_array_new ();
g_ptr_array_add (menu->entries, menu_separator_create ());
1998-02-27 07:54:42 +03:00
}
static void
menubar_paint_idx (WMenuBar *menubar, unsigned int idx, int color)
1998-02-27 07:54:42 +03:00
{
const Menu *menu = g_ptr_array_index (menubar->menu, menubar->selected);
const menu_entry_t *entry = g_ptr_array_index (menu->entries, idx);
1998-02-27 07:54:42 +03:00
const int y = 2 + idx;
int x = menu->start_x;
if (x + menu->max_entry_len + 3 > menubar->widget.cols)
x = menubar->widget.cols - menu->max_entry_len - 3;
1998-02-27 07:54:42 +03:00
if (entry == NULL) {
/* menu separator */
tty_setcolor (MENU_ENTRY_COLOR);
1998-02-27 07:54:42 +03:00
widget_move (&menubar->widget, y, x - 1);
tty_print_alt_char (ACS_LTEE);
tty_draw_hline (menubar->widget.y + y, menubar->widget.x + x,
ACS_HLINE, menu->max_entry_len + 2);
widget_move (&menubar->widget, y, x + menu->max_entry_len + 2);
tty_print_alt_char (ACS_RTEE);
} else {
/* menu text */
tty_setcolor (color);
widget_move (&menubar->widget, y, x);
tty_print_char ((unsigned char) entry->first_letter);
tty_draw_hline (-1, -1, ' ', menu->max_entry_len + 1); /* clear line */
tty_print_string (entry->text.start);
if (entry->text.hotkey != NULL) {
tty_setcolor (color == MENU_SELECTED_COLOR ?
MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
tty_print_string (entry->text.hotkey);
tty_setcolor (color);
}
if (entry->text.end != NULL)
tty_print_string (entry->text.end);
/* move cursor to the start of entry text */
widget_move (&menubar->widget, y, x + 1);
1998-02-27 07:54:42 +03:00
}
}
static void
menubar_draw_drop (WMenuBar *menubar)
1998-02-27 07:54:42 +03:00
{
const Menu *menu = g_ptr_array_index (menubar->menu, menubar->selected);
const unsigned int count = menu->entries->len;
int column = menu->start_x - 1;
unsigned int i;
if (column + menu->max_entry_len + 4 > menubar->widget.cols)
column = menubar->widget.cols - menu->max_entry_len - 4;
1998-02-27 07:54:42 +03:00
tty_setcolor (MENU_ENTRY_COLOR);
1998-02-27 07:54:42 +03:00
draw_box (menubar->widget.parent,
menubar->widget.y + 1, menubar->widget.x + column,
count + 2, menu->max_entry_len + 4);
/* draw items except selected */
for (i = 0; i < count; i++)
if (i != menu->selected)
menubar_paint_idx (menubar, i, MENU_ENTRY_COLOR);
/* draw selected item at last to move cursor to the nice location */
menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
1998-02-27 07:54:42 +03:00
}
static void
menubar_set_color (WMenuBar *menubar, int current, gboolean hotkey)
{
if (!menubar->is_active)
tty_setcolor (hotkey ? COLOR_HOT_FOCUS : SELECTED_COLOR);
else if (current == menubar->selected)
tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
else
tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
}
static void
menubar_draw (WMenuBar *menubar)
1998-02-27 07:54:42 +03:00
{
unsigned int i;
1998-02-27 07:54:42 +03:00
/* First draw the complete menubar */
tty_setcolor (menubar->is_active ? MENU_ENTRY_COLOR : SELECTED_COLOR);
2009-06-03 23:07:06 +04:00
tty_draw_hline (menubar->widget.y, menubar->widget.x, ' ', menubar->widget.cols);
1998-02-27 07:54:42 +03:00
/* Now each one of the entries */
for (i = 0; i < menubar->menu->len; i++) {
Menu *menu = g_ptr_array_index (menubar->menu, i);
menubar_set_color (menubar, i, FALSE);
widget_move (&menubar->widget, 0, menu->start_x);
tty_print_string (menu->text.start);
if (menu->text.hotkey != NULL) {
menubar_set_color (menubar, i, TRUE);
tty_print_string (menu->text.hotkey);
menubar_set_color (menubar, i, FALSE);
}
if (menu->text.end != NULL)
tty_print_string (menu->text.end);
1998-02-27 07:54:42 +03:00
}
if (menubar->is_dropped)
1998-02-27 07:54:42 +03:00
menubar_draw_drop (menubar);
else
widget_move (&menubar->widget, 0,
((Menu *) g_ptr_array_index (menubar->menu,
menubar->selected))->start_x);
1998-02-27 07:54:42 +03:00
}
static void
menubar_remove (WMenuBar *menubar)
1998-02-27 07:54:42 +03:00
{
if (menubar->is_dropped) {
menubar->is_dropped = FALSE;
1998-02-27 07:54:42 +03:00
do_refresh ();
menubar->is_dropped = TRUE;
1998-02-27 07:54:42 +03:00
}
}
static void
menubar_left (WMenuBar *menubar)
1998-02-27 07:54:42 +03:00
{
menubar_remove (menubar);
if (menubar->selected == 0)
menubar->selected = menubar->menu->len - 1;
else
menubar->selected--;
menubar_draw (menubar);
1998-02-27 07:54:42 +03:00
}
static void
menubar_right (WMenuBar *menubar)
1998-02-27 07:54:42 +03:00
{
menubar_remove (menubar);
menubar->selected = (menubar->selected + 1) % menubar->menu->len;
menubar_draw (menubar);
1998-02-27 07:54:42 +03:00
}
static void
menubar_finish (WMenuBar *menubar)
1998-02-27 07:54:42 +03:00
{
menubar->is_dropped = FALSE;
menubar->is_active = FALSE;
1998-02-27 07:54:42 +03:00
menubar->widget.lines = 1;
widget_want_hotkey (menubar->widget, 0);
dlg_select_by_id (menubar->widget.parent, menubar->previous_widget);
1998-02-27 07:54:42 +03:00
do_refresh ();
}
static void
menubar_drop (WMenuBar *menubar, unsigned int selected)
1998-02-27 07:54:42 +03:00
{
menubar->is_dropped = TRUE;
1998-02-27 07:54:42 +03:00
menubar->selected = selected;
menubar_draw (menubar);
}
static void
menubar_execute (WMenuBar *menubar, unsigned int idx)
1998-02-27 07:54:42 +03:00
{
const Menu *menu = g_ptr_array_index (menubar->menu, menubar->selected);
const menu_entry_t *entry = g_ptr_array_index (menu->entries, idx);
if ((entry == NULL) || (entry->callback == NULL))
return;
is_right = (menubar->selected != 0);
/* This used to be the other way round, i.e. first callback and
then menubar_finish. The new order (hack?) is needed to make
change_panel () work which is used in quick_view_cmd () -- Norbert
*/
1998-02-27 07:54:42 +03:00
menubar_finish (menubar);
(*entry->callback) ();
do_refresh ();
1998-02-27 07:54:42 +03:00
}
static void
menubar_down (WMenuBar *menubar)
1998-02-27 07:54:42 +03:00
{
Menu *menu = g_ptr_array_index (menubar->menu, menubar->selected);
menu_entry_t *entry;
menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
1998-02-27 07:54:42 +03:00
do {
menu->selected = (menu->selected + 1) % menu->entries->len;
entry = (menu_entry_t *) g_ptr_array_index (menu->entries, menu->selected);
} while ((entry == NULL) || (entry->callback == NULL));
menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
1998-02-27 07:54:42 +03:00
}
static void
menubar_up (WMenuBar *menubar)
1998-02-27 07:54:42 +03:00
{
Menu *menu = g_ptr_array_index (menubar->menu, menubar->selected);
menu_entry_t *entry;
menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
1998-02-27 07:54:42 +03:00
do {
if (menu->selected == 0)
menu->selected = menu->entries->len - 1;
else
menu->selected--;
entry = (menu_entry_t *) g_ptr_array_index (menu->entries, menu->selected);
} while ((entry == NULL) || (entry->callback == NULL));
menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
}
static int
menubar_handle_key (WMenuBar *menubar, int key)
{
1998-02-27 07:54:42 +03:00
/* Lowercase */
if (isascii (key))
key = g_ascii_tolower (key);
if (is_abort_char (key)) {
1998-02-27 07:54:42 +03:00
menubar_finish (menubar);
return 1;
}
/* menubar help or menubar navigation */
switch (key) {
case KEY_F(1):
if (menubar->is_dropped)
interactive_display (NULL,
((Menu *) g_ptr_array_index (menubar->menu,
menubar->selected))->help_node);
else
interactive_display (NULL, "[Menu Bar]");
menubar_draw (menubar);
return 1;
case KEY_LEFT:
case XCTRL('b'):
1998-02-27 07:54:42 +03:00
menubar_left (menubar);
return 1;
case KEY_RIGHT:
case XCTRL ('f'):
1998-02-27 07:54:42 +03:00
menubar_right (menubar);
return 1;
}
if (!menubar->is_dropped) {
unsigned int i;
/* drop menu by hotkey */
for (i = 0; i < menubar->menu->len; i++) {
Menu *menu = g_ptr_array_index (menubar->menu, i);
1998-02-27 07:54:42 +03:00
if ((menu->text.hotkey != NULL)
&& (key == g_ascii_tolower (menu->text.hotkey[0]))) {
menubar_drop (menubar, i);
return 1;
1998-02-27 07:54:42 +03:00
}
}
/* drop menu by Enter or Dowwn key */
if (key == KEY_ENTER || key == XCTRL ('n')
|| key == KEY_DOWN || key == '\n')
1998-02-27 07:54:42 +03:00
menubar_drop (menubar, menubar->selected);
1998-02-27 07:54:42 +03:00
return 1;
}
{
const Menu *menu = g_ptr_array_index (menubar->menu, menubar->selected);
unsigned int i;
/* execute menu callback by hotkey */
for (i = 0; i < menu->entries->len; i++) {
const menu_entry_t *entry = g_ptr_array_index (menu->entries, i);
1998-02-27 07:54:42 +03:00
if ((entry != NULL) && (entry->callback != NULL)
&& (entry->text.hotkey != NULL)
&& (key == g_ascii_tolower (entry->text.hotkey[0]))) {
menubar_execute (menubar, i);
return 1;
}
}
/* menu execute by Enter or menu navigation */
switch (key) {
case KEY_ENTER:
case '\n':
menubar_execute (menubar, menu->selected);
1998-02-27 07:54:42 +03:00
return 1;
case KEY_DOWN:
case XCTRL ('n'):
menubar_down (menubar);
break;
case KEY_UP:
case XCTRL ('p'):
menubar_up (menubar);
break;
1998-02-27 07:54:42 +03:00
}
}
1998-02-27 07:54:42 +03:00
return 0;
}
static cb_ret_t
2005-05-23 20:39:52 +04:00
menubar_callback (Widget *w, widget_msg_t msg, int parm)
1998-02-27 07:54:42 +03:00
{
WMenuBar *menubar = (WMenuBar *) w;
2005-05-23 20:39:52 +04:00
switch (msg) {
1998-02-27 07:54:42 +03:00
/* We do not want the focus unless we have been activated */
case WIDGET_FOCUS:
if (!menubar->is_active)
return MSG_NOT_HANDLED;
1998-02-27 07:54:42 +03:00
widget_want_cursor (menubar->widget, 1);
1998-02-27 07:54:42 +03:00
/* Trick to get all the mouse events */
menubar->widget.lines = LINES;
/* Trick to get all of the hotkeys */
widget_want_hotkey (menubar->widget, 1);
menubar_draw (menubar);
return MSG_HANDLED;
1998-02-27 07:54:42 +03:00
/* We don't want the buttonbar to activate while using the menubar */
case WIDGET_HOTKEY:
case WIDGET_KEY:
if (menubar->is_active) {
menubar_handle_key (menubar, parm);
return MSG_HANDLED;
}
return MSG_NOT_HANDLED;
1998-02-27 07:54:42 +03:00
case WIDGET_CURSOR:
/* Put the cursor in a suitable place */
return MSG_NOT_HANDLED;
1998-02-27 07:54:42 +03:00
case WIDGET_UNFOCUS:
if (menubar->is_active)
return MSG_NOT_HANDLED;
widget_want_cursor (menubar->widget, 0);
return MSG_HANDLED;
1998-02-27 07:54:42 +03:00
case WIDGET_DRAW:
if (menubar_visible) {
1998-02-27 07:54:42 +03:00
menubar_draw (menubar);
return MSG_HANDLED;
}
/* fall through */
case WIDGET_RESIZED:
/* try show menu after screen resize */
send_message (w, WIDGET_FOCUS, 0);
return MSG_HANDLED;
case WIDGET_DESTROY:
menubar_set_menu (menubar, NULL);
return MSG_HANDLED;
default:
return default_proc (msg, parm);
1998-02-27 07:54:42 +03:00
}
}
static int
menubar_event (Gpm_Event *event, void *data)
1998-02-27 07:54:42 +03:00
{
WMenuBar *menubar = data;
gboolean was_active = TRUE;
1998-02-27 07:54:42 +03:00
int left_x, right_x, bottom_y;
Menu *menu;
1998-02-27 07:54:42 +03:00
/* ignore unsupported events */
if ((event->type & (GPM_UP | GPM_DOWN | GPM_DRAG)) == 0)
1998-02-27 07:54:42 +03:00
return MOU_NORMAL;
/* ignore wheel events if menu is inactive */
if (!menubar->is_active
&& ((event->buttons & (GPM_B_MIDDLE | GPM_B_UP | GPM_B_DOWN)) != 0))
return MOU_NORMAL;
if (!menubar->is_dropped) {
menubar->previous_widget = menubar->widget.parent->current->dlg_id;
menubar->is_active = TRUE;
menubar->is_dropped = TRUE;
was_active = FALSE;
}
1998-02-27 07:54:42 +03:00
/* Mouse operations on the menubar */
if (event->y == 1 || !was_active) {
if ((event->type & GPM_UP) != 0)
1998-02-27 07:54:42 +03:00
return MOU_NORMAL;
/* wheel events on menubar */
if (event->buttons & GPM_B_UP)
menubar_left (menubar);
else if (event->buttons & GPM_B_DOWN)
menubar_right (menubar);
else {
int new_selection = 0;
while ((new_selection < menubar->menu->len)
&& (event->x > ((Menu *) g_ptr_array_index (menubar->menu,
new_selection))->start_x))
new_selection++;
if (new_selection != 0) /* Don't set the invalid value -1 */
new_selection--;
1998-02-27 07:54:42 +03:00
if (!was_active) {
menubar->selected = new_selection;
dlg_select_widget (menubar);
} else {
menubar_remove (menubar);
menubar->selected = new_selection;
}
menubar_draw (menubar);
1998-02-27 07:54:42 +03:00
}
return MOU_NORMAL;
}
if (!menubar->is_dropped || (event->y < 2))
1998-02-27 07:54:42 +03:00
return MOU_NORMAL;
/* middle click -- everywhere */
if (((event->buttons & GPM_B_MIDDLE) != 0)
&& ((event->type & GPM_DOWN) != 0)) {
Menu *menu = (Menu *) g_ptr_array_index (menubar->menu, menubar->selected);
menubar_execute (menubar, menu->selected);
1998-02-27 07:54:42 +03:00
return MOU_NORMAL;
}
/* the mouse operation is on the menus or it is not */
menu = (Menu *) g_ptr_array_index (menubar->menu, menubar->selected);
left_x = menu->start_x;
right_x = left_x + menu->max_entry_len + 3;
if (right_x > menubar->widget.cols) {
left_x = menubar->widget.cols - menu->max_entry_len - 3;
right_x = menubar->widget.cols;
}
bottom_y = menu->entries->len + 3;
1998-02-27 07:54:42 +03:00
if ((event->x >= left_x) && (event->x <= right_x) && (event->y <= bottom_y)){
1998-02-27 07:54:42 +03:00
int pos = event->y - 3;
const menu_entry_t *entry = g_ptr_array_index (menu->entries, pos);
1998-02-27 07:54:42 +03:00
/* mouse wheel */
if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) {
menubar_up (menubar);
return MOU_NORMAL;
}
if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
menubar_down (menubar);
return MOU_NORMAL;
}
/* ignore events above and below dropped down menu */
if ((pos < 0) || (pos >= menu->entries->len))
return MOU_NORMAL;
if ((entry != NULL) && (entry->callback != NULL)) {
menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
menu->selected = pos;
menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
1998-02-27 07:54:42 +03:00
if ((event->type & GPM_UP) != 0)
menubar_execute (menubar, pos);
}
1998-02-27 07:54:42 +03:00
} else
/* use click not wheel to close menu */
if (((event->type & GPM_DOWN) != 0)
&& ((event->buttons & (GPM_B_UP | GPM_B_DOWN)) == 0))
1998-02-27 07:54:42 +03:00
menubar_finish (menubar);
1998-02-27 07:54:42 +03:00
return MOU_NORMAL;
}
WMenuBar *
menubar_new (int y, int x, int cols, GPtrArray *menu)
{
WMenuBar *menubar = g_new0 (WMenuBar, 1);
init_widget (&menubar->widget, y, x, 1, cols,
menubar_callback, menubar_event);
widget_want_cursor (menubar->widget, 0);
menubar_set_menu (menubar, menu);
return menubar;
}
void
menubar_set_menu (WMenuBar *menubar, GPtrArray *menu)
{
/* delete previous menu */
if (menubar->menu != NULL) {
g_ptr_array_foreach (menubar->menu, (GFunc) destroy_menu, NULL);
g_ptr_array_free (menubar->menu, TRUE);
}
/* add new menu */
menubar->is_active = FALSE;
menubar->is_dropped = FALSE;
menubar->menu = menu;
menubar->selected = 0;
menubar_arrange (menubar);
}
void
menubar_add_menu (WMenuBar *menubar, Menu *menu)
{
if (menubar->menu == NULL)
menubar->menu = g_ptr_array_new ();
if (menu != NULL)
g_ptr_array_add (menubar->menu, menu);
menubar_arrange (menubar);
}
/*
* Properly space menubar items. Should be called when menubar is created
* and also when widget width is changed (i.e. upon xterm resize).
*/
void
menubar_arrange (WMenuBar* menubar)
{
int start_x = 1;
unsigned int i;
int gap;
if ((menubar->menu == NULL) || (menubar->menu->len == 0))
return;
#ifndef RESIZABLE_MENUBAR
gap = 3;
for (i = 0; i < menubar->menu->len; i++) {
Menu *menu = g_ptr_array_index (menubar->menu, i);
int len = hotkey_width (menu->text);
menu->start_x = start_x;
start_x += len + gap;
}
#else /* RESIZABLE_MENUBAR */
gap = menubar->widget.cols - 2;
/* First, calculate gap between items... */
for (i = 0; i < menubar->menu->len; i++) {
Menu *menu = g_ptr_array_index (menubar->menu, i);
/* preserve length here, to be used below */
menu->start_x = hotkey_width (menu->text);
gap -= menu->start_x;
}
gap /= (menubar->menu->len - 1);
if (gap <= 0) {
/* We are out of luck - window is too narrow... */
gap = 1;
}
/* ...and now fix start positions of menubar items */
for (i = 0; i < menubar->menu->len; i++) {
Menu *menu = g_ptr_array_index (menubar->menu, i);
int len = menu->start_x;
menu->start_x = start_x;
start_x += len + gap;
}
#endif /* RESIZABLE_MENUBAR */
1998-02-27 07:54:42 +03:00
}