diff options
Diffstat (limited to 'basemenu.cc')
-rw-r--r-- | basemenu.cc | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/basemenu.cc b/basemenu.cc new file mode 100644 index 0000000..b758803 --- /dev/null +++ b/basemenu.cc @@ -0,0 +1,552 @@ +// Copyright (C) 2003 Mooffie <mooffie@typo.co.il> +// +// 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. +// +// 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + +#include <config.h> + +#include "basemenu.h" +#include "themes.h" + +///////////////////////////// PopupMenu //////////////////////////////////// + +PopupMenu::PopupMenu(PopupMenu *aParent, PulldownMenu aMnu) +{ + parent = aParent; + init(aMnu); +} + +void PopupMenu::init(PulldownMenu aMnu) +{ + mnu = aMnu; + count = 0; + top = 0; + current = 0; + if (aMnu) { + complete_menu(mnu); + while (mnu[count].action) + count++; + } +} + +void PopupMenu::complete_menu(PulldownMenu mnu) +{ + for (int i = 0; mnu[i].action; i++) { + if (get_primary_target()->get_action_event(mnu[i].action, mnu[i].evt)) { + if (!mnu[i].desc) + mnu[i].desc = get_primary_target()->get_action_description(mnu[i].action); + } else { + if (get_secondary_target()) { + get_secondary_target()->get_action_event(mnu[i].action, mnu[i].evt); + if (!mnu[i].desc) + mnu[i].desc = get_secondary_target()->get_action_description(mnu[i].action); + } + } + if (mnu[i].label) { + unistring u = u8string(mnu[i].label); + if (u.has_char('~')) { + mnu[i].shortkey = u[u.index('~') + 1]; + if (mnu[i].shortkey >= 'A' && mnu[i].shortkey <= 'Z') + mnu[i].shortkey -= 'A' - 'a'; + } + } + } +} + +INTERACTIVE void PopupMenu::move_previous_item() +{ + if (current > 0) + while (mnu[--current].action[0] == '-') + ; + else + move_last_item(); + + if (current < top) + top = current; +} + +INTERACTIVE void PopupMenu::move_next_item() +{ + if (current < count - 1) + while (mnu[++current].action[0] == '-') + ; + else + move_first_item(); + + if (current >= top + window_height() - 2) + top = current - window_height() + 3; +} + +INTERACTIVE void PopupMenu::move_first_item() +{ + current = top = 0; +} + +INTERACTIVE void PopupMenu::move_last_item() +{ + current = count > 1 ? count - 2 : 0; + move_next_item(); // update top +} + +void PopupMenu::end_modal(PostResult rslt) +{ + post_result = rslt; + Widget::end_modal(); +} + +INTERACTIVE void PopupMenu::prev_menu() +{ + end_modal(mnuPrev); +} + +INTERACTIVE void PopupMenu::next_menu() +{ + if (mnu[current].submenu) + select(); + else + end_modal(mnuNext); +} + +void PopupMenu::update_ancestors() +{ + if (parent) { + parent->update_ancestors(); + parent->update(); + } +} + +INTERACTIVE void PopupMenu::select() +{ + if (mnu[current].command_parameter1 || mnu[current].command_parameter2) { + show_hint(""); // clear the dialogline _before_ + // executing the command (so we won't erase + // what the command prints there). + do_command(mnu[current].command_parameter1, + mnu[current].command_parameter2, + mnu[current].command_parameter3); + } + + if (!mnu[current].submenu) { + // If it's not a submenu then it's easy. + if (mnu[current].evt.empty()) { + // When the event is not set, it usualy means that + // do_command() was executed and no more processing + // is required. + end_modal(mnuCancel); + } else { + set_next_event(mnu[current].evt); + end_modal(mnuSelect); + show_hint(""); + } + return; + } + + // No, we need to post a submenu. + PopupMenu::PostResult post_result; + Event evt; + PopupMenu *p = create_popupmenu(this, mnu[current].submenu); + post_result = p->post(window_begx() + window_width() - 2, + window_begy() + current - top + 1, evt); + delete p; + + switch (post_result) { + case PopupMenu::mnuSelect: + end_modal(mnuSelect); + break; + case PopupMenu::mnuCancel: + end_modal(mnuCancel); + break; + case PopupMenu::mnuNext: + end_modal(mnuNext); + break; + case PopupMenu::mnuPrev: + clear_other_popups(); + update_ancestors(); + break; + } +} + +INTERACTIVE void PopupMenu::cancel_menu() +{ + end_modal(mnuCancel); + show_hint(""); +} + +bool PopupMenu::handle_event(const Event &evt) +{ + if (evt.is_literal()) { + for (int i = 0; i < count; i++) { + if (mnu[i].shortkey == evt.ch) { + current = i; + update(); + select(); + return true; + } + } + } + return Dispatcher::handle_event(evt); +} + +void PopupMenu::draw_frame() +{ + int attr = get_attr(MENU_FRAME_ATTR); + if (terminal::graphical_boxes) { + wborder(wnd, ACS_VLINE|attr, ACS_VLINE|attr, ACS_HLINE|attr, ACS_HLINE|attr, + ACS_ULCORNER|attr, ACS_URCORNER|attr, ACS_LLCORNER|attr, ACS_LRCORNER|attr); + } else { + wborder(wnd, '|'|attr, '|'|attr, '-'|attr, '-'|attr, + '+'|attr, '+'|attr, '+'|attr, '+'|attr); + } + + if (top != 0) + mvwhline(wnd, 0, 2, terminal::graphical_boxes ? ACS_UARROW : '^', 1); + if (top + window_height() - 2 < count) + mvwhline(wnd, window_height() - 1, 2, terminal::graphical_boxes ? ACS_DARROW : 'v', 1); +} + +int PopupMenu::get_item_optimal_width(int item) +{ + if (mnu[item].label) + return strlen(mnu[item].label) + 1 + mnu[item].evt.to_string().length() + 2; + else + return 2; +} + +int PopupMenu::get_optimal_width() +{ + int max = 0; + for (int i = 0; i < count; i++) { + int w = get_item_optimal_width(i); + if (w > max) + max = w; + } + return max; +} + +void PopupMenu::update() +{ + // Draw the menu. + + int last_visible_item = top + window_height() - 3; + if (last_visible_item >= count) + last_visible_item = count - 1; + int item; + + // Draw the labels + for (item = top; item <= last_visible_item; item++) { + int y = item - top + 1; + wattrset(wnd, (item == current) ? get_attr(MENU_SELECTED_ATTR) : get_attr(MENU_ATTR)); + wmove(wnd, y, 1); + for (int i = 0; i < window_width() - 2; i++) + waddch(wnd, ' '); + wmove(wnd, y, 2); + + if (mnu[item].action[0] != '-') { + u8string u8key; + unistring ulabel = u8string(mnu[item].label); + + int key_ofs = 0; + if (mnu[item].shortkey) { + key_ofs = ulabel.index('~')+1; + unichar key = ulabel[key_ofs]; + u8key = u8string(unistring(&key, &key + 1)); + ulabel.erase(ulabel.begin()+key_ofs-1, ulabel.begin()+key_ofs); + } + + u8string label_without_tilde(ulabel); + draw_string(label_without_tilde.c_str()); + + if (mnu[item].shortkey) { + wmove(wnd, y, key_ofs+1); + wattrset(wnd, (item == current) ? get_attr(MENU_LETTER_SELECTED_ATTR) : get_attr(MENU_LETTER_ATTR)); + draw_string(u8key.c_str()); + } + + wattrset(wnd, (item == current) ? get_attr(MENU_INDICATOR_SELECTED_ATTR) : get_attr(MENU_INDICATOR_ATTR)); + if (mnu[item].state_id) { + wmove(wnd, y, 1); + int id = mnu[item].state_id; + bool checked = get_item_state(id); + if (checked || id < 5000) { // Avoid painting a blank (" ") + // in order not to affect + // the cursor color. + draw_string(checked + ? (id >= 5000 ? "*" : "+") + : (id >= 5000 ? " " : "-")); + } + } + wattrset(wnd, (item == current) ? get_attr(MENU_SELECTED_ATTR) : get_attr(MENU_ATTR)); + + if (mnu[item].submenu) { + wmove(wnd, y, window_width()-3); + draw_string(">"); + } else { + // Print the hot-key + u8string keyname = mnu[item].evt.to_string(); + wmove(wnd, y, window_width() - keyname.length() - 3); + waddch(wnd, ' '); + draw_string(keyname.c_str()); + waddch(wnd, ' '); + } + } + } + + // Draw the frame + wattrset(wnd, get_attr(MENU_FRAME_ATTR)); + draw_frame(); + for (item = top; item <= last_visible_item; item++) { + int y = item - top + 1; + if (mnu[item].action[0] == '-') { + if (terminal::graphical_boxes) { + mvwhline(wnd, y, 0, ACS_LTEE, 1); + mvwhline(wnd, y, 1, ACS_HLINE, window_width()-2); + mvwhline(wnd, y, window_width()-1, ACS_RTEE, 1); + } else { + mvwhline(wnd, y, 2, '-', window_width()-4); + } + } + } + + // Place the cursor before the item (useful for monochrome terminals). + wmove(wnd, current - top + 1, 1); + + show_hint(mnu[current].desc); + + wnoutrefresh(wnd); +} + +// reposition() - adjusts the window size to fit the screen +// and the menu items. + +void PopupMenu::reposition(int x, int y) +{ + int width = get_optimal_width() + 2; + int height = count + 2; + + int scr_width, scr_height; + getmaxyx(stdscr, scr_height, scr_width); + + if (x + width > scr_width) { + x = scr_width - width; + if (x < 0) + x = 0; + if (width > scr_width) + width = scr_width; + } + + if (y + height > scr_height) { + y = scr_height - height; + if (y < 0) { + y = 0; + height = scr_height; + } + // don't hide the menubar line + if (y == 0 && scr_height > 3) { + y++; + height--; + } + } + + resize(height, width, y, x); +} + +PopupMenu::PostResult PopupMenu::post(int x, int y, Event &evt) +{ + if (!is_valid_window()) // if we haven't yet created it + create_window(1, 1); + scrollok(wnd, 0); // so we can draw '+' at the bottom right corner. + reposition(x, y); + set_modal(); + while (is_modal()) { + Event evt; + update(); + doupdate(); + get_next_event(evt, wnd); + handle_event(evt); + } + //show_hint(""); // I moved it to a better place because I don't + // want to erase what do_command might print there. + destroy_window(); + return post_result; +} + +INTERACTIVE void PopupMenu::screen_resize() +{ +#ifdef KEY_RESIZE + set_next_event(Event(KEY_RESIZE)); + cancel_menu(); +#endif +} + +/////////////////////////////// Menubar //////////////////////////////////// + +Menubar::Menubar() +{ + create_window(1, 1); +} + +void Menubar::init(MenubarMenu aMnu) +{ + mnu = aMnu; + count = 0; + while (mnu[count].label) + count++; + current = -1; + dirty = true; +} + +void Menubar::set_current(int i) +{ + current = i; + invalidate_view(); +} + +INTERACTIVE void Menubar::select() +{ + PopupMenu::PostResult post_result; + Event evt; + + PopupMenu *p = create_popupmenu(mnu[current].submenu); + post_result = p->post(get_ofs(current), 1, evt); + delete p; + + if (post_result == PopupMenu::mnuCancel || post_result == PopupMenu::mnuSelect) { + set_current(-1); + } + refresh_screen(); + + switch (post_result) { + case PopupMenu::mnuCancel: + doupdate(); + end_modal(); + break; + case PopupMenu::mnuSelect: + end_modal(); + break; + case PopupMenu::mnuPrev: + prev_menu(); + update(); + select(); + break; + case PopupMenu::mnuNext: + next_menu(); + update(); + select(); + break; + } + +} + +INTERACTIVE void Menubar::next_menu() +{ + if (current >= count - 1) + set_current(0); + else + set_current(current + 1); +} + +INTERACTIVE void Menubar::prev_menu() +{ + if (current <= 0) + set_current(count - 1); + else + set_current(current - 1); +} + +int Menubar::get_ofs(int item) +{ + int ofs = 1; + for (int i = 0; i < item; i++) + ofs += strlen(mnu[i].label) + 2; + return ofs; +} + +void Menubar::resize(int lines, int columns, int y, int x) +{ + Widget::resize(lines, columns, y, x); + invalidate_view(); +} + +INTERACTIVE void Menubar::screen_resize() +{ +#ifdef KEY_RESIZE + set_next_event(Event(KEY_RESIZE)); + end_modal(); +#endif +} + +void Menubar::update() +{ + if (!dirty) + return; + + werase(wnd); + wattrset(wnd, get_attr(MENUBAR_ATTR)); + wmove(wnd, 0, 0); + for (int i = 0; i < window_width(); i++) + waddch(wnd, ' '); + wmove(wnd, 0, 0); + + for (int i = 0; i < count; i++) { + wmove(wnd, 0, get_ofs(i)); + wattrset(wnd, (current == i) ? get_attr(MENU_SELECTED_ATTR) : get_attr(MENUBAR_ATTR)); + draw_string(current == i ? " " : " ", false); + draw_string(mnu[i].label, false); + draw_string(current == i ? " " : " ", false); + } + + // Place the cursor before the item (useful for monochrome terminals). + if (current >= 0) + wmove(wnd, 0, get_ofs(current)); + + wnoutrefresh(wnd); + dirty = false; +} + +bool Menubar::handle_event(const Event &evt) +{ + if (evt.is_literal()) { + for (int i = 0; i < count; i++) { + if (mnu[i].label) { + unistring u = u8string(mnu[i].label); + unichar shortkey = u[0]; + if (shortkey >= 'A' && shortkey <= 'Z') + shortkey -= 'A' - 'a'; + if (shortkey == evt.ch) { + set_current(i); + update(); + select(); + return true; + } + } + } + } + return Dispatcher::handle_event(evt); +} + +void Menubar::exec() +{ + set_current(0); + set_modal(); + while (is_modal()) { + Event evt; + update(); + doupdate(); + get_next_event(evt, wnd); + handle_event(evt); + } + set_current(-1); + update(); +} + |