root/lib/widget/dialog.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. dlg_get_next_or_prev_of
  2. dlg_select_next_or_prev
  3. dlg_broadcast_msg_to
  4. dlg_read_history
  5. dlg_find_widget_callback
  6. refresh_cmd
  7. dlg_execute_cmd
  8. dlg_handle_key
  9. dlg_mouse_translator
  10. dlg_mouse_event
  11. dlg_try_hotkey
  12. dlg_key_event
  13. frontend_dlg_run
  14. dlg_find_widget_by_id
  15. dlg_widget_set_position
  16. dlg_default_repaint
  17. dlg_set_position
  18. dlg_set_size
  19. dlg_default_callback
  20. dlg_create
  21. dlg_set_default_colors
  22. dlg_erase
  23. add_widget_autopos
  24. add_widget
  25. add_widget_before
  26. del_widget
  27. do_refresh
  28. dlg_broadcast_msg
  29. find_widget_type
  30. dlg_find
  31. dlg_find_by_id
  32. dlg_select_by_id
  33. dlg_select_prev_widget
  34. dlg_select_next_widget
  35. update_cursor
  36. dlg_redraw
  37. dlg_stop
  38. dlg_init
  39. dlg_process_event
  40. dlg_run_done
  41. dlg_run
  42. dlg_destroy
  43. dlg_save_history
  44. dlg_set_title
  45. dlg_get_title
  46. dlg_set_current_widget_next
  47. dlg_set_current_widget_prev
  48. dlg_get_widget_next_of
  49. dlg_get_widget_prev_of

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

/* [previous][next][first][last][top][bottom][index][help]  */