basemenu.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. // Copyright (C) 2003 Mooffie <mooffie@typo.co.il>
  2. //
  3. // This program is free software; you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation; either version 2 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  16. #include <config.h>
  17. #include "basemenu.h"
  18. #include "themes.h"
  19. ///////////////////////////// PopupMenu ////////////////////////////////////
  20. PopupMenu::PopupMenu(PopupMenu *aParent, PulldownMenu aMnu)
  21. {
  22. parent = aParent;
  23. init(aMnu);
  24. }
  25. void PopupMenu::init(PulldownMenu aMnu)
  26. {
  27. mnu = aMnu;
  28. count = 0;
  29. top = 0;
  30. current = 0;
  31. if (aMnu) {
  32. complete_menu(mnu);
  33. while (mnu[count].action)
  34. count++;
  35. }
  36. }
  37. void PopupMenu::complete_menu(PulldownMenu mnu)
  38. {
  39. for (int i = 0; mnu[i].action; i++) {
  40. if (get_primary_target()->get_action_event(mnu[i].action, mnu[i].evt)) {
  41. if (!mnu[i].desc)
  42. mnu[i].desc = get_primary_target()->get_action_description(mnu[i].action);
  43. } else {
  44. if (get_secondary_target()) {
  45. get_secondary_target()->get_action_event(mnu[i].action, mnu[i].evt);
  46. if (!mnu[i].desc)
  47. mnu[i].desc = get_secondary_target()->get_action_description(mnu[i].action);
  48. }
  49. }
  50. if (mnu[i].label) {
  51. unistring u = u8string(mnu[i].label);
  52. if (u.has_char('~')) {
  53. mnu[i].shortkey = u[u.index('~') + 1];
  54. if (mnu[i].shortkey >= 'A' && mnu[i].shortkey <= 'Z')
  55. mnu[i].shortkey -= 'A' - 'a';
  56. }
  57. }
  58. }
  59. }
  60. INTERACTIVE void PopupMenu::move_previous_item()
  61. {
  62. if (current > 0)
  63. while (mnu[--current].action[0] == '-')
  64. ;
  65. else
  66. move_last_item();
  67. if (current < top)
  68. top = current;
  69. }
  70. INTERACTIVE void PopupMenu::move_next_item()
  71. {
  72. if (current < count - 1)
  73. while (mnu[++current].action[0] == '-')
  74. ;
  75. else
  76. move_first_item();
  77. if (current >= top + window_height() - 2)
  78. top = current - window_height() + 3;
  79. }
  80. INTERACTIVE void PopupMenu::move_first_item()
  81. {
  82. current = top = 0;
  83. }
  84. INTERACTIVE void PopupMenu::move_last_item()
  85. {
  86. current = count > 1 ? count - 2 : 0;
  87. move_next_item(); // update top
  88. }
  89. void PopupMenu::end_modal(PostResult rslt)
  90. {
  91. post_result = rslt;
  92. Widget::end_modal();
  93. }
  94. INTERACTIVE void PopupMenu::prev_menu()
  95. {
  96. end_modal(mnuPrev);
  97. }
  98. INTERACTIVE void PopupMenu::next_menu()
  99. {
  100. if (mnu[current].submenu)
  101. select();
  102. else
  103. end_modal(mnuNext);
  104. }
  105. void PopupMenu::update_ancestors()
  106. {
  107. if (parent) {
  108. parent->update_ancestors();
  109. parent->update();
  110. }
  111. }
  112. INTERACTIVE void PopupMenu::select()
  113. {
  114. if (mnu[current].command_parameter1 || mnu[current].command_parameter2) {
  115. show_hint(""); // clear the dialogline _before_
  116. // executing the command (so we won't erase
  117. // what the command prints there).
  118. do_command(mnu[current].command_parameter1,
  119. mnu[current].command_parameter2,
  120. mnu[current].command_parameter3);
  121. }
  122. if (!mnu[current].submenu) {
  123. // If it's not a submenu then it's easy.
  124. if (mnu[current].evt.empty()) {
  125. // When the event is not set, it usualy means that
  126. // do_command() was executed and no more processing
  127. // is required.
  128. end_modal(mnuCancel);
  129. } else {
  130. set_next_event(mnu[current].evt);
  131. end_modal(mnuSelect);
  132. show_hint("");
  133. }
  134. return;
  135. }
  136. // No, we need to post a submenu.
  137. PopupMenu::PostResult post_result;
  138. Event evt;
  139. PopupMenu *p = create_popupmenu(this, mnu[current].submenu);
  140. post_result = p->post(window_begx() + window_width() - 2,
  141. window_begy() + current - top + 1, evt);
  142. delete p;
  143. switch (post_result) {
  144. case PopupMenu::mnuSelect:
  145. end_modal(mnuSelect);
  146. break;
  147. case PopupMenu::mnuCancel:
  148. end_modal(mnuCancel);
  149. break;
  150. case PopupMenu::mnuNext:
  151. end_modal(mnuNext);
  152. break;
  153. case PopupMenu::mnuPrev:
  154. clear_other_popups();
  155. update_ancestors();
  156. break;
  157. }
  158. }
  159. INTERACTIVE void PopupMenu::cancel_menu()
  160. {
  161. end_modal(mnuCancel);
  162. show_hint("");
  163. }
  164. bool PopupMenu::handle_event(const Event &evt)
  165. {
  166. if (evt.is_literal()) {
  167. for (int i = 0; i < count; i++) {
  168. if (mnu[i].shortkey == evt.ch) {
  169. current = i;
  170. update();
  171. select();
  172. return true;
  173. }
  174. }
  175. }
  176. return Dispatcher::handle_event(evt);
  177. }
  178. void PopupMenu::draw_frame()
  179. {
  180. int attr = get_attr(MENU_FRAME_ATTR);
  181. if (terminal::graphical_boxes) {
  182. wborder(wnd, ACS_VLINE|attr, ACS_VLINE|attr, ACS_HLINE|attr, ACS_HLINE|attr,
  183. ACS_ULCORNER|attr, ACS_URCORNER|attr, ACS_LLCORNER|attr, ACS_LRCORNER|attr);
  184. } else {
  185. wborder(wnd, '|'|attr, '|'|attr, '-'|attr, '-'|attr,
  186. '+'|attr, '+'|attr, '+'|attr, '+'|attr);
  187. }
  188. if (top != 0)
  189. mvwhline(wnd, 0, 2, terminal::graphical_boxes ? ACS_UARROW : '^', 1);
  190. if (top + window_height() - 2 < count)
  191. mvwhline(wnd, window_height() - 1, 2, terminal::graphical_boxes ? ACS_DARROW : 'v', 1);
  192. }
  193. int PopupMenu::get_item_optimal_width(int item)
  194. {
  195. if (mnu[item].label)
  196. return strlen(mnu[item].label) + 1 + mnu[item].evt.to_string().length() + 2;
  197. else
  198. return 2;
  199. }
  200. int PopupMenu::get_optimal_width()
  201. {
  202. int max = 0;
  203. for (int i = 0; i < count; i++) {
  204. int w = get_item_optimal_width(i);
  205. if (w > max)
  206. max = w;
  207. }
  208. return max;
  209. }
  210. void PopupMenu::update()
  211. {
  212. // Draw the menu.
  213. int last_visible_item = top + window_height() - 3;
  214. if (last_visible_item >= count)
  215. last_visible_item = count - 1;
  216. int item;
  217. // Draw the labels
  218. for (item = top; item <= last_visible_item; item++) {
  219. int y = item - top + 1;
  220. wattrset(wnd, (item == current) ? get_attr(MENU_SELECTED_ATTR) : get_attr(MENU_ATTR));
  221. wmove(wnd, y, 1);
  222. for (int i = 0; i < window_width() - 2; i++)
  223. waddch(wnd, ' ');
  224. wmove(wnd, y, 2);
  225. if (mnu[item].action[0] != '-') {
  226. u8string u8key;
  227. unistring ulabel = u8string(mnu[item].label);
  228. int key_ofs = 0;
  229. if (mnu[item].shortkey) {
  230. key_ofs = ulabel.index('~')+1;
  231. unichar key = ulabel[key_ofs];
  232. u8key = u8string(unistring(&key, &key + 1));
  233. ulabel.erase(ulabel.begin()+key_ofs-1, ulabel.begin()+key_ofs);
  234. }
  235. u8string label_without_tilde(ulabel);
  236. draw_string(label_without_tilde.c_str());
  237. if (mnu[item].shortkey) {
  238. wmove(wnd, y, key_ofs+1);
  239. wattrset(wnd, (item == current) ? get_attr(MENU_LETTER_SELECTED_ATTR) : get_attr(MENU_LETTER_ATTR));
  240. draw_string(u8key.c_str());
  241. }
  242. wattrset(wnd, (item == current) ? get_attr(MENU_INDICATOR_SELECTED_ATTR) : get_attr(MENU_INDICATOR_ATTR));
  243. if (mnu[item].state_id) {
  244. wmove(wnd, y, 1);
  245. int id = mnu[item].state_id;
  246. bool checked = get_item_state(id);
  247. if (checked || id < 5000) { // Avoid painting a blank (" ")
  248. // in order not to affect
  249. // the cursor color.
  250. draw_string(checked
  251. ? (id >= 5000 ? "*" : "+")
  252. : (id >= 5000 ? " " : "-"));
  253. }
  254. }
  255. wattrset(wnd, (item == current) ? get_attr(MENU_SELECTED_ATTR) : get_attr(MENU_ATTR));
  256. if (mnu[item].submenu) {
  257. wmove(wnd, y, window_width()-3);
  258. draw_string(">");
  259. } else {
  260. // Print the hot-key
  261. u8string keyname = mnu[item].evt.to_string();
  262. wmove(wnd, y, window_width() - keyname.length() - 3);
  263. waddch(wnd, ' ');
  264. draw_string(keyname.c_str());
  265. waddch(wnd, ' ');
  266. }
  267. }
  268. }
  269. // Draw the frame
  270. wattrset(wnd, get_attr(MENU_FRAME_ATTR));
  271. draw_frame();
  272. for (item = top; item <= last_visible_item; item++) {
  273. int y = item - top + 1;
  274. if (mnu[item].action[0] == '-') {
  275. if (terminal::graphical_boxes) {
  276. mvwhline(wnd, y, 0, ACS_LTEE, 1);
  277. mvwhline(wnd, y, 1, ACS_HLINE, window_width()-2);
  278. mvwhline(wnd, y, window_width()-1, ACS_RTEE, 1);
  279. } else {
  280. mvwhline(wnd, y, 2, '-', window_width()-4);
  281. }
  282. }
  283. }
  284. // Place the cursor before the item (useful for monochrome terminals).
  285. wmove(wnd, current - top + 1, 1);
  286. show_hint(mnu[current].desc);
  287. wnoutrefresh(wnd);
  288. }
  289. // reposition() - adjusts the window size to fit the screen
  290. // and the menu items.
  291. void PopupMenu::reposition(int x, int y)
  292. {
  293. int width = get_optimal_width() + 2;
  294. int height = count + 2;
  295. int scr_width, scr_height;
  296. getmaxyx(stdscr, scr_height, scr_width);
  297. if (x + width > scr_width) {
  298. x = scr_width - width;
  299. if (x < 0)
  300. x = 0;
  301. if (width > scr_width)
  302. width = scr_width;
  303. }
  304. if (y + height > scr_height) {
  305. y = scr_height - height;
  306. if (y < 0) {
  307. y = 0;
  308. height = scr_height;
  309. }
  310. // don't hide the menubar line
  311. if (y == 0 && scr_height > 3) {
  312. y++;
  313. height--;
  314. }
  315. }
  316. resize(height, width, y, x);
  317. }
  318. PopupMenu::PostResult PopupMenu::post(int x, int y, Event &evt)
  319. {
  320. if (!is_valid_window()) // if we haven't yet created it
  321. create_window(1, 1);
  322. scrollok(wnd, 0); // so we can draw '+' at the bottom right corner.
  323. reposition(x, y);
  324. set_modal();
  325. while (is_modal()) {
  326. Event evt;
  327. update();
  328. doupdate();
  329. get_next_event(evt, wnd);
  330. handle_event(evt);
  331. }
  332. //show_hint(""); // I moved it to a better place because I don't
  333. // want to erase what do_command might print there.
  334. destroy_window();
  335. return post_result;
  336. }
  337. INTERACTIVE void PopupMenu::screen_resize()
  338. {
  339. #ifdef KEY_RESIZE
  340. set_next_event(Event(KEY_RESIZE));
  341. cancel_menu();
  342. #endif
  343. }
  344. /////////////////////////////// Menubar ////////////////////////////////////
  345. Menubar::Menubar()
  346. {
  347. create_window(1, 1);
  348. }
  349. void Menubar::init(MenubarMenu aMnu)
  350. {
  351. mnu = aMnu;
  352. count = 0;
  353. while (mnu[count].label)
  354. count++;
  355. current = -1;
  356. dirty = true;
  357. }
  358. void Menubar::set_current(int i)
  359. {
  360. current = i;
  361. invalidate_view();
  362. }
  363. INTERACTIVE void Menubar::select()
  364. {
  365. PopupMenu::PostResult post_result;
  366. Event evt;
  367. PopupMenu *p = create_popupmenu(mnu[current].submenu);
  368. post_result = p->post(get_ofs(current), 1, evt);
  369. delete p;
  370. if (post_result == PopupMenu::mnuCancel || post_result == PopupMenu::mnuSelect) {
  371. set_current(-1);
  372. }
  373. refresh_screen();
  374. switch (post_result) {
  375. case PopupMenu::mnuCancel:
  376. doupdate();
  377. end_modal();
  378. break;
  379. case PopupMenu::mnuSelect:
  380. end_modal();
  381. break;
  382. case PopupMenu::mnuPrev:
  383. prev_menu();
  384. update();
  385. select();
  386. break;
  387. case PopupMenu::mnuNext:
  388. next_menu();
  389. update();
  390. select();
  391. break;
  392. }
  393. }
  394. INTERACTIVE void Menubar::next_menu()
  395. {
  396. if (current >= count - 1)
  397. set_current(0);
  398. else
  399. set_current(current + 1);
  400. }
  401. INTERACTIVE void Menubar::prev_menu()
  402. {
  403. if (current <= 0)
  404. set_current(count - 1);
  405. else
  406. set_current(current - 1);
  407. }
  408. int Menubar::get_ofs(int item)
  409. {
  410. int ofs = 1;
  411. for (int i = 0; i < item; i++)
  412. ofs += strlen(mnu[i].label) + 2;
  413. return ofs;
  414. }
  415. void Menubar::resize(int lines, int columns, int y, int x)
  416. {
  417. Widget::resize(lines, columns, y, x);
  418. invalidate_view();
  419. }
  420. INTERACTIVE void Menubar::screen_resize()
  421. {
  422. #ifdef KEY_RESIZE
  423. set_next_event(Event(KEY_RESIZE));
  424. end_modal();
  425. #endif
  426. }
  427. void Menubar::update()
  428. {
  429. if (!dirty)
  430. return;
  431. werase(wnd);
  432. wattrset(wnd, get_attr(MENUBAR_ATTR));
  433. wmove(wnd, 0, 0);
  434. for (int i = 0; i < window_width(); i++)
  435. waddch(wnd, ' ');
  436. wmove(wnd, 0, 0);
  437. for (int i = 0; i < count; i++) {
  438. wmove(wnd, 0, get_ofs(i));
  439. wattrset(wnd, (current == i) ? get_attr(MENU_SELECTED_ATTR) : get_attr(MENUBAR_ATTR));
  440. draw_string(current == i ? " " : " ", false);
  441. draw_string(mnu[i].label, false);
  442. draw_string(current == i ? " " : " ", false);
  443. }
  444. // Place the cursor before the item (useful for monochrome terminals).
  445. if (current >= 0)
  446. wmove(wnd, 0, get_ofs(current));
  447. wnoutrefresh(wnd);
  448. dirty = false;
  449. }
  450. bool Menubar::handle_event(const Event &evt)
  451. {
  452. if (evt.is_literal()) {
  453. for (int i = 0; i < count; i++) {
  454. if (mnu[i].label) {
  455. unistring u = u8string(mnu[i].label);
  456. unichar shortkey = u[0];
  457. if (shortkey >= 'A' && shortkey <= 'Z')
  458. shortkey -= 'A' - 'a';
  459. if (shortkey == evt.ch) {
  460. set_current(i);
  461. update();
  462. select();
  463. return true;
  464. }
  465. }
  466. }
  467. }
  468. return Dispatcher::handle_event(evt);
  469. }
  470. void Menubar::exec()
  471. {
  472. set_current(0);
  473. set_modal();
  474. while (is_modal()) {
  475. Event evt;
  476. update();
  477. doupdate();
  478. get_next_event(evt, wnd);
  479. handle_event(evt);
  480. }
  481. set_current(-1);
  482. update();
  483. }