dialog.c 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324
  1. /*
  2. Dialog box features module for the Midnight Commander
  3. Copyright (C) 1994-2019
  4. Free Software Foundation, Inc.
  5. This file is part of the Midnight Commander.
  6. The Midnight Commander is free software: you can redistribute it
  7. and/or modify it under the terms of the GNU General Public License as
  8. published by the Free Software Foundation, either version 3 of the License,
  9. or (at your option) any later version.
  10. The Midnight Commander is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. /** \file dialog.c
  18. * \brief Source: dialog box features module
  19. */
  20. #include <config.h>
  21. #include <ctype.h>
  22. #include <errno.h>
  23. #include <stdlib.h>
  24. #include <stdio.h>
  25. #include <string.h>
  26. #include <sys/types.h>
  27. #include <sys/stat.h>
  28. #include "lib/global.h"
  29. #include "lib/tty/tty.h"
  30. #include "lib/skin.h"
  31. #include "lib/tty/key.h"
  32. #include "lib/strutil.h"
  33. #include "lib/fileloc.h" /* MC_HISTORY_FILE */
  34. #include "lib/event.h" /* mc_event_raise() */
  35. #include "lib/util.h" /* MC_PTR_FREE */
  36. #include "lib/mcconfig.h" /* num_history_items_recorded */
  37. #include "lib/widget.h"
  38. #include "lib/widget/mouse.h"
  39. /*** global variables ****************************************************************************/
  40. /* Color styles for normal and error dialogs */
  41. dlg_colors_t dialog_colors;
  42. dlg_colors_t alarm_colors;
  43. dlg_colors_t listbox_colors;
  44. /* Primitive way to check if the the current dialog is our dialog */
  45. /* This is needed by async routines like load_prompt */
  46. GList *top_dlg = NULL;
  47. /* A hook list for idle events */
  48. hook_t *idle_hook = NULL;
  49. /* If set then dialogs just clean the screen when refreshing, else */
  50. /* they do a complete refresh, refreshing all the parts of the program */
  51. gboolean fast_refresh = FALSE;
  52. /* left click outside of dialog closes it */
  53. gboolean mouse_close_dialog = FALSE;
  54. const global_keymap_t *dialog_map = NULL;
  55. /*** file scope macro definitions ****************************************************************/
  56. /*** file scope type declarations ****************************************************************/
  57. /* Control widget positions in dialog */
  58. typedef struct
  59. {
  60. int shift_x;
  61. int scale_x;
  62. int shift_y;
  63. int scale_y;
  64. } widget_shift_scale_t;
  65. /*** file scope variables ************************************************************************/
  66. /* --------------------------------------------------------------------------------------------- */
  67. /*** file scope functions ************************************************************************/
  68. /* --------------------------------------------------------------------------------------------- */
  69. static GList *
  70. dlg_get_next_or_prev_of (const GList * list, gboolean next)
  71. {
  72. GList *l = NULL;
  73. if (list != NULL)
  74. {
  75. const WDialog *owner = CONST_WIDGET (list->data)->owner;
  76. if (owner != NULL)
  77. {
  78. if (next)
  79. {
  80. l = g_list_next (list);
  81. if (l == NULL)
  82. l = owner->widgets;
  83. }
  84. else
  85. {
  86. l = g_list_previous (list);
  87. if (l == NULL)
  88. l = g_list_last (owner->widgets);
  89. }
  90. }
  91. }
  92. return l;
  93. }
  94. /* --------------------------------------------------------------------------------------------- */
  95. static void
  96. dlg_select_next_or_prev (WDialog * h, gboolean next)
  97. {
  98. if (h->widgets != NULL && h->current != NULL)
  99. {
  100. GList *l = h->current;
  101. Widget *w;
  102. do
  103. {
  104. l = dlg_get_next_or_prev_of (l, next);
  105. w = WIDGET (l->data);
  106. }
  107. while ((widget_get_state (w, WST_DISABLED) || !widget_get_options (w, WOP_SELECTABLE))
  108. && l != h->current);
  109. widget_select (l->data);
  110. }
  111. }
  112. /* --------------------------------------------------------------------------------------------- */
  113. /**
  114. * broadcast a message to all the widgets in a dialog that have
  115. * the options set to flags. If flags is zero, the message is sent
  116. * to all widgets.
  117. */
  118. static void
  119. dlg_broadcast_msg_to (WDialog * h, widget_msg_t msg, gboolean reverse, widget_options_t flags)
  120. {
  121. GList *p, *first;
  122. if (h->widgets == NULL)
  123. return;
  124. if (h->current == NULL)
  125. h->current = h->widgets;
  126. p = dlg_get_next_or_prev_of (h->current, !reverse);
  127. first = p;
  128. do
  129. {
  130. Widget *w = WIDGET (p->data);
  131. p = dlg_get_next_or_prev_of (p, !reverse);
  132. if ((flags == 0) || ((flags & w->options) != 0))
  133. send_message (w, NULL, msg, 0, NULL);
  134. }
  135. while (first != p);
  136. }
  137. /* --------------------------------------------------------------------------------------------- */
  138. /**
  139. * Read histories from the ${XDG_CACHE_HOME}/mc/history file
  140. */
  141. static void
  142. dlg_read_history (WDialog * h)
  143. {
  144. char *profile;
  145. ev_history_load_save_t event_data;
  146. if (num_history_items_recorded == 0) /* this is how to disable */
  147. return;
  148. profile = mc_config_get_full_path (MC_HISTORY_FILE);
  149. event_data.cfg = mc_config_init (profile, TRUE);
  150. event_data.receiver = NULL;
  151. /* create all histories in dialog */
  152. mc_event_raise (h->event_group, MCEVENT_HISTORY_LOAD, &event_data);
  153. mc_config_deinit (event_data.cfg);
  154. g_free (profile);
  155. }
  156. /* --------------------------------------------------------------------------------------------- */
  157. static int
  158. dlg_find_widget_callback (const void *a, const void *b)
  159. {
  160. const Widget *w = CONST_WIDGET (a);
  161. const widget_cb_fn f = b;
  162. return (w->callback == f) ? 0 : 1;
  163. }
  164. /* --------------------------------------------------------------------------------------------- */
  165. static void
  166. refresh_cmd (void)
  167. {
  168. #ifdef HAVE_SLANG
  169. tty_touch_screen ();
  170. mc_refresh ();
  171. #else
  172. /* Use this if the refreshes fail */
  173. clr_scr ();
  174. repaint_screen ();
  175. #endif /* HAVE_SLANG */
  176. }
  177. /* --------------------------------------------------------------------------------------------- */
  178. static cb_ret_t
  179. dlg_execute_cmd (WDialog * h, long command)
  180. {
  181. cb_ret_t ret = MSG_HANDLED;
  182. if (send_message (h, NULL, MSG_ACTION, command, NULL) == MSG_HANDLED)
  183. return MSG_HANDLED;
  184. switch (command)
  185. {
  186. case CK_Ok:
  187. h->ret_value = B_ENTER;
  188. dlg_stop (h);
  189. break;
  190. case CK_Cancel:
  191. h->ret_value = B_CANCEL;
  192. dlg_stop (h);
  193. break;
  194. case CK_Up:
  195. case CK_Left:
  196. dlg_select_prev_widget (h);
  197. break;
  198. case CK_Down:
  199. case CK_Right:
  200. dlg_select_next_widget (h);
  201. break;
  202. case CK_Help:
  203. {
  204. ev_help_t event_data = { NULL, h->help_ctx };
  205. mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
  206. }
  207. break;
  208. case CK_Suspend:
  209. mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL);
  210. refresh_cmd ();
  211. break;
  212. case CK_Refresh:
  213. refresh_cmd ();
  214. break;
  215. case CK_ScreenList:
  216. if (!widget_get_state (WIDGET (h), WST_MODAL))
  217. dialog_switch_list ();
  218. else
  219. ret = MSG_NOT_HANDLED;
  220. break;
  221. case CK_ScreenNext:
  222. if (!widget_get_state (WIDGET (h), WST_MODAL))
  223. dialog_switch_next ();
  224. else
  225. ret = MSG_NOT_HANDLED;
  226. break;
  227. case CK_ScreenPrev:
  228. if (!widget_get_state (WIDGET (h), WST_MODAL))
  229. dialog_switch_prev ();
  230. else
  231. ret = MSG_NOT_HANDLED;
  232. break;
  233. default:
  234. ret = MSG_NOT_HANDLED;
  235. }
  236. return ret;
  237. }
  238. /* --------------------------------------------------------------------------------------------- */
  239. static cb_ret_t
  240. dlg_handle_key (WDialog * h, int d_key)
  241. {
  242. long command;
  243. command = keybind_lookup_keymap_command (dialog_map, d_key);
  244. if (command != CK_IgnoreKey)
  245. return dlg_execute_cmd (h, command);
  246. return MSG_NOT_HANDLED;
  247. }
  248. /* --------------------------------------------------------------------------------------------- */
  249. /**
  250. * This is the low-level mouse handler.
  251. * It receives a Gpm_Event event and translates it into a higher level protocol.
  252. */
  253. static int
  254. dlg_mouse_translator (Gpm_Event * event, Widget * w)
  255. {
  256. mouse_event_t me;
  257. me = mouse_translate_event (w, event);
  258. return mouse_process_event (w, &me);
  259. }
  260. /* --------------------------------------------------------------------------------------------- */
  261. static int
  262. dlg_mouse_event (WDialog * h, Gpm_Event * event)
  263. {
  264. Widget *wh = WIDGET (h);
  265. GList *p;
  266. /* close the dialog by mouse left click out of dialog area */
  267. if (mouse_close_dialog && (wh->pos_flags & WPOS_FULLSCREEN) == 0
  268. && ((event->buttons & GPM_B_LEFT) != 0) && ((event->type & GPM_DOWN) != 0)
  269. && !mouse_global_in_widget (event, wh))
  270. {
  271. h->ret_value = B_CANCEL;
  272. dlg_stop (h);
  273. return MOU_NORMAL;
  274. }
  275. if (wh->mouse_callback != NULL)
  276. {
  277. int mou;
  278. mou = dlg_mouse_translator (event, wh);
  279. if (mou != MOU_UNHANDLED)
  280. return mou;
  281. }
  282. if (h->widgets == NULL)
  283. return MOU_UNHANDLED;
  284. /* send the event to widgets in reverse Z-order */
  285. p = g_list_last (h->widgets);
  286. do
  287. {
  288. Widget *w = WIDGET (p->data);
  289. if (!widget_get_state (w, WST_DISABLED) && w->mouse_callback != NULL)
  290. {
  291. /* put global cursor position to the widget */
  292. int ret;
  293. ret = dlg_mouse_translator (event, w);
  294. if (ret != MOU_UNHANDLED)
  295. return ret;
  296. }
  297. p = g_list_previous (p);
  298. }
  299. while (p != NULL);
  300. return MOU_UNHANDLED;
  301. }
  302. /* --------------------------------------------------------------------------------------------- */
  303. static cb_ret_t
  304. dlg_try_hotkey (WDialog * h, int d_key)
  305. {
  306. GList *hot_cur;
  307. Widget *current;
  308. cb_ret_t handled;
  309. int c;
  310. if (h->widgets == NULL)
  311. return MSG_NOT_HANDLED;
  312. if (h->current == NULL)
  313. h->current = h->widgets;
  314. /*
  315. * Explanation: we don't send letter hotkeys to other widgets if
  316. * the currently selected widget is an input line
  317. */
  318. current = WIDGET (h->current->data);
  319. if (widget_get_state (current, WST_DISABLED))
  320. return MSG_NOT_HANDLED;
  321. if (widget_get_options (current, WOP_IS_INPUT))
  322. {
  323. /* skip ascii control characters, anything else can valid character in
  324. * some encoding */
  325. if (d_key >= 32 && d_key < 256)
  326. return MSG_NOT_HANDLED;
  327. }
  328. /* If it's an alt key, send the message */
  329. c = d_key & ~ALT (0);
  330. if (d_key & ALT (0) && g_ascii_isalpha (c))
  331. d_key = g_ascii_tolower (c);
  332. handled = MSG_NOT_HANDLED;
  333. if (widget_get_options (current, WOP_WANT_HOTKEY))
  334. handled = send_message (current, NULL, MSG_HOTKEY, d_key, NULL);
  335. /* If not used, send hotkey to other widgets */
  336. if (handled == MSG_HANDLED)
  337. return MSG_HANDLED;
  338. hot_cur = dlg_get_widget_next_of (h->current);
  339. /* send it to all widgets */
  340. while (h->current != hot_cur && handled == MSG_NOT_HANDLED)
  341. {
  342. current = WIDGET (hot_cur->data);
  343. if (widget_get_options (current, WOP_WANT_HOTKEY)
  344. && !widget_get_state (current, WST_DISABLED))
  345. handled = send_message (current, NULL, MSG_HOTKEY, d_key, NULL);
  346. if (handled == MSG_NOT_HANDLED)
  347. hot_cur = dlg_get_widget_next_of (hot_cur);
  348. }
  349. if (handled == MSG_HANDLED)
  350. widget_select (WIDGET (hot_cur->data));
  351. return handled;
  352. }
  353. /* --------------------------------------------------------------------------------------------- */
  354. static void
  355. dlg_key_event (WDialog * h, int d_key)
  356. {
  357. cb_ret_t handled;
  358. if (h->widgets == NULL)
  359. return;
  360. if (h->current == NULL)
  361. h->current = h->widgets;
  362. /* TAB used to cycle */
  363. if (!widget_get_options (WIDGET (h), WOP_WANT_TAB))
  364. {
  365. if (d_key == '\t')
  366. {
  367. dlg_select_next_widget (h);
  368. return;
  369. }
  370. else if ((d_key & ~(KEY_M_SHIFT | KEY_M_CTRL)) == '\t')
  371. {
  372. dlg_select_prev_widget (h);
  373. return;
  374. }
  375. }
  376. /* first can dlg_callback handle the key */
  377. handled = send_message (h, NULL, MSG_KEY, d_key, NULL);
  378. /* next try the hotkey */
  379. if (handled == MSG_NOT_HANDLED)
  380. handled = dlg_try_hotkey (h, d_key);
  381. if (handled == MSG_HANDLED)
  382. send_message (h, NULL, MSG_HOTKEY_HANDLED, 0, NULL);
  383. else
  384. /* not used - then try widget_callback */
  385. handled = send_message (h->current->data, NULL, MSG_KEY, d_key, NULL);
  386. /* not used- try to use the unhandled case */
  387. if (handled == MSG_NOT_HANDLED)
  388. handled = send_message (h, NULL, MSG_UNHANDLED_KEY, d_key, NULL);
  389. if (handled == MSG_NOT_HANDLED)
  390. handled = dlg_handle_key (h, d_key);
  391. (void) handled;
  392. send_message (h, NULL, MSG_POST_KEY, d_key, NULL);
  393. }
  394. /* --------------------------------------------------------------------------------------------- */
  395. static void
  396. frontend_dlg_run (WDialog * h)
  397. {
  398. Widget *wh = WIDGET (h);
  399. Gpm_Event event;
  400. event.x = -1;
  401. /* close opened editors, viewers, etc */
  402. if (!widget_get_state (wh, WST_MODAL) && mc_global.midnight_shutdown)
  403. {
  404. send_message (h, NULL, MSG_VALIDATE, 0, NULL);
  405. return;
  406. }
  407. while (widget_get_state (wh, WST_ACTIVE))
  408. {
  409. int d_key;
  410. if (tty_got_winch ())
  411. dialog_change_screen_size ();
  412. if (is_idle ())
  413. {
  414. if (idle_hook)
  415. execute_hooks (idle_hook);
  416. while (widget_get_state (wh, WST_IDLE) && is_idle ())
  417. send_message (wh, NULL, MSG_IDLE, 0, NULL);
  418. /* Allow terminating the dialog from the idle handler */
  419. if (!widget_get_state (wh, WST_ACTIVE))
  420. break;
  421. }
  422. update_cursor (h);
  423. /* Clear interrupt flag */
  424. tty_got_interrupt ();
  425. d_key = tty_get_event (&event, h->mouse_status == MOU_REPEAT, TRUE);
  426. dlg_process_event (h, d_key, &event);
  427. if (widget_get_state (wh, WST_CLOSED))
  428. send_message (h, NULL, MSG_VALIDATE, 0, NULL);
  429. }
  430. }
  431. /* --------------------------------------------------------------------------------------------- */
  432. static int
  433. dlg_find_widget_by_id (gconstpointer a, gconstpointer b)
  434. {
  435. const Widget *w = CONST_WIDGET (a);
  436. unsigned long id = GPOINTER_TO_UINT (b);
  437. return w->id == id ? 0 : 1;
  438. }
  439. /* --------------------------------------------------------------------------------------------- */
  440. static void
  441. dlg_widget_set_position (gpointer data, gpointer user_data)
  442. {
  443. /* there are, mainly, 2 generally possible situations:
  444. * 1. control sticks to one side - it should be moved
  445. * 2. control sticks to two sides of one direction - it should be sized
  446. */
  447. Widget *c = WIDGET (data);
  448. Widget *wh = WIDGET (c->owner);
  449. const widget_shift_scale_t *wss = (const widget_shift_scale_t *) user_data;
  450. int x = c->x;
  451. int y = c->y;
  452. int cols = c->cols;
  453. int lines = c->lines;
  454. if ((c->pos_flags & WPOS_CENTER_HORZ) != 0)
  455. x = wh->x + (wh->cols - c->cols) / 2;
  456. else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0 && (c->pos_flags & WPOS_KEEP_RIGHT) != 0)
  457. {
  458. x += wss->shift_x;
  459. cols += wss->scale_x;
  460. }
  461. else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0)
  462. x += wss->shift_x;
  463. else if ((c->pos_flags & WPOS_KEEP_RIGHT) != 0)
  464. x += wss->shift_x + wss->scale_x;
  465. if ((c->pos_flags & WPOS_CENTER_VERT) != 0)
  466. y = wh->y + (wh->lines - c->lines) / 2;
  467. else if ((c->pos_flags & WPOS_KEEP_TOP) != 0 && (c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
  468. {
  469. y += wss->shift_y;
  470. lines += wss->scale_y;
  471. }
  472. else if ((c->pos_flags & WPOS_KEEP_TOP) != 0)
  473. y += wss->shift_y;
  474. else if ((c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
  475. y += wss->shift_y + wss->scale_y;
  476. widget_set_size (c, y, x, lines, cols);
  477. }
  478. /* --------------------------------------------------------------------------------------------- */
  479. /*** public functions ****************************************************************************/
  480. /* --------------------------------------------------------------------------------------------- */
  481. /** Clean the dialog area, draw the frame and the title */
  482. void
  483. dlg_default_repaint (WDialog * h)
  484. {
  485. Widget *wh = WIDGET (h);
  486. int space;
  487. if (!widget_get_state (wh, WST_ACTIVE))
  488. return;
  489. space = h->compact ? 0 : 1;
  490. tty_setcolor (h->color[DLG_COLOR_NORMAL]);
  491. dlg_erase (h);
  492. tty_draw_box (wh->y + space, wh->x + space, wh->lines - 2 * space, wh->cols - 2 * space, FALSE);
  493. if (h->title != NULL)
  494. {
  495. /* TODO: truncate long title */
  496. tty_setcolor (h->color[DLG_COLOR_TITLE]);
  497. widget_move (h, space, (wh->cols - str_term_width1 (h->title)) / 2);
  498. tty_print_string (h->title);
  499. }
  500. }
  501. /* --------------------------------------------------------------------------------------------- */
  502. /** this function allows to set dialog position */
  503. void
  504. dlg_set_position (WDialog * h, int y, int x, int lines, int cols)
  505. {
  506. Widget *wh = WIDGET (h);
  507. widget_shift_scale_t wss;
  508. /* save old positions, will be used to reposition childs */
  509. int ox, oy, oc, ol;
  510. /* save old positions, will be used to reposition childs */
  511. ox = wh->x;
  512. oy = wh->y;
  513. oc = wh->cols;
  514. ol = wh->lines;
  515. wh->x = x;
  516. wh->y = y;
  517. wh->lines = lines;
  518. wh->cols = cols;
  519. /* dialog is empty */
  520. if (h->widgets == NULL)
  521. return;
  522. if (h->current == NULL)
  523. h->current = h->widgets;
  524. /* values by which controls should be moved */
  525. wss.shift_x = wh->x - ox;
  526. wss.scale_x = wh->cols - oc;
  527. wss.shift_y = wh->y - oy;
  528. wss.scale_y = wh->lines - ol;
  529. if (wss.shift_x != 0 || wss.shift_y != 0 || wss.scale_x != 0 || wss.scale_y != 0)
  530. g_list_foreach (h->widgets, dlg_widget_set_position, &wss);
  531. }
  532. /* --------------------------------------------------------------------------------------------- */
  533. /** Set dialog size and position */
  534. void
  535. dlg_set_size (WDialog * h, int lines, int cols)
  536. {
  537. int x = 0, y = 0;
  538. widget_adjust_position (WIDGET (h)->pos_flags, &y, &x, &lines, &cols);
  539. dlg_set_position (h, y, x, lines, cols);
  540. }
  541. /* --------------------------------------------------------------------------------------------- */
  542. /** Default dialog callback */
  543. cb_ret_t
  544. dlg_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
  545. {
  546. WDialog *h = DIALOG (w);
  547. (void) sender;
  548. (void) parm;
  549. (void) data;
  550. switch (msg)
  551. {
  552. case MSG_DRAW:
  553. if (h->color != NULL)
  554. {
  555. dlg_default_repaint (h);
  556. return MSG_HANDLED;
  557. }
  558. return MSG_NOT_HANDLED;
  559. case MSG_IDLE:
  560. /* we don't want endless loop */
  561. widget_idle (w, FALSE);
  562. return MSG_HANDLED;
  563. case MSG_RESIZE:
  564. /* this is default resizing mechanism */
  565. /* the main idea of this code is to resize dialog
  566. according to flags (if any of flags require automatic
  567. resizing, like WPOS_CENTER, end after that reposition
  568. controls in dialog according to flags of widget) */
  569. dlg_set_size (h, w->lines, w->cols);
  570. return MSG_HANDLED;
  571. default:
  572. break;
  573. }
  574. return MSG_NOT_HANDLED;
  575. }
  576. /* --------------------------------------------------------------------------------------------- */
  577. WDialog *
  578. dlg_create (gboolean modal, int y1, int x1, int lines, int cols, widget_pos_flags_t pos_flags,
  579. gboolean compact, const int *colors, widget_cb_fn callback,
  580. widget_mouse_cb_fn mouse_callback, const char *help_ctx, const char *title)
  581. {
  582. WDialog *new_d;
  583. Widget *w;
  584. new_d = g_new0 (WDialog, 1);
  585. w = WIDGET (new_d);
  586. widget_adjust_position (pos_flags, &y1, &x1, &lines, &cols);
  587. widget_init (w, y1, x1, lines, cols, (callback != NULL) ? callback : dlg_default_callback,
  588. mouse_callback);
  589. w->pos_flags = pos_flags;
  590. w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
  591. w->state |= WST_CONSTRUCT | WST_FOCUSED;
  592. if (modal)
  593. w->state |= WST_MODAL;
  594. new_d->color = colors;
  595. new_d->help_ctx = help_ctx;
  596. new_d->compact = compact;
  597. new_d->data = NULL;
  598. new_d->mouse_status = MOU_UNHANDLED;
  599. dlg_set_title (new_d, title);
  600. /* unique name of event group for this dialog */
  601. new_d->event_group = g_strdup_printf ("%s_%p", MCEVENT_GROUP_DIALOG, (void *) new_d);
  602. return new_d;
  603. }
  604. /* --------------------------------------------------------------------------------------------- */
  605. void
  606. dlg_set_default_colors (void)
  607. {
  608. dialog_colors[DLG_COLOR_NORMAL] = COLOR_NORMAL;
  609. dialog_colors[DLG_COLOR_FOCUS] = COLOR_FOCUS;
  610. dialog_colors[DLG_COLOR_HOT_NORMAL] = COLOR_HOT_NORMAL;
  611. dialog_colors[DLG_COLOR_HOT_FOCUS] = COLOR_HOT_FOCUS;
  612. dialog_colors[DLG_COLOR_TITLE] = COLOR_TITLE;
  613. alarm_colors[DLG_COLOR_NORMAL] = ERROR_COLOR;
  614. alarm_colors[DLG_COLOR_FOCUS] = ERROR_FOCUS;
  615. alarm_colors[DLG_COLOR_HOT_NORMAL] = ERROR_HOT_NORMAL;
  616. alarm_colors[DLG_COLOR_HOT_FOCUS] = ERROR_HOT_FOCUS;
  617. alarm_colors[DLG_COLOR_TITLE] = ERROR_TITLE;
  618. listbox_colors[DLG_COLOR_NORMAL] = PMENU_ENTRY_COLOR;
  619. listbox_colors[DLG_COLOR_FOCUS] = PMENU_SELECTED_COLOR;
  620. listbox_colors[DLG_COLOR_HOT_NORMAL] = PMENU_ENTRY_COLOR;
  621. listbox_colors[DLG_COLOR_HOT_FOCUS] = PMENU_SELECTED_COLOR;
  622. listbox_colors[DLG_COLOR_TITLE] = PMENU_TITLE_COLOR;
  623. }
  624. /* --------------------------------------------------------------------------------------------- */
  625. void
  626. dlg_erase (WDialog * h)
  627. {
  628. Widget *wh = WIDGET (h);
  629. if (wh != NULL && widget_get_state (wh, WST_ACTIVE))
  630. tty_fill_region (wh->y, wh->x, wh->lines, wh->cols, ' ');
  631. }
  632. /* --------------------------------------------------------------------------------------------- */
  633. /**
  634. * Insert widget to dialog before requested widget. Make the widget current. Return widget ID.
  635. */
  636. unsigned long
  637. add_widget_autopos (WDialog * h, void *w, widget_pos_flags_t pos_flags, const void *before)
  638. {
  639. Widget *wh = WIDGET (h);
  640. Widget *widget;
  641. GList *new_current;
  642. /* Don't accept 0 widgets */
  643. if (w == NULL)
  644. abort ();
  645. widget = WIDGET (w);
  646. if ((pos_flags & WPOS_CENTER_HORZ) != 0)
  647. widget->x = (wh->cols - widget->cols) / 2;
  648. widget->x += wh->x;
  649. if ((pos_flags & WPOS_CENTER_VERT) != 0)
  650. widget->y = (wh->lines - widget->lines) / 2;
  651. widget->y += wh->y;
  652. widget->owner = h;
  653. widget->pos_flags = pos_flags;
  654. widget->id = h->widget_id++;
  655. if (h->widgets == NULL || before == NULL)
  656. {
  657. h->widgets = g_list_append (h->widgets, widget);
  658. new_current = g_list_last (h->widgets);
  659. }
  660. else
  661. {
  662. GList *b;
  663. b = g_list_find (h->widgets, before);
  664. /* don't accept widget not from dialog. This shouldn't happen */
  665. if (b == NULL)
  666. abort ();
  667. b = g_list_next (b);
  668. h->widgets = g_list_insert_before (h->widgets, b, widget);
  669. if (b != NULL)
  670. new_current = g_list_previous (b);
  671. else
  672. new_current = g_list_last (h->widgets);
  673. }
  674. /* widget has been added at runtime */
  675. if (widget_get_state (wh, WST_ACTIVE))
  676. {
  677. send_message (widget, NULL, MSG_INIT, 0, NULL);
  678. widget_select (widget);
  679. }
  680. else
  681. h->current = new_current;
  682. return widget->id;
  683. }
  684. /* --------------------------------------------------------------------------------------------- */
  685. /** wrapper to simply add lefttop positioned controls */
  686. unsigned long
  687. add_widget (WDialog * h, void *w)
  688. {
  689. return add_widget_autopos (h, w, WPOS_KEEP_DEFAULT,
  690. h->current != NULL ? h->current->data : NULL);
  691. }
  692. /* --------------------------------------------------------------------------------------------- */
  693. unsigned long
  694. add_widget_before (WDialog * h, void *w, void *before)
  695. {
  696. return add_widget_autopos (h, w, WPOS_KEEP_DEFAULT, before);
  697. }
  698. /* --------------------------------------------------------------------------------------------- */
  699. /** delete widget from dialog */
  700. void
  701. del_widget (void *w)
  702. {
  703. WDialog *h;
  704. GList *d;
  705. /* Don't accept NULL widget. This shouldn't happen */
  706. if (w == NULL)
  707. abort ();
  708. h = WIDGET (w)->owner;
  709. d = g_list_find (h->widgets, w);
  710. if (d == h->current)
  711. dlg_set_current_widget_next (h);
  712. h->widgets = g_list_remove_link (h->widgets, d);
  713. if (h->widgets == NULL)
  714. h->current = NULL;
  715. send_message (d->data, NULL, MSG_DESTROY, 0, NULL);
  716. g_free (d->data);
  717. g_list_free_1 (d);
  718. /* widget has been deleted in runtime */
  719. if (widget_get_state (WIDGET (h), WST_ACTIVE))
  720. {
  721. dlg_redraw (h);
  722. dlg_select_current_widget (h);
  723. }
  724. }
  725. /* --------------------------------------------------------------------------------------------- */
  726. void
  727. do_refresh (void)
  728. {
  729. GList *d = top_dlg;
  730. if (fast_refresh)
  731. {
  732. if (d != NULL)
  733. dlg_redraw (DIALOG (d->data));
  734. }
  735. else
  736. {
  737. /* Search first fullscreen dialog */
  738. for (; d != NULL; d = g_list_next (d))
  739. if ((WIDGET (d->data)->pos_flags & WPOS_FULLSCREEN) != 0)
  740. break;
  741. /* back to top dialog */
  742. for (; d != NULL; d = g_list_previous (d))
  743. dlg_redraw (DIALOG (d->data));
  744. }
  745. }
  746. /* --------------------------------------------------------------------------------------------- */
  747. /** broadcast a message to all the widgets in a dialog */
  748. void
  749. dlg_broadcast_msg (WDialog * h, widget_msg_t msg)
  750. {
  751. dlg_broadcast_msg_to (h, msg, FALSE, 0);
  752. }
  753. /* --------------------------------------------------------------------------------------------- */
  754. /** Find the widget with the given callback in the dialog h */
  755. Widget *
  756. find_widget_type (const WDialog * h, widget_cb_fn callback)
  757. {
  758. GList *w;
  759. w = g_list_find_custom (h->widgets, (gconstpointer) callback, dlg_find_widget_callback);
  760. return (w == NULL) ? NULL : WIDGET (w->data);
  761. }
  762. /* --------------------------------------------------------------------------------------------- */
  763. GList *
  764. dlg_find (const WDialog * h, const Widget * w)
  765. {
  766. return (w->owner == NULL || w->owner != h) ? NULL : g_list_find (h->widgets, w);
  767. }
  768. /* --------------------------------------------------------------------------------------------- */
  769. /** Find the widget with the given id */
  770. Widget *
  771. dlg_find_by_id (const WDialog * h, unsigned long id)
  772. {
  773. GList *w;
  774. w = g_list_find_custom (h->widgets, GUINT_TO_POINTER (id), dlg_find_widget_by_id);
  775. return w != NULL ? WIDGET (w->data) : NULL;
  776. }
  777. /* --------------------------------------------------------------------------------------------- */
  778. /** Find the widget with the given id in the dialog h and select it */
  779. void
  780. dlg_select_by_id (const WDialog * h, unsigned long id)
  781. {
  782. Widget *w;
  783. w = dlg_find_by_id (h, id);
  784. if (w != NULL)
  785. widget_select (w);
  786. }
  787. /* --------------------------------------------------------------------------------------------- */
  788. /** Try to select previous widget in the tab order */
  789. void
  790. dlg_select_prev_widget (WDialog * h)
  791. {
  792. dlg_select_next_or_prev (h, FALSE);
  793. }
  794. /* --------------------------------------------------------------------------------------------- */
  795. /** Try to select next widget in the tab order */
  796. void
  797. dlg_select_next_widget (WDialog * h)
  798. {
  799. dlg_select_next_or_prev (h, TRUE);
  800. }
  801. /* --------------------------------------------------------------------------------------------- */
  802. void
  803. update_cursor (WDialog * h)
  804. {
  805. GList *p = h->current;
  806. if (p != NULL && widget_get_state (WIDGET (h), WST_ACTIVE))
  807. {
  808. Widget *w = WIDGET (p->data);
  809. if (!widget_get_state (w, WST_DISABLED) && widget_get_options (w, WOP_WANT_CURSOR))
  810. send_message (w, NULL, MSG_CURSOR, 0, NULL);
  811. else
  812. do
  813. {
  814. p = dlg_get_widget_next_of (p);
  815. if (p == h->current)
  816. break;
  817. w = WIDGET (p->data);
  818. if (!widget_get_state (w, WST_DISABLED) && widget_get_options (w, WOP_WANT_CURSOR)
  819. && send_message (w, NULL, MSG_CURSOR, 0, NULL) == MSG_HANDLED)
  820. break;
  821. }
  822. while (TRUE);
  823. }
  824. }
  825. /* --------------------------------------------------------------------------------------------- */
  826. /**
  827. * Redraw the widgets in reverse order, leaving the current widget
  828. * as the last one
  829. */
  830. void
  831. dlg_redraw (WDialog * h)
  832. {
  833. if (!widget_get_state (WIDGET (h), WST_ACTIVE))
  834. return;
  835. if (h->winch_pending)
  836. {
  837. h->winch_pending = FALSE;
  838. send_message (h, NULL, MSG_RESIZE, 0, NULL);
  839. }
  840. send_message (h, NULL, MSG_DRAW, 0, NULL);
  841. dlg_broadcast_msg (h, MSG_DRAW);
  842. update_cursor (h);
  843. }
  844. /* --------------------------------------------------------------------------------------------- */
  845. void
  846. dlg_stop (WDialog * h)
  847. {
  848. widget_set_state (WIDGET (h), WST_CLOSED, TRUE);
  849. }
  850. /* --------------------------------------------------------------------------------------------- */
  851. /** Init the process */
  852. void
  853. dlg_init (WDialog * h)
  854. {
  855. Widget *wh = WIDGET (h);
  856. if (top_dlg != NULL && widget_get_state (WIDGET (top_dlg->data), WST_MODAL))
  857. widget_set_state (wh, WST_MODAL, TRUE);
  858. /* add dialog to the stack */
  859. top_dlg = g_list_prepend (top_dlg, h);
  860. /* Initialize dialog manager and widgets */
  861. if (widget_get_state (wh, WST_CONSTRUCT))
  862. {
  863. if (!widget_get_state (wh, WST_MODAL))
  864. dialog_switch_add (h);
  865. send_message (h, NULL, MSG_INIT, 0, NULL);
  866. dlg_broadcast_msg (h, MSG_INIT);
  867. dlg_read_history (h);
  868. }
  869. /* Select the first widget that takes focus */
  870. while (h->current != NULL && !widget_get_options (WIDGET (h->current->data), WOP_SELECTABLE)
  871. && !widget_get_state (WIDGET (h->current->data), WST_DISABLED))
  872. dlg_set_current_widget_next (h);
  873. widget_set_state (wh, WST_ACTIVE, TRUE);
  874. dlg_redraw (h);
  875. /* focus found widget */
  876. if (h->current != NULL)
  877. widget_set_state (WIDGET (h->current->data), WST_FOCUSED, TRUE);
  878. h->ret_value = 0;
  879. }
  880. /* --------------------------------------------------------------------------------------------- */
  881. void
  882. dlg_process_event (WDialog * h, int key, Gpm_Event * event)
  883. {
  884. switch (key)
  885. {
  886. case EV_NONE:
  887. if (tty_got_interrupt ())
  888. dlg_execute_cmd (h, CK_Cancel);
  889. break;
  890. case EV_MOUSE:
  891. h->mouse_status = dlg_mouse_event (h, event);
  892. break;
  893. default:
  894. dlg_key_event (h, key);
  895. break;
  896. }
  897. }
  898. /* --------------------------------------------------------------------------------------------- */
  899. /** Shutdown the dlg_run */
  900. void
  901. dlg_run_done (WDialog * h)
  902. {
  903. top_dlg = g_list_remove (top_dlg, h);
  904. if (widget_get_state (WIDGET (h), WST_CLOSED))
  905. {
  906. send_message (h, h->current == NULL ? NULL : WIDGET (h->current->data), MSG_END, 0, NULL);
  907. if (!widget_get_state (WIDGET (h), WST_MODAL))
  908. dialog_switch_remove (h);
  909. }
  910. }
  911. /* --------------------------------------------------------------------------------------------- */
  912. /**
  913. * Standard run dialog routine
  914. * We have to keep this routine small so that we can duplicate it's
  915. * behavior on complex routines like the file routines, this way,
  916. * they can call the dlg_process_event without rewriting all the code
  917. */
  918. int
  919. dlg_run (WDialog * h)
  920. {
  921. dlg_init (h);
  922. frontend_dlg_run (h);
  923. dlg_run_done (h);
  924. return h->ret_value;
  925. }
  926. /* --------------------------------------------------------------------------------------------- */
  927. void
  928. dlg_destroy (WDialog * h)
  929. {
  930. /* if some widgets have history, save all history at one moment here */
  931. dlg_save_history (h);
  932. dlg_broadcast_msg (h, MSG_DESTROY);
  933. g_list_free_full (h->widgets, g_free);
  934. mc_event_group_del (h->event_group);
  935. g_free (h->event_group);
  936. g_free (h->title);
  937. g_free (h);
  938. do_refresh ();
  939. }
  940. /* --------------------------------------------------------------------------------------------- */
  941. /**
  942. * Write history to the ${XDG_CACHE_HOME}/mc/history file
  943. */
  944. void
  945. dlg_save_history (WDialog * h)
  946. {
  947. char *profile;
  948. int i;
  949. if (num_history_items_recorded == 0) /* this is how to disable */
  950. return;
  951. profile = mc_config_get_full_path (MC_HISTORY_FILE);
  952. i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
  953. if (i != -1)
  954. close (i);
  955. /* Make sure the history is only readable by the user */
  956. if (chmod (profile, S_IRUSR | S_IWUSR) != -1 || errno == ENOENT)
  957. {
  958. ev_history_load_save_t event_data;
  959. event_data.cfg = mc_config_init (profile, FALSE);
  960. event_data.receiver = NULL;
  961. /* get all histories in dialog */
  962. mc_event_raise (h->event_group, MCEVENT_HISTORY_SAVE, &event_data);
  963. mc_config_save_file (event_data.cfg, NULL);
  964. mc_config_deinit (event_data.cfg);
  965. }
  966. g_free (profile);
  967. }
  968. /* --------------------------------------------------------------------------------------------- */
  969. void
  970. dlg_set_title (WDialog * h, const char *title)
  971. {
  972. MC_PTR_FREE (h->title);
  973. /* Strip existing spaces, add one space before and after the title */
  974. if (title != NULL && title[0] != '\0')
  975. {
  976. char *t;
  977. t = g_strstrip (g_strdup (title));
  978. if (t[0] != '\0')
  979. h->title = g_strdup_printf (" %s ", t);
  980. g_free (t);
  981. }
  982. }
  983. /* --------------------------------------------------------------------------------------------- */
  984. char *
  985. dlg_get_title (const WDialog * h, size_t len)
  986. {
  987. char *t;
  988. if (h == NULL)
  989. abort ();
  990. if (h->get_title != NULL)
  991. t = h->get_title (h, len);
  992. else
  993. t = g_strdup ("");
  994. return t;
  995. }
  996. /* --------------------------------------------------------------------------------------------- */
  997. /**
  998. * Switch current widget to widget after current in dialog.
  999. *
  1000. * @param h WDialog widget
  1001. */
  1002. void
  1003. dlg_set_current_widget_next (WDialog * h)
  1004. {
  1005. h->current = dlg_get_next_or_prev_of (h->current, TRUE);
  1006. }
  1007. /* --------------------------------------------------------------------------------------------- */
  1008. /**
  1009. * Switch current widget to widget before current in dialog.
  1010. *
  1011. * @param h WDialog widget
  1012. */
  1013. void
  1014. dlg_set_current_widget_prev (WDialog * h)
  1015. {
  1016. h->current = dlg_get_next_or_prev_of (h->current, FALSE);
  1017. }
  1018. /* --------------------------------------------------------------------------------------------- */
  1019. /**
  1020. * Get widget that is after specified widget in dialog.
  1021. *
  1022. * @param w widget holder
  1023. *
  1024. * @return widget that is after "w" or NULL if "w" is NULL or widget doesn't have owner
  1025. */
  1026. GList *
  1027. dlg_get_widget_next_of (GList * w)
  1028. {
  1029. return dlg_get_next_or_prev_of (w, TRUE);
  1030. }
  1031. /* --------------------------------------------------------------------------------------------- */
  1032. /**
  1033. * Get widget that is before specified widget in dialog.
  1034. *
  1035. * @param w widget holder
  1036. *
  1037. * @return widget that is before "w" or NULL if "w" is NULL or widget doesn't have owner
  1038. */
  1039. GList *
  1040. dlg_get_widget_prev_of (GList * w)
  1041. {
  1042. return dlg_get_next_or_prev_of (w, FALSE);
  1043. }
  1044. /* --------------------------------------------------------------------------------------------- */