comparison src/ui_tree_edit.c @ 9:d907d608745f

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