quick.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. /*
  2. Widget based utility functions.
  3. Copyright (C) 1994-2025
  4. Free Software Foundation, Inc.
  5. Authors:
  6. Miguel de Icaza, 1994, 1995, 1996
  7. Radek Doulik, 1994, 1995
  8. Jakub Jelinek, 1995
  9. Andrej Borsenkow, 1995
  10. Andrew Borodin <aborodin@vmail.ru>, 2009-2022
  11. This file is part of the Midnight Commander.
  12. The Midnight Commander is free software: you can redistribute it
  13. and/or modify it under the terms of the GNU General Public License as
  14. published by the Free Software Foundation, either version 3 of the License,
  15. or (at your option) any later version.
  16. The Midnight Commander is distributed in the hope that it will be useful,
  17. but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. GNU General Public License for more details.
  20. You should have received a copy of the GNU General Public License
  21. along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. */
  23. /** \file quick.c
  24. * \brief Source: quick dialog engine
  25. */
  26. #include <config.h>
  27. #include <stdlib.h>
  28. #include <stdio.h> /* fprintf() */
  29. #include "lib/global.h"
  30. #include "lib/strutil.h" /* str_term_width1() */
  31. #include "lib/util.h" /* tilde_expand() */
  32. #include "lib/widget.h"
  33. /*** global variables ****************************************************************************/
  34. /*** file scope macro definitions ****************************************************************/
  35. #ifdef ENABLE_NLS
  36. #define I18N(x) (x = x != NULL && *x != '\0' ? _(x) : x)
  37. #else
  38. #define I18N(x) (x = x)
  39. #endif
  40. /*** file scope type declarations ****************************************************************/
  41. typedef struct
  42. {
  43. Widget *widget;
  44. quick_widget_t *quick_widget;
  45. } quick_widget_item_t;
  46. /*** forward declarations (file scope functions) *************************************************/
  47. /*** file scope variables ************************************************************************/
  48. /* --------------------------------------------------------------------------------------------- */
  49. /*** file scope functions ************************************************************************/
  50. /* --------------------------------------------------------------------------------------------- */
  51. static WInput *
  52. quick_create_input (int y, int x, const quick_widget_t *qw)
  53. {
  54. WInput *in;
  55. in = input_new (y, x, input_colors, 8, qw->u.input.text, qw->u.input.histname,
  56. qw->u.input.completion_flags);
  57. in->is_password = qw->u.input.is_passwd;
  58. in->strip_password = qw->u.input.strip_passwd;
  59. return in;
  60. }
  61. /* --------------------------------------------------------------------------------------------- */
  62. static void
  63. quick_create_labeled_input (GArray *widgets, int *y, int x, quick_widget_t *quick_widget,
  64. int *width)
  65. {
  66. quick_widget_item_t in, label;
  67. label.quick_widget = g_new0 (quick_widget_t, 1);
  68. label.quick_widget->widget_type = quick_label;
  69. label.quick_widget->options = quick_widget->options;
  70. label.quick_widget->state = quick_widget->state;
  71. /* FIXME: this should be turned in depend of label_location */
  72. label.quick_widget->pos_flags = quick_widget->pos_flags;
  73. switch (quick_widget->u.input.label_location)
  74. {
  75. case input_label_above:
  76. label.widget = WIDGET (label_new (*y, x, I18N (quick_widget->u.input.label_text)));
  77. *y += label.widget->rect.lines - 1;
  78. g_array_append_val (widgets, label);
  79. in.widget = WIDGET (quick_create_input (++(*y), x, quick_widget));
  80. in.quick_widget = quick_widget;
  81. g_array_append_val (widgets, in);
  82. *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
  83. break;
  84. case input_label_left:
  85. label.widget = WIDGET (label_new (*y, x, I18N (quick_widget->u.input.label_text)));
  86. g_array_append_val (widgets, label);
  87. in.widget = WIDGET (quick_create_input (*y, x + label.widget->rect.cols + 1, quick_widget));
  88. in.quick_widget = quick_widget;
  89. g_array_append_val (widgets, in);
  90. *width = label.widget->rect.cols + in.widget->rect.cols + 1;
  91. break;
  92. case input_label_right:
  93. in.widget = WIDGET (quick_create_input (*y, x, quick_widget));
  94. in.quick_widget = quick_widget;
  95. g_array_append_val (widgets, in);
  96. label.widget =
  97. WIDGET (label_new
  98. (*y, x + in.widget->rect.cols + 1, I18N (quick_widget->u.input.label_text)));
  99. g_array_append_val (widgets, label);
  100. *width = label.widget->rect.cols + in.widget->rect.cols + 1;
  101. break;
  102. case input_label_below:
  103. in.widget = WIDGET (quick_create_input (*y, x, quick_widget));
  104. in.quick_widget = quick_widget;
  105. g_array_append_val (widgets, in);
  106. label.widget = WIDGET (label_new (++(*y), x, I18N (quick_widget->u.input.label_text)));
  107. *y += label.widget->rect.lines - 1;
  108. g_array_append_val (widgets, label);
  109. *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
  110. break;
  111. default:
  112. g_free (label.quick_widget);
  113. return;
  114. }
  115. INPUT (in.widget)->label = LABEL (label.widget);
  116. /* cross references */
  117. label.quick_widget->u.label.input = in.quick_widget;
  118. in.quick_widget->u.input.label = label.quick_widget;
  119. }
  120. /* --------------------------------------------------------------------------------------------- */
  121. /*** public functions ****************************************************************************/
  122. /* --------------------------------------------------------------------------------------------- */
  123. int
  124. quick_dialog_skip (quick_dialog_t *quick_dlg, int nskip)
  125. {
  126. int len;
  127. int blen = 0;
  128. int x, y; /* current positions */
  129. int y1 = 0; /* bottom of 1st column in case of two columns */
  130. int y2 = -1; /* start of two columns */
  131. int width1 = 0; /* width of single column */
  132. int width2 = 0; /* width of each of two columns */
  133. gboolean have_groupbox = FALSE;
  134. gboolean two_columns = FALSE;
  135. gboolean put_buttons = FALSE;
  136. /* x position of 1st column is 3 */
  137. const int x1 = 3;
  138. /* x position of 2nd column is 4 and it will be fixed later, after creation of all widgets */
  139. int x2 = 4;
  140. GArray *widgets;
  141. size_t i;
  142. quick_widget_t *quick_widget;
  143. WGroupbox *g = NULL;
  144. WDialog *dd;
  145. GList *input_labels = NULL; /* Widgets not directly requested by the user. */
  146. int return_val;
  147. len = str_term_width1 (I18N (quick_dlg->title)) + 6;
  148. quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
  149. y = 1;
  150. x = x1;
  151. /* create widgets */
  152. widgets = g_array_sized_new (FALSE, FALSE, sizeof (quick_widget_item_t), 8);
  153. for (quick_widget = quick_dlg->widgets; quick_widget->widget_type != quick_end; quick_widget++)
  154. {
  155. quick_widget_item_t item = { NULL, quick_widget };
  156. int width = 0;
  157. switch (quick_widget->widget_type)
  158. {
  159. case quick_checkbox:
  160. item.widget =
  161. WIDGET (check_new
  162. (++y, x, *quick_widget->u.checkbox.state,
  163. I18N (quick_widget->u.checkbox.text)));
  164. g_array_append_val (widgets, item);
  165. width = item.widget->rect.cols;
  166. if (g != NULL)
  167. width += 2;
  168. if (two_columns)
  169. width2 = MAX (width2, width);
  170. else
  171. width1 = MAX (width1, width);
  172. break;
  173. case quick_button:
  174. /* single button */
  175. item.widget = WIDGET (button_new (++y, x, quick_widget->u.button.action,
  176. quick_widget->u.button.action == B_ENTER ?
  177. DEFPUSH_BUTTON : NORMAL_BUTTON,
  178. I18N (quick_widget->u.button.text),
  179. quick_widget->u.button.callback));
  180. g_array_append_val (widgets, item);
  181. width = item.widget->rect.cols;
  182. if (g != NULL)
  183. width += 2;
  184. if (two_columns)
  185. width2 = MAX (width2, width);
  186. else
  187. width1 = MAX (width1, width);
  188. break;
  189. case quick_input:
  190. *quick_widget->u.input.result = NULL;
  191. y++;
  192. if (quick_widget->u.input.label_location != input_label_none)
  193. {
  194. quick_create_labeled_input (widgets, &y, x, quick_widget, &width);
  195. input_labels = g_list_prepend (input_labels, quick_widget->u.input.label);
  196. }
  197. else
  198. {
  199. item.widget = WIDGET (quick_create_input (y, x, quick_widget));
  200. g_array_append_val (widgets, item);
  201. width = item.widget->rect.cols;
  202. }
  203. if (g != NULL)
  204. width += 2;
  205. if (two_columns)
  206. width2 = MAX (width2, width);
  207. else
  208. width1 = MAX (width1, width);
  209. break;
  210. case quick_label:
  211. item.widget = WIDGET (label_new (++y, x, I18N (quick_widget->u.label.text)));
  212. g_array_append_val (widgets, item);
  213. y += item.widget->rect.lines - 1;
  214. width = item.widget->rect.cols;
  215. if (g != NULL)
  216. width += 2;
  217. if (two_columns)
  218. width2 = MAX (width2, width);
  219. else
  220. width1 = MAX (width1, width);
  221. break;
  222. case quick_radio:
  223. {
  224. WRadio *r;
  225. char **items = NULL;
  226. /* create the copy of radio_items to avoid mwmory leak */
  227. items = g_new (char *, quick_widget->u.radio.count + 1);
  228. for (i = 0; i < (size_t) quick_widget->u.radio.count; i++)
  229. items[i] = g_strdup (_(quick_widget->u.radio.items[i]));
  230. items[i] = NULL;
  231. r = radio_new (++y, x, quick_widget->u.radio.count, (const char **) items);
  232. r->pos = r->sel = *quick_widget->u.radio.value;
  233. g_strfreev (items);
  234. item.widget = WIDGET (r);
  235. g_array_append_val (widgets, item);
  236. y += item.widget->rect.lines - 1;
  237. width = item.widget->rect.cols;
  238. if (g != NULL)
  239. width += 2;
  240. if (two_columns)
  241. width2 = MAX (width2, width);
  242. else
  243. width1 = MAX (width1, width);
  244. }
  245. break;
  246. case quick_start_groupbox:
  247. I18N (quick_widget->u.groupbox.title);
  248. len = str_term_width1 (quick_widget->u.groupbox.title);
  249. g = groupbox_new (++y, x, 1, len + 4, quick_widget->u.groupbox.title);
  250. item.widget = WIDGET (g);
  251. g_array_append_val (widgets, item);
  252. have_groupbox = TRUE;
  253. break;
  254. case quick_stop_groupbox:
  255. if (g != NULL)
  256. {
  257. Widget *w = WIDGET (g);
  258. y++;
  259. w->rect.lines = y + 1 - w->rect.y;
  260. g = NULL;
  261. g_array_append_val (widgets, item);
  262. }
  263. break;
  264. case quick_separator:
  265. y++;
  266. if (quick_widget->u.separator.line)
  267. {
  268. item.widget = WIDGET (hline_new (y, x, 1));
  269. g_array_append_val (widgets, item);
  270. }
  271. break;
  272. case quick_start_columns:
  273. y2 = y;
  274. g_array_append_val (widgets, item);
  275. two_columns = TRUE;
  276. break;
  277. case quick_next_column:
  278. x = x2;
  279. y1 = y;
  280. y = y2;
  281. break;
  282. case quick_stop_columns:
  283. x = x1;
  284. y = MAX (y1, y);
  285. g_array_append_val (widgets, item);
  286. two_columns = FALSE;
  287. break;
  288. case quick_buttons:
  289. /* start put several buttons in bottom line */
  290. if (quick_widget->u.separator.space)
  291. {
  292. y++;
  293. if (quick_widget->u.separator.line)
  294. item.widget = WIDGET (hline_new (y, 1, -1));
  295. }
  296. g_array_append_val (widgets, item);
  297. /* several buttons in bottom line */
  298. y++;
  299. quick_widget++;
  300. for (; quick_widget->widget_type == quick_button; quick_widget++)
  301. {
  302. item.widget = WIDGET (button_new (y, x++, quick_widget->u.button.action,
  303. quick_widget->u.button.action == B_ENTER ?
  304. DEFPUSH_BUTTON : NORMAL_BUTTON,
  305. I18N (quick_widget->u.button.text),
  306. quick_widget->u.button.callback));
  307. item.quick_widget = quick_widget;
  308. g_array_append_val (widgets, item);
  309. blen += item.widget->rect.cols + 1;
  310. }
  311. /* stop dialog build here */
  312. blen--;
  313. quick_widget->widget_type = quick_end;
  314. quick_widget--;
  315. break;
  316. default:
  317. break;
  318. }
  319. }
  320. /* adjust dialog width */
  321. quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, blen + 6);
  322. if (have_groupbox)
  323. {
  324. if (width1 != 0)
  325. width1 += 2;
  326. if (width2 != 0)
  327. width2 += 2;
  328. }
  329. if (width2 == 0)
  330. len = width1 + 6;
  331. else
  332. {
  333. len = width2 * 2 + 7;
  334. if (width1 != 0)
  335. len = MAX (len, width1 + 6);
  336. }
  337. quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
  338. width1 = quick_dlg->rect.cols - 6;
  339. width2 = (quick_dlg->rect.cols - 7) / 2;
  340. if (quick_dlg->rect.x == -1 || quick_dlg->rect.y == -1)
  341. dd = dlg_create (TRUE, 0, 0, y + 3, quick_dlg->rect.cols, WPOS_CENTER | WPOS_TRYUP, FALSE,
  342. dialog_colors, quick_dlg->callback, quick_dlg->mouse_callback,
  343. quick_dlg->help, quick_dlg->title);
  344. else
  345. dd = dlg_create (TRUE, quick_dlg->rect.y, quick_dlg->rect.x, y + 3, quick_dlg->rect.cols,
  346. WPOS_KEEP_DEFAULT, FALSE, dialog_colors, quick_dlg->callback,
  347. quick_dlg->mouse_callback, quick_dlg->help, quick_dlg->title);
  348. /* add widgets into the dialog */
  349. x2 = x1 + width2 + 1;
  350. g = NULL;
  351. two_columns = FALSE;
  352. x = (WIDGET (dd)->rect.cols - blen) / 2;
  353. for (i = 0; i < widgets->len; i++)
  354. {
  355. quick_widget_item_t *item;
  356. int column_width;
  357. WRect *r;
  358. item = &g_array_index (widgets, quick_widget_item_t, i);
  359. column_width = two_columns ? width2 : width1;
  360. /* adjust widget width and x position */
  361. switch (item->quick_widget->widget_type)
  362. {
  363. case quick_label:
  364. {
  365. quick_widget_t *input = item->quick_widget->u.label.input;
  366. if (input != NULL && input->u.input.label_location == input_label_right)
  367. {
  368. /* location of this label will be adjusted later */
  369. break;
  370. }
  371. }
  372. MC_FALLTHROUGH;
  373. case quick_checkbox:
  374. case quick_radio:
  375. r = &item->widget->rect;
  376. if (r->x != x1)
  377. r->x = x2;
  378. if (g != NULL)
  379. r->x += 2;
  380. break;
  381. case quick_button:
  382. r = &item->widget->rect;
  383. if (!put_buttons)
  384. {
  385. if (r->x != x1)
  386. r->x = x2;
  387. if (g != NULL)
  388. r->x += 2;
  389. }
  390. else
  391. {
  392. r->x = x;
  393. x += r->cols + 1;
  394. }
  395. break;
  396. case quick_input:
  397. {
  398. Widget *label = WIDGET (INPUT (item->widget)->label);
  399. int width = column_width;
  400. if (g != NULL)
  401. width -= 4;
  402. r = &item->widget->rect;
  403. switch (item->quick_widget->u.input.label_location)
  404. {
  405. case input_label_left:
  406. /* label was adjusted before; adjust input line */
  407. r->x = label->rect.x + label->rect.cols + 1 - WIDGET (label->owner)->rect.x;
  408. r->cols = width - label->rect.cols - 1;
  409. break;
  410. case input_label_right:
  411. if (r->x != x1)
  412. r->x = x2;
  413. if (g != NULL)
  414. r->x += 2;
  415. r->cols = width - label->rect.cols - 1;
  416. label->rect.x = r->x + r->cols + 1;
  417. break;
  418. default:
  419. if (r->x != x1)
  420. r->x = x2;
  421. if (g != NULL)
  422. r->x += 2;
  423. r->cols = width;
  424. break;
  425. }
  426. /* forced update internal variables of input line */
  427. r->lines = 1;
  428. widget_set_size_rect (item->widget, r);
  429. }
  430. break;
  431. case quick_start_groupbox:
  432. g = GROUPBOX (item->widget);
  433. r = &item->widget->rect;
  434. if (r->x != x1)
  435. r->x = x2;
  436. r->cols = column_width;
  437. break;
  438. case quick_stop_groupbox:
  439. g = NULL;
  440. break;
  441. case quick_separator:
  442. if (item->widget != NULL)
  443. {
  444. r = &item->widget->rect;
  445. if (g != NULL)
  446. {
  447. Widget *wg = WIDGET (g);
  448. HLINE (item->widget)->auto_adjust_cols = FALSE;
  449. r->x = wg->rect.x + 1 - WIDGET (wg->owner)->rect.x;
  450. r->cols = wg->rect.cols;
  451. }
  452. else if (two_columns)
  453. {
  454. HLINE (item->widget)->auto_adjust_cols = FALSE;
  455. if (r->x != x1)
  456. r->x = x2;
  457. r->x--;
  458. r->cols = column_width + 2;
  459. }
  460. else
  461. HLINE (item->widget)->auto_adjust_cols = TRUE;
  462. }
  463. break;
  464. case quick_start_columns:
  465. two_columns = TRUE;
  466. break;
  467. case quick_stop_columns:
  468. two_columns = FALSE;
  469. break;
  470. case quick_buttons:
  471. /* several buttons in bottom line */
  472. put_buttons = TRUE;
  473. break;
  474. default:
  475. break;
  476. }
  477. if (item->widget != NULL)
  478. {
  479. unsigned long id;
  480. /* add widget into dialog */
  481. item->widget->options |= item->quick_widget->options; /* FIXME: cannot reset flags, setup only */
  482. item->widget->state |= item->quick_widget->state; /* FIXME: cannot reset flags, setup only */
  483. id = group_add_widget_autopos (GROUP (dd), item->widget, item->quick_widget->pos_flags,
  484. NULL);
  485. if (item->quick_widget->id != NULL)
  486. *item->quick_widget->id = id;
  487. }
  488. }
  489. /* skip frame widget */
  490. if (dd->bg != NULL)
  491. nskip++;
  492. while (nskip-- != 0)
  493. group_set_current_widget_next (GROUP (dd));
  494. return_val = dlg_run (dd);
  495. /* Get the data if we found something interesting */
  496. if (return_val != B_CANCEL)
  497. for (i = 0; i < widgets->len; i++)
  498. {
  499. quick_widget_item_t *item;
  500. item = &g_array_index (widgets, quick_widget_item_t, i);
  501. switch (item->quick_widget->widget_type)
  502. {
  503. case quick_checkbox:
  504. *item->quick_widget->u.checkbox.state = CHECK (item->widget)->state;
  505. break;
  506. case quick_input:
  507. if ((item->quick_widget->u.input.completion_flags & INPUT_COMPLETE_CD) != 0)
  508. *item->quick_widget->u.input.result =
  509. tilde_expand (input_get_ctext (INPUT (item->widget)));
  510. else
  511. *item->quick_widget->u.input.result = input_get_text (INPUT (item->widget));
  512. break;
  513. case quick_radio:
  514. *item->quick_widget->u.radio.value = RADIO (item->widget)->sel;
  515. break;
  516. default:
  517. break;
  518. }
  519. }
  520. widget_destroy (WIDGET (dd));
  521. g_list_free_full (input_labels, g_free); /* destroy input labels created before */
  522. g_array_free (widgets, TRUE);
  523. return return_val;
  524. }
  525. /* --------------------------------------------------------------------------------------------- */