root/lib/widget/dialog.c

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

DEFINITIONS

This source file includes following definitions.
  1. dlg_default_get_colors
  2. dlg_read_history
  3. refresh_cmd
  4. dlg_execute_cmd
  5. dlg_handle_key
  6. dlg_key_event
  7. dlg_handle_mouse_event
  8. frontend_dlg_run
  9. dlg_default_destroy
  10. dlg_default_callback
  11. dlg_default_mouse_callback
  12. dlg_create
  13. dlg_set_default_colors
  14. do_refresh
  15. dlg_stop
  16. dlg_init
  17. dlg_process_event
  18. dlg_run_done
  19. dlg_run
  20. dlg_save_history
  21. dlg_get_title

   1 /*
   2    Dialog box features module for the Midnight Commander
   3 
   4    Copyright (C) 1994-2021
   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 /*** file scope variables ************************************************************************/
  79 
  80 /* --------------------------------------------------------------------------------------------- */
  81 /*** file scope functions ************************************************************************/
  82 /* --------------------------------------------------------------------------------------------- */
  83 
  84 static const int *
  85 dlg_default_get_colors (const Widget * w)
     /* [previous][next][first][last][top][bottom][index][help]  */
  86 {
  87     return CONST_DIALOG (w)->colors;
  88 }
  89 
  90 /* --------------------------------------------------------------------------------------------- */
  91 /**
  92   * Read histories from the ${XDG_CACHE_HOME}/mc/history file
  93   */
  94 static void
  95 dlg_read_history (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
  96 {
  97     char *profile;
  98     ev_history_load_save_t event_data;
  99 
 100     if (num_history_items_recorded == 0)        /* this is how to disable */
 101         return;
 102 
 103     profile = mc_config_get_full_path (MC_HISTORY_FILE);
 104     event_data.cfg = mc_config_init (profile, TRUE);
 105     event_data.receiver = NULL;
 106 
 107     /* create all histories in dialog */
 108     mc_event_raise (h->event_group, MCEVENT_HISTORY_LOAD, &event_data);
 109 
 110     mc_config_deinit (event_data.cfg);
 111     g_free (profile);
 112 }
 113 
 114 /* --------------------------------------------------------------------------------------------- */
 115 
 116 static void
 117 refresh_cmd (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 118 {
 119 #ifdef HAVE_SLANG
 120     tty_touch_screen ();
 121     mc_refresh ();
 122 #else
 123     /* Use this if the refreshes fail */
 124     tty_clear_screen ();
 125     repaint_screen ();
 126 #endif /* HAVE_SLANG */
 127 }
 128 
 129 /* --------------------------------------------------------------------------------------------- */
 130 
 131 static cb_ret_t
 132 dlg_execute_cmd (WDialog * h, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 133 {
 134     WGroup *g = GROUP (h);
 135     cb_ret_t ret = MSG_HANDLED;
 136 
 137     if (send_message (h, NULL, MSG_ACTION, command, NULL) == MSG_HANDLED)
 138         return MSG_HANDLED;
 139 
 140     switch (command)
 141     {
 142     case CK_Ok:
 143         h->ret_value = B_ENTER;
 144         dlg_stop (h);
 145         break;
 146     case CK_Cancel:
 147         h->ret_value = B_CANCEL;
 148         dlg_stop (h);
 149         break;
 150 
 151     case CK_Up:
 152     case CK_Left:
 153         group_select_prev_widget (g);
 154         break;
 155     case CK_Down:
 156     case CK_Right:
 157         group_select_next_widget (g);
 158         break;
 159 
 160     case CK_Help:
 161         {
 162             ev_help_t event_data = { NULL, h->help_ctx };
 163             mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
 164         }
 165         break;
 166 
 167     case CK_Suspend:
 168         mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL);
 169         refresh_cmd ();
 170         break;
 171     case CK_Refresh:
 172         refresh_cmd ();
 173         break;
 174 
 175     case CK_ScreenList:
 176         if (!widget_get_state (WIDGET (h), WST_MODAL))
 177             dialog_switch_list ();
 178         else
 179             ret = MSG_NOT_HANDLED;
 180         break;
 181     case CK_ScreenNext:
 182         if (!widget_get_state (WIDGET (h), WST_MODAL))
 183             dialog_switch_next ();
 184         else
 185             ret = MSG_NOT_HANDLED;
 186         break;
 187     case CK_ScreenPrev:
 188         if (!widget_get_state (WIDGET (h), WST_MODAL))
 189             dialog_switch_prev ();
 190         else
 191             ret = MSG_NOT_HANDLED;
 192         break;
 193 
 194     default:
 195         ret = MSG_NOT_HANDLED;
 196     }
 197 
 198     return ret;
 199 }
 200 
 201 /* --------------------------------------------------------------------------------------------- */
 202 
 203 static cb_ret_t
 204 dlg_handle_key (WDialog * h, int d_key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 205 {
 206     long command;
 207 
 208     command = widget_lookup_key (WIDGET (h), d_key);
 209     if (command == CK_IgnoreKey)
 210         command = keybind_lookup_keymap_command (dialog_map, d_key);
 211     if (command != CK_IgnoreKey)
 212         return dlg_execute_cmd (h, command);
 213 
 214     return MSG_NOT_HANDLED;
 215 }
 216 
 217 /* --------------------------------------------------------------------------------------------- */
 218 
 219 static void
 220 dlg_key_event (WDialog * h, int d_key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 221 {
 222     Widget *w = WIDGET (h);
 223     WGroup *g = GROUP (h);
 224     cb_ret_t handled;
 225 
 226     if (g->widgets == NULL)
 227         return;
 228 
 229     if (g->current == NULL)
 230         g->current = g->widgets;
 231 
 232     /* TAB used to cycle */
 233     if (!widget_get_options (w, WOP_WANT_TAB))
 234     {
 235         if (d_key == '\t')
 236         {
 237             group_select_next_widget (g);
 238             return;
 239         }
 240         else if ((d_key & ~(KEY_M_SHIFT | KEY_M_CTRL)) == '\t')
 241         {
 242             group_select_prev_widget (g);
 243             return;
 244         }
 245     }
 246 
 247     /* first can dlalog handle the key itself */
 248     handled = send_message (h, NULL, MSG_KEY, d_key, NULL);
 249 
 250     if (handled == MSG_NOT_HANDLED)
 251         handled = group_default_callback (w, NULL, MSG_KEY, d_key, NULL);
 252 
 253     if (handled == MSG_NOT_HANDLED)
 254         handled = dlg_handle_key (h, d_key);
 255 
 256     (void) handled;
 257     send_message (h, NULL, MSG_POST_KEY, d_key, NULL);
 258 }
 259 
 260 /* --------------------------------------------------------------------------------------------- */
 261 
 262 static int
 263 dlg_handle_mouse_event (Widget * w, Gpm_Event * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 264 {
 265     if (w->mouse_callback != NULL)
 266     {
 267         int mou;
 268 
 269         mou = mouse_handle_event (w, event);
 270         if (mou != MOU_UNHANDLED)
 271             return mou;
 272     }
 273 
 274     return group_handle_mouse_event (w, event);
 275 }
 276 
 277 /* --------------------------------------------------------------------------------------------- */
 278 
 279 static void
 280 frontend_dlg_run (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 281 {
 282     Widget *wh = WIDGET (h);
 283     Gpm_Event event;
 284 
 285     event.x = -1;
 286 
 287     /* close opened editors, viewers, etc */
 288     if (!widget_get_state (wh, WST_MODAL) && mc_global.midnight_shutdown)
 289     {
 290         send_message (h, NULL, MSG_VALIDATE, 0, NULL);
 291         return;
 292     }
 293 
 294     while (widget_get_state (wh, WST_ACTIVE))
 295     {
 296         int d_key;
 297 
 298         if (tty_got_winch ())
 299             dialog_change_screen_size ();
 300 
 301         if (is_idle ())
 302         {
 303             if (idle_hook)
 304                 execute_hooks (idle_hook);
 305 
 306             while (widget_get_state (wh, WST_IDLE) && is_idle ())
 307                 send_message (wh, NULL, MSG_IDLE, 0, NULL);
 308 
 309             /* Allow terminating the dialog from the idle handler */
 310             if (!widget_get_state (wh, WST_ACTIVE))
 311                 break;
 312         }
 313 
 314         widget_update_cursor (wh);
 315 
 316         /* Clear interrupt flag */
 317         tty_got_interrupt ();
 318         d_key = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, TRUE);
 319 
 320         dlg_process_event (h, d_key, &event);
 321 
 322         if (widget_get_state (wh, WST_CLOSED))
 323             send_message (h, NULL, MSG_VALIDATE, 0, NULL);
 324     }
 325 }
 326 
 327 /* --------------------------------------------------------------------------------------------- */
 328 
 329 static void
 330 dlg_default_destroy (Widget * w)
     /* [previous][next][first][last][top][bottom][index][help]  */
 331 {
 332     WDialog *h = DIALOG (w);
 333 
 334     /* if some widgets have history, save all histories at one moment here */
 335     dlg_save_history (h);
 336     group_default_callback (w, NULL, MSG_DESTROY, 0, NULL);
 337     mc_event_group_del (h->event_group);
 338     g_free (h->event_group);
 339     g_free (h);
 340 
 341     do_refresh ();
 342 }
 343 
 344 /* --------------------------------------------------------------------------------------------- */
 345 /*** public functions ****************************************************************************/
 346 /* --------------------------------------------------------------------------------------------- */
 347 /** Default dialog callback */
 348 
 349 cb_ret_t
 350 dlg_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 351 {
 352     switch (msg)
 353     {
 354     case MSG_INIT:
 355         /* nothing to init in dialog itself */
 356         return MSG_HANDLED;
 357 
 358     case MSG_IDLE:
 359         /* we don't want endless loop */
 360         widget_idle (w, FALSE);
 361         return MSG_HANDLED;
 362 
 363     case MSG_DESTROY:
 364         /* nothing to deinit in dialog itself */
 365         return MSG_HANDLED;
 366 
 367     default:
 368         return group_default_callback (w, sender, msg, parm, data);
 369     }
 370 }
 371 
 372 /* --------------------------------------------------------------------------------------------- */
 373 
 374 void
 375 dlg_default_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 376 {
 377     switch (msg)
 378     {
 379     case MSG_MOUSE_CLICK:
 380         if (event->y < 0 || event->y >= w->lines || event->x < 0 || event->x >= w->cols)
 381         {
 382             DIALOG (w)->ret_value = B_CANCEL;
 383             dlg_stop (DIALOG (w));
 384         }
 385         break;
 386 
 387     default:
 388         /* return MOU_UNHANDLED */
 389         event->result.abort = TRUE;
 390         break;
 391     }
 392 }
 393 
 394 /* --------------------------------------------------------------------------------------------- */
 395 
 396 WDialog *
 397 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]  */
 398             gboolean compact, const int *colors, widget_cb_fn callback,
 399             widget_mouse_cb_fn mouse_callback, const char *help_ctx, const char *title)
 400 {
 401     WDialog *new_d;
 402     Widget *w;
 403     WGroup *g;
 404 
 405     new_d = g_new0 (WDialog, 1);
 406     w = WIDGET (new_d);
 407     g = GROUP (new_d);
 408     widget_adjust_position (pos_flags, &y1, &x1, &lines, &cols);
 409     group_init (g, y1, x1, lines, cols, callback != NULL ? callback : dlg_default_callback,
 410                 mouse_callback != NULL ? mouse_callback : dlg_default_mouse_callback);
 411 
 412     w->pos_flags = pos_flags;
 413     w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
 414     w->state |= WST_FOCUSED;
 415     /* Temporary hack: dialog doesn't have an owner, own itself. */
 416     w->owner = g;
 417 
 418     w->keymap = dialog_map;
 419 
 420     w->mouse_handler = dlg_handle_mouse_event;
 421     w->mouse.forced_capture = mouse_close_dialog && (w->pos_flags & WPOS_FULLSCREEN) == 0;
 422 
 423     w->destroy = dlg_default_destroy;
 424     w->get_colors = dlg_default_get_colors;
 425 
 426     new_d->colors = colors;
 427     new_d->help_ctx = help_ctx;
 428     new_d->compact = compact;
 429     new_d->data = NULL;
 430 
 431     if (modal)
 432     {
 433         w->state |= WST_MODAL;
 434 
 435         new_d->bg = WIDGET (frame_new (0, 0, w->lines, w->cols, title, FALSE, new_d->compact));
 436         group_add_widget (g, new_d->bg);
 437         frame_set_title (FRAME (new_d->bg), title);
 438     }
 439 
 440     /* unique name of event group for this dialog */
 441     new_d->event_group = g_strdup_printf ("%s_%p", MCEVENT_GROUP_DIALOG, (void *) new_d);
 442 
 443     return new_d;
 444 }
 445 
 446 /* --------------------------------------------------------------------------------------------- */
 447 
 448 void
 449 dlg_set_default_colors (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 450 {
 451     dialog_colors[DLG_COLOR_NORMAL] = COLOR_NORMAL;
 452     dialog_colors[DLG_COLOR_FOCUS] = COLOR_FOCUS;
 453     dialog_colors[DLG_COLOR_HOT_NORMAL] = COLOR_HOT_NORMAL;
 454     dialog_colors[DLG_COLOR_HOT_FOCUS] = COLOR_HOT_FOCUS;
 455     dialog_colors[DLG_COLOR_TITLE] = COLOR_TITLE;
 456 
 457     alarm_colors[DLG_COLOR_NORMAL] = ERROR_COLOR;
 458     alarm_colors[DLG_COLOR_FOCUS] = ERROR_FOCUS;
 459     alarm_colors[DLG_COLOR_HOT_NORMAL] = ERROR_HOT_NORMAL;
 460     alarm_colors[DLG_COLOR_HOT_FOCUS] = ERROR_HOT_FOCUS;
 461     alarm_colors[DLG_COLOR_TITLE] = ERROR_TITLE;
 462 
 463     listbox_colors[DLG_COLOR_NORMAL] = PMENU_ENTRY_COLOR;
 464     listbox_colors[DLG_COLOR_FOCUS] = PMENU_SELECTED_COLOR;
 465     listbox_colors[DLG_COLOR_HOT_NORMAL] = PMENU_ENTRY_COLOR;
 466     listbox_colors[DLG_COLOR_HOT_FOCUS] = PMENU_SELECTED_COLOR;
 467     listbox_colors[DLG_COLOR_TITLE] = PMENU_TITLE_COLOR;
 468 }
 469 
 470 /* --------------------------------------------------------------------------------------------- */
 471 
 472 void
 473 do_refresh (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 474 {
 475     GList *d = top_dlg;
 476 
 477     if (fast_refresh)
 478     {
 479         if (d != NULL)
 480             widget_draw (WIDGET (d->data));
 481     }
 482     else
 483     {
 484         /* Search first fullscreen dialog */
 485         for (; d != NULL; d = g_list_next (d))
 486             if ((WIDGET (d->data)->pos_flags & WPOS_FULLSCREEN) != 0)
 487                 break;
 488 
 489         /* when small dialog (i.e. error message) is created first,
 490            there is no fullscreen dialog in the stack */
 491         if (d == NULL)
 492             d = g_list_last (top_dlg);
 493 
 494         /* back to top dialog */
 495         for (; d != NULL; d = g_list_previous (d))
 496             widget_draw (WIDGET (d->data));
 497     }
 498 }
 499 
 500 /* --------------------------------------------------------------------------------------------- */
 501 
 502 void
 503 dlg_stop (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 504 {
 505     widget_set_state (WIDGET (h), WST_CLOSED, TRUE);
 506 }
 507 
 508 /* --------------------------------------------------------------------------------------------- */
 509 /** Init the process */
 510 
 511 void
 512 dlg_init (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 513 {
 514     WGroup *g = GROUP (h);
 515     Widget *wh = WIDGET (h);
 516 
 517     if (top_dlg != NULL && widget_get_state (WIDGET (top_dlg->data), WST_MODAL))
 518         widget_set_state (wh, WST_MODAL, TRUE);
 519 
 520     /* add dialog to the stack */
 521     top_dlg = g_list_prepend (top_dlg, h);
 522 
 523     /* Initialize dialog manager and widgets */
 524     if (widget_get_state (wh, WST_CONSTRUCT))
 525     {
 526         if (!widget_get_state (wh, WST_MODAL))
 527             dialog_switch_add (h);
 528 
 529         send_message (h, NULL, MSG_INIT, 0, NULL);
 530         group_default_callback (wh, NULL, MSG_INIT, 0, NULL);
 531         dlg_read_history (h);
 532     }
 533 
 534     /* Select the first widget that takes focus */
 535     while (g->current != NULL && !widget_is_focusable (g->current->data))
 536         group_set_current_widget_next (g);
 537 
 538     widget_set_state (wh, WST_ACTIVE, TRUE);
 539     /* draw dialog and focus found widget */
 540     widget_set_state (wh, WST_FOCUSED, TRUE);
 541 
 542     h->ret_value = 0;
 543 }
 544 
 545 /* --------------------------------------------------------------------------------------------- */
 546 
 547 void
 548 dlg_process_event (WDialog * h, int key, Gpm_Event * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 549 {
 550     switch (key)
 551     {
 552     case EV_NONE:
 553         if (tty_got_interrupt ())
 554             dlg_execute_cmd (h, CK_Cancel);
 555         break;
 556 
 557     case EV_MOUSE:
 558         {
 559             Widget *w = WIDGET (h);
 560 
 561             GROUP (h)->mouse_status = w->mouse_handler (w, event);
 562             break;
 563         }
 564 
 565     default:
 566         dlg_key_event (h, key);
 567         break;
 568     }
 569 }
 570 
 571 /* --------------------------------------------------------------------------------------------- */
 572 /** Shutdown the dlg_run */
 573 
 574 void
 575 dlg_run_done (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 576 {
 577     top_dlg = g_list_remove (top_dlg, h);
 578 
 579     if (widget_get_state (WIDGET (h), WST_CLOSED))
 580     {
 581         send_message (h, GROUP (h)->current == NULL ? NULL : WIDGET (GROUP (h)->current->data),
 582                       MSG_END, 0, NULL);
 583         if (!widget_get_state (WIDGET (h), WST_MODAL))
 584             dialog_switch_remove (h);
 585     }
 586 }
 587 
 588 /* --------------------------------------------------------------------------------------------- */
 589 /**
 590  * Standard run dialog routine
 591  * We have to keep this routine small so that we can duplicate it's
 592  * behavior on complex routines like the file routines, this way,
 593  * they can call the dlg_process_event without rewriting all the code
 594  */
 595 
 596 int
 597 dlg_run (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 598 {
 599     dlg_init (h);
 600     frontend_dlg_run (h);
 601     dlg_run_done (h);
 602     return h->ret_value;
 603 }
 604 
 605 /* --------------------------------------------------------------------------------------------- */
 606 
 607 /**
 608   * Write history to the ${XDG_CACHE_HOME}/mc/history file
 609   */
 610 void
 611 dlg_save_history (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 612 {
 613     char *profile;
 614     int i;
 615 
 616     if (num_history_items_recorded == 0)        /* this is how to disable */
 617         return;
 618 
 619     profile = mc_config_get_full_path (MC_HISTORY_FILE);
 620     i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
 621     if (i != -1)
 622         close (i);
 623 
 624     /* Make sure the history is only readable by the user */
 625     if (chmod (profile, S_IRUSR | S_IWUSR) != -1 || errno == ENOENT)
 626     {
 627         ev_history_load_save_t event_data;
 628 
 629         event_data.cfg = mc_config_init (profile, FALSE);
 630         event_data.receiver = NULL;
 631 
 632         /* get all histories in dialog */
 633         mc_event_raise (h->event_group, MCEVENT_HISTORY_SAVE, &event_data);
 634 
 635         mc_config_save_file (event_data.cfg, NULL);
 636         mc_config_deinit (event_data.cfg);
 637     }
 638 
 639     g_free (profile);
 640 }
 641 
 642 /* --------------------------------------------------------------------------------------------- */
 643 
 644 char *
 645 dlg_get_title (const WDialog * h, size_t len)
     /* [previous][next][first][last][top][bottom][index][help]  */
 646 {
 647     char *t;
 648 
 649     if (h == NULL)
 650         abort ();
 651 
 652     if (h->get_title != NULL)
 653         t = h->get_title (h, len);
 654     else
 655         t = g_strdup ("");
 656 
 657     return t;
 658 }
 659 
 660 /* --------------------------------------------------------------------------------------------- */

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