1f3f8ce04109c27117ba25c6314f4da531391992
[geeqie.git] / src / ui_tree_edit.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 - 2012 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #ifdef HAVE_CONFIG_H
14 #  include "config.h"
15 #endif
16 #include "intl.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include <gtk/gtk.h>
23 #include <gdk/gdkkeysyms.h>
24
25 #include "ui_tree_edit.h"
26
27 /*
28  *-------------------------------------------------------------------
29  * cell popup editor
30  *-------------------------------------------------------------------
31  */
32
33 static void tree_edit_close(TreeEditData *ted)
34 {
35         gtk_grab_remove(ted->window);
36         gdk_keyboard_ungrab(GDK_CURRENT_TIME);
37         gdk_pointer_ungrab(GDK_CURRENT_TIME);
38
39         gtk_widget_destroy(ted->window);
40
41         g_free(ted->old_name);
42         g_free(ted->new_name);
43         gtk_tree_path_free(ted->path);
44
45         g_free(ted);
46 }
47
48 static void tree_edit_do(TreeEditData *ted)
49 {
50         ted->new_name = g_strdup(gtk_entry_get_text(GTK_ENTRY(ted->entry)));
51
52         if (strcmp(ted->new_name, ted->old_name) != 0)
53                 {
54                 if (ted->edit_func)
55                         {
56                         if (ted->edit_func(ted, ted->old_name, ted->new_name, ted->edit_data))
57                                 {
58                                 /* hmm, should the caller be required to set text instead ? */
59                                 }
60                         }
61                 }
62 }
63
64 static gboolean tree_edit_click_end_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
65 {
66         TreeEditData *ted = data;
67
68         tree_edit_do(ted);
69         tree_edit_close(ted);
70
71         return TRUE;
72 }
73
74 static gboolean tree_edit_click_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
75 {
76         TreeEditData *ted = data;
77
78         gint x, y;
79         gint w, h;
80
81         gint xr, yr;
82
83         xr = (gint)event->x_root;
84         yr = (gint)event->y_root;
85
86         gdk_window_get_origin(ted->window->window, &x, &y);
87         gdk_drawable_get_size(ted->window->window, &w, &h);
88
89         if (xr < x || yr < y || xr > x + w || yr > y + h)
90                 {
91                 /* gobble the release event, so it does not propgate to an underlying widget */
92                 g_signal_connect(G_OBJECT(ted->window), "button_release_event",
93                                  G_CALLBACK(tree_edit_click_end_cb), ted);
94                 return TRUE;
95                 }
96         return FALSE;
97 }
98
99 static gboolean tree_edit_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
100 {
101         TreeEditData *ted = data;
102
103         switch (event->keyval)
104                 {
105                 case GDK_Return:
106                 case GDK_KP_Enter:
107                 case GDK_Tab:           /* ok, we are going to intercept the focus change
108                                            from keyboard and act like return was hit */
109                 case GDK_ISO_Left_Tab:
110                 case GDK_Up:
111                 case GDK_Down:
112                 case GDK_KP_Up:
113                 case GDK_KP_Down:
114                 case GDK_KP_Left:
115                 case GDK_KP_Right:
116                         tree_edit_do(ted);
117                         tree_edit_close(ted);
118                         break;
119                 case GDK_Escape:
120                         tree_edit_close(ted);
121                         break;
122                 default:
123                         break;
124                 }
125
126         return FALSE;
127 }
128
129 static gboolean tree_edit_by_path_idle_cb(gpointer data)
130 {
131         TreeEditData *ted = data;
132         GdkRectangle rect;
133         gint x, y, w, h;        /* geometry of cell within tree */
134         gint wx, wy;            /* geometry of tree from root window */
135         gint sx, sw;
136
137         gtk_tree_view_get_cell_area(ted->tree, ted->path, ted->column, &rect);
138
139         x = rect.x;
140         y = rect.y;
141         w = rect.width + 4;
142         h = rect.height + 4;
143
144         if (gtk_tree_view_column_cell_get_position(ted->column, ted->cell, &sx, &sw))
145                 {
146                 x += sx;
147                 w = MAX(w - sx, sw);
148                 }
149
150         gdk_window_get_origin(gtk_tree_view_get_bin_window(ted->tree), &wx, &wy);
151
152         x += wx - 2; /* the -val is to 'fix' alignment of entry position */
153         y += wy - 2;
154
155         /* now show it */
156         gtk_widget_set_size_request(ted->window, w, h);
157         gtk_widget_realize(ted->window);
158         gtk_window_move(GTK_WINDOW(ted->window), x, y);
159         gtk_window_resize(GTK_WINDOW(ted->window), w, h);
160         gtk_widget_show(ted->window);
161
162         /* grab it */
163         gtk_widget_grab_focus(ted->entry);
164         /* explicitely set the focus flag for the entry, for some reason on popup windows this
165          * is not set, and causes no edit cursor to appear ( popups not allowed focus? )
166          */
167         GTK_WIDGET_SET_FLAGS(ted->entry, GTK_HAS_FOCUS);
168         gtk_grab_add(ted->window);
169         gdk_pointer_grab(ted->window->window, TRUE,
170                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK,
171                          NULL, NULL, GDK_CURRENT_TIME);
172         gdk_keyboard_grab(ted->window->window, TRUE, GDK_CURRENT_TIME);
173
174         return FALSE;
175 }
176
177 gboolean tree_edit_by_path(GtkTreeView *tree, GtkTreePath *tpath, gint column, const gchar *text,
178                            gboolean (*edit_func)(TreeEditData *, const gchar *, const gchar *, gpointer), gpointer data)
179 {
180         TreeEditData *ted;
181         GtkTreeViewColumn *tcolumn;
182         GtkCellRenderer *cell = NULL;
183         GList *list;
184         GList *work;
185
186         if (!edit_func) return FALSE;
187 #if GTK_CHECK_VERSION(2,20,0)
188         if (!gtk_widget_get_visible(tree)) return FALSE;
189 #else
190         if (!GTK_WIDGET_VISIBLE(tree)) return FALSE;
191 #endif
192
193         tcolumn = gtk_tree_view_get_column(tree, column);
194         if (!tcolumn) return FALSE;
195
196 #if GTK_CHECK_VERSION(2,18,0)
197         list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(tcolumn));
198 #else
199         list = gtk_tree_view_column_get_cell_renderers(tcolumn);
200 #endif
201         work = list;
202         while (work && !cell)
203                 {
204                 cell = work->data;
205                 if (!GTK_IS_CELL_RENDERER_TEXT(cell))
206                         {
207                         cell = NULL;
208                         }
209                 work = work->next;
210                 }
211
212         g_list_free(list);
213         if (!cell) return FALSE;
214
215         if (!text) text = "";
216
217         ted = g_new0(TreeEditData, 1);
218
219         ted->old_name = g_strdup(text);
220
221         ted->edit_func = edit_func;
222         ted->edit_data = data;
223
224         ted->tree = tree;
225         ted->path = gtk_tree_path_copy(tpath);
226         ted->column = tcolumn;
227         ted->cell = cell;
228
229         gtk_tree_view_scroll_to_cell(ted->tree, ted->path, ted->column, FALSE, 0.0, 0.0);
230
231         /* create the window */
232
233         ted->window = gtk_window_new(GTK_WINDOW_POPUP);
234         gtk_window_set_resizable(GTK_WINDOW(ted->window), FALSE);
235         g_signal_connect(G_OBJECT(ted->window), "button_press_event",
236                          G_CALLBACK(tree_edit_click_cb), ted);
237         g_signal_connect(G_OBJECT(ted->window), "key_press_event",
238                          G_CALLBACK(tree_edit_key_press_cb), ted);
239
240         ted->entry = gtk_entry_new();
241         gtk_entry_set_text(GTK_ENTRY(ted->entry), ted->old_name);
242         gtk_editable_select_region(GTK_EDITABLE(ted->entry), 0, strlen(ted->old_name));
243         gtk_container_add(GTK_CONTAINER(ted->window), ted->entry);
244         gtk_widget_show(ted->entry);
245
246         /* due to the fact that gtktreeview scrolls in an idle loop, we cannot
247          * reliably get the cell position until those scroll priority signals are processed
248          */
249         g_idle_add_full(G_PRIORITY_DEFAULT_IDLE - 2, tree_edit_by_path_idle_cb, ted, NULL);
250
251         return TRUE;
252 }
253
254 /*
255  *-------------------------------------------------------------------
256  * tree cell position retrieval
257  *-------------------------------------------------------------------
258  */
259
260 gboolean tree_view_get_cell_origin(GtkTreeView *widget, GtkTreePath *tpath, gint column, gboolean text_cell_only,
261                                    gint *x, gint *y, gint *width, gint *height)
262 {
263         gint x_origin, y_origin;
264         gint x_offset, y_offset;
265         gint header_size;
266         GtkTreeViewColumn *tv_column;
267         GdkRectangle rect;
268
269         tv_column = gtk_tree_view_get_column(widget, column);
270         if (!tv_column || !tpath) return FALSE;
271
272         /* hmm, appears the rect will not account for X scroll, but does for Y scroll
273          * use x_offset instead for X scroll (sigh)
274          */
275         gtk_tree_view_get_cell_area(widget, tpath, tv_column, &rect);
276 #if GTK_CHECK_VERSION(2,12,0)
277         gtk_tree_view_convert_tree_to_widget_coords(widget, 0, 0, &x_offset, &y_offset);
278 #else
279         gtk_tree_view_tree_to_widget_coords(widget, 0, 0, &x_offset, &y_offset);
280 #endif
281         gdk_window_get_origin(GTK_WIDGET(widget)->window, &x_origin, &y_origin);
282
283         if (gtk_tree_view_get_headers_visible(widget))
284                 {
285                 header_size = tv_column->button->allocation.height;
286                 }
287         else
288                 {
289                 header_size = 0;
290                 }
291
292         if (text_cell_only)
293                 {
294                 GtkCellRenderer *cell = NULL;
295                 GList *renderers;
296                 GList *work;
297                 gint cell_x;
298                 gint cell_width;
299
300 #if GTK_CHECK_VERSION(2,18,0)
301                 renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(tv_column));
302 #else
303                 renderers = gtk_tree_view_column_get_cell_renderers(tv_column);
304 #endif
305                 work = renderers;
306                 while (work && !cell)
307                         {
308                         cell = work->data;
309                         work = work->next;
310                         if (!GTK_IS_CELL_RENDERER_TEXT(cell)) cell = NULL;
311                         }
312                 g_list_free(renderers);
313
314                 if (!cell) return FALSE;
315
316                 if (!gtk_tree_view_column_cell_get_position(tv_column, cell, &cell_x, &cell_width))
317                         {
318                         cell_x = 0;
319                         cell_width = rect.width;
320                         }
321                 *x = x_origin + x_offset + rect.x + cell_x;
322                 *width = cell_width;
323                 }
324         else
325                 {
326                 *x = x_origin + x_offset + rect.x;
327                 *width = rect.width;
328                 }
329         *y = y_origin + rect.y + header_size;
330         *height = rect.height;
331         return TRUE;
332 }
333
334 void tree_view_get_cell_clamped(GtkTreeView *widget, GtkTreePath *tpath, gint column, gboolean text_cell_only,
335                                 gint *x, gint *y, gint *width, gint *height)
336 {
337         gint wx, wy, ww, wh;
338         GdkWindow *window;
339
340         window = GTK_WIDGET(widget)->window;
341         gdk_window_get_origin(window, &wx, &wy);
342         gdk_drawable_get_size(window, &ww, &wh);
343         if (!tree_view_get_cell_origin(widget, tpath, column, text_cell_only, x,  y, width, height))
344                 {
345                 *x = wx;
346                 *y = wy;
347                 *width = ww;
348                 *height = wh;
349                 return;
350                 }
351
352         *width = MIN(*width, ww);
353         *x = CLAMP(*x, wx, wx + ww - (*width));
354         *y = CLAMP(*y, wy, wy + wh);
355         *height = MIN(*height, wy + wh - (*y));
356 }
357
358 #if GTK_CHECK_VERSION(2,8,0)
359 /* an implementation that uses gtk_tree_view_get_visible_range */
360 gint tree_view_row_get_visibility(GtkTreeView *widget, GtkTreeIter *iter, gboolean fully_visible)
361 {
362         GtkTreeModel *store;
363         GtkTreePath *tpath, *start_path, *end_path;
364         gint ret = 0;
365
366         if (!gtk_tree_view_get_visible_range(widget, &start_path, &end_path)) return -1; /* we will most probably scroll down, needed for tree_view_row_make_visible */
367
368         store = gtk_tree_view_get_model(widget);
369         tpath = gtk_tree_model_get_path(store, iter);
370
371         if (fully_visible)
372                 {
373                 if (gtk_tree_path_compare(tpath, start_path) <= 0)
374                         {
375                         ret = -1;
376                         }
377                 else if (gtk_tree_path_compare(tpath, end_path) >= 0)
378                         {
379                         ret = 1;
380                         }
381                 }
382         else
383                 {
384                 if (gtk_tree_path_compare(tpath, start_path) < 0)
385                         {
386                         ret = -1;
387                         }
388                 else if (gtk_tree_path_compare(tpath, end_path) > 0)
389                         {
390                         ret = 1;
391                         }
392                 }
393
394         gtk_tree_path_free(tpath);
395         gtk_tree_path_free(start_path);
396         gtk_tree_path_free(end_path);
397         return ret;
398 }
399
400 #else 
401 /* an implementation that uses gtk_tree_view_get_visible_rect, it seems to be more error prone than the variant above */
402
403 gint tree_view_row_get_visibility(GtkTreeView *widget, GtkTreeIter *iter, gboolean fully_visible)
404 {
405         GtkTreeModel *store;
406         GtkTreePath *tpath;
407         gint cx, cy;
408
409         GdkRectangle vrect;
410         GdkRectangle crect;
411
412         if (!GTK_WIDGET_REALIZED(GTK_WIDGET(widget))) return -1; /* we will most probably scroll down, needed for tree_view_row_make_visible */
413
414         store = gtk_tree_view_get_model(widget);
415         tpath = gtk_tree_model_get_path(store, iter);
416
417         gtk_tree_view_get_visible_rect(widget, &vrect);
418         gtk_tree_view_get_cell_area(widget, tpath, NULL, &crect);
419         gtk_tree_path_free(tpath);
420
421
422 #if GTK_CHECK_VERSION(2,12,0)
423         gtk_tree_view_convert_widget_to_tree_coords(widget, crect.x, crect.y, &cx, &cy);
424 #else
425         gtk_tree_view_widget_to_tree_coords(widget, crect.x, crect.y, &cx, &cy);
426 #endif
427
428         if (fully_visible)
429                 {
430                 if (cy < vrect.y) return -1;
431                 if (cy + crect.height > vrect.y + vrect.height) return 1;
432                 return 0;
433                 }
434
435         if (cy + crect.height < vrect.y) return -1;
436         if (cy > vrect.y + vrect.height) return 1;
437         return 0;
438 }
439 #endif
440
441 gint tree_view_row_make_visible(GtkTreeView *widget, GtkTreeIter *iter, gboolean center)
442 {
443         GtkTreePath *tpath;
444         gint vis;
445
446         vis = tree_view_row_get_visibility(widget, iter, TRUE);
447
448         tpath = gtk_tree_model_get_path(gtk_tree_view_get_model(widget), iter);
449         if (center && vis != 0)
450                 {
451                 gtk_tree_view_scroll_to_cell(widget, tpath, NULL, TRUE, 0.5, 0.0);
452                 }
453         else if (vis < 0)
454                 {
455                 gtk_tree_view_scroll_to_cell(widget, tpath, NULL, TRUE, 0.0, 0.0);
456                 }
457         else if (vis > 0)
458                 {
459                 gtk_tree_view_scroll_to_cell(widget, tpath, NULL, TRUE, 1.0, 0.0);
460                 }
461         gtk_tree_path_free(tpath);
462
463         return vis;
464 }
465
466 gboolean tree_view_move_cursor_away(GtkTreeView *widget, GtkTreeIter *iter, gboolean only_selected)
467 {
468         GtkTreeModel *store;
469         GtkTreePath *tpath;
470         GtkTreePath *fpath;
471         gboolean move = FALSE;
472
473         if (!iter) return FALSE;
474
475         store = gtk_tree_view_get_model(widget);
476         tpath = gtk_tree_model_get_path(store, iter);
477         gtk_tree_view_get_cursor(widget, &fpath, NULL);
478
479         if (fpath && gtk_tree_path_compare(tpath, fpath) == 0)
480                 {
481                 GtkTreeSelection *selection;
482
483                 selection = gtk_tree_view_get_selection(widget);
484
485                 if (!only_selected ||
486                     gtk_tree_selection_path_is_selected(selection, tpath))
487                         {
488                         GtkTreeIter current;
489
490                         current = *iter;
491                         if (gtk_tree_model_iter_next(store, &current))
492                                 {
493                                 gtk_tree_path_next(tpath);
494                                 move = TRUE;
495                                 }
496                         else if (gtk_tree_path_prev(tpath) &&
497                                  gtk_tree_model_get_iter(store, &current, tpath))
498                                 {
499                                 move = TRUE;
500                                 }
501
502                         if (move)
503                                 {
504                                 gtk_tree_view_set_cursor(widget, tpath, NULL, FALSE);
505                                 }
506                         }
507                 }
508
509         gtk_tree_path_free(tpath);
510         if (fpath) gtk_tree_path_free(fpath);
511
512         return move;
513 }
514
515 gint tree_path_to_row(GtkTreePath *tpath)
516 {
517         gint *indices;
518
519         indices = gtk_tree_path_get_indices(tpath);
520         if (indices) return indices[0];
521
522         return -1;
523 }
524
525
526 /*
527  *-------------------------------------------------------------------
528  * color utilities
529  *-------------------------------------------------------------------
530  */
531
532 void shift_color(GdkColor *src, gshort val, gint direction)
533 {
534         gshort cs;
535
536         if (val == -1)
537                 {
538                 val = STYLE_SHIFT_STANDARD;
539                 }
540         else
541                 {
542                 val = CLAMP(val, 1, 100);
543                 }
544         cs = 0xffff / 100 * val;
545
546         /* up or down ? */
547         if (direction < 0 ||
548             (direction == 0 &&((gint)src->red + (gint)src->green + (gint)src->blue) / 3 > 0xffff / 2))
549                 {
550                 src->red = MAX(0 , src->red - cs);
551                 src->green = MAX(0 , src->green - cs);
552                 src->blue = MAX(0 , src->blue - cs);
553                 }
554         else
555                 {
556                 src->red = MIN(0xffff, src->red + cs);
557                 src->green = MIN(0xffff, src->green + cs);
558                 src->blue = MIN(0xffff, src->blue + cs);
559                 }
560 }
561
562 /* darkens or lightens a style's color for given state
563  * esp. useful for alternating dark/light in (c)lists
564  */
565 void style_shift_color(GtkStyle *style, GtkStateType type, gshort shift_value, gint direction)
566 {
567         if (!style) return;
568
569         shift_color(&style->base[type], shift_value, direction);
570         shift_color(&style->bg[type], shift_value, direction);
571 }
572
573 /*
574  *-------------------------------------------------------------------
575  * auto scroll by mouse position
576  *-------------------------------------------------------------------
577  */
578
579 #define AUTO_SCROLL_DEFAULT_SPEED 100
580 #define AUTO_SCROLL_DEFAULT_REGION 20
581
582 typedef struct _AutoScrollData AutoScrollData;
583 struct _AutoScrollData
584 {
585         guint timer_id; /* event source id */
586         gint region_size;
587         GtkWidget *widget;
588         GtkAdjustment *adj;
589         gint max_step;
590
591         gint (*notify_func)(GtkWidget *, gint, gint, gpointer);
592         gpointer notify_data;
593 };
594
595 void widget_auto_scroll_stop(GtkWidget *widget)
596 {
597         AutoScrollData *sd;
598
599         sd = g_object_get_data(G_OBJECT(widget), "autoscroll");
600         if (!sd) return;
601         g_object_set_data(G_OBJECT(widget), "autoscroll", NULL);
602
603         if (sd->timer_id) g_source_remove(sd->timer_id);
604         g_free(sd);
605 }
606
607 static gboolean widget_auto_scroll_cb(gpointer data)
608 {
609         AutoScrollData *sd = data;
610         GdkWindow *window;
611         gint x, y;
612         gint w, h;
613         gint amt = 0;
614
615         if (sd->max_step < sd->region_size)
616                 {
617                 sd->max_step = MIN(sd->region_size, sd->max_step + 2);
618                 }
619
620         window = sd->widget->window;
621         gdk_window_get_pointer(window, &x, &y, NULL);
622         gdk_drawable_get_size(window, &w, &h);
623
624         if (x < 0 || x >= w || y < 0 || y >= h)
625                 {
626                 sd->timer_id = 0;
627                 widget_auto_scroll_stop(sd->widget);
628                 return FALSE;
629                 }
630
631         if (h < sd->region_size * 3)
632                 {
633                 /* height is cramped, nicely divide into three equal regions */
634                 if (y < h / 3 || y > h / 3 * 2)
635                         {
636                         amt = (y < h / 2) ? 0 - ((h / 2) - y) : y - (h / 2);
637                         }
638                 }
639         else if (y < sd->region_size)
640                 {
641                 amt = 0 - (sd->region_size - y);
642                 }
643         else if (y >= h - sd->region_size)
644                 {
645                 amt = y - (h - sd->region_size);
646                 }
647
648         if (amt != 0)
649                 {
650                 amt = CLAMP(amt, 0 - sd->max_step, sd->max_step);
651
652                 if (sd->adj->value != CLAMP(sd->adj->value + amt, sd->adj->lower, sd->adj->upper - sd->adj->page_size))
653                         {
654                         /* only notify when scrolling is needed */
655                         if (sd->notify_func && !sd->notify_func(sd->widget, x, y, sd->notify_data))
656                                 {
657                                 sd->timer_id = 0;
658                                 widget_auto_scroll_stop(sd->widget);
659                                 return FALSE;
660                                 }
661
662                         gtk_adjustment_set_value(sd->adj,
663                                 CLAMP(sd->adj->value + amt, sd->adj->lower, sd->adj->upper - sd->adj->page_size));
664                         }
665                 }
666
667         return TRUE;
668 }
669
670 gint widget_auto_scroll_start(GtkWidget *widget, GtkAdjustment *v_adj, gint scroll_speed, gint region_size,
671                               gint (*notify_func)(GtkWidget *widget, gint x, gint y, gpointer data), gpointer notify_data)
672 {
673         AutoScrollData *sd;
674
675         if (!widget || !v_adj) return 0;
676         if (g_object_get_data(G_OBJECT(widget), "autoscroll")) return 0;
677         if (scroll_speed < 1) scroll_speed = AUTO_SCROLL_DEFAULT_SPEED;
678         if (region_size < 1) region_size = AUTO_SCROLL_DEFAULT_REGION;
679
680         sd = g_new0(AutoScrollData, 1);
681         sd->widget = widget;
682         sd->adj = v_adj;
683         sd->region_size = region_size;
684         sd->max_step = 1;
685         sd->timer_id = g_timeout_add(scroll_speed, widget_auto_scroll_cb, sd);
686
687         sd->notify_func = notify_func;
688         sd->notify_data = notify_data;
689
690         g_object_set_data(G_OBJECT(widget), "autoscroll", sd);
691
692         return scroll_speed;
693 }
694
695
696 /*
697  *-------------------------------------------------------------------
698  * GList utils
699  *-------------------------------------------------------------------
700  */
701
702 GList *uig_list_insert_link(GList *list, GList *link, gpointer data)
703 {
704         GList *new_list;
705
706         if (!list || link == list) return g_list_prepend(list, data);
707         if (!link) return g_list_append(list, data);
708
709         new_list = g_list_alloc();
710         new_list->data = data;
711
712         if (link->prev)
713                 {
714                 link->prev->next = new_list;
715                 new_list->prev = link->prev;
716                 }
717         else
718                 {
719                 list = new_list;
720                 }
721         link->prev = new_list;
722         new_list->next = link;
723
724         return list;
725 }
726
727 GList *uig_list_insert_list(GList *parent, GList *insert_link, GList *list)
728 {
729         GList *end;
730
731         if (!insert_link) return g_list_concat(parent, list);
732         if (insert_link == parent) return g_list_concat(list, parent);
733         if (!parent) return list;
734         if (!list) return parent;
735
736         end  = g_list_last(list);
737
738         if (insert_link->prev) insert_link->prev->next = list;
739         list->prev = insert_link->prev;
740         insert_link->prev = end;
741         end->next = insert_link;
742
743         return parent;
744 }
745 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */