1 /*
2  *      desktop.c
3  *
4  *      Copyright 2010 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5  *      Copyright 2012-2017 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
6  *
7  *      This program is free software; you can redistribute it and/or modify
8  *      it under the terms of the GNU General Public License as published by
9  *      the Free Software Foundation; either version 2 of the License, or
10  *      (at your option) any later version.
11  *
12  *      This program is distributed in the hope that it will be useful,
13  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *      GNU General Public License for more details.
16  *
17  *      You should have received a copy of the GNU General Public License
18  *      along with this program; if not, write to the Free Software
19  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  *      MA 02110-1301, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include "desktop.h"
28 #include "pcmanfm.h"
29 
30 #include <glib/gi18n.h>
31 
32 #include <gdk/gdkx.h>
33 #include <gdk/gdkkeysyms.h>
34 #include <X11/Xlib.h>
35 #include <X11/Xatom.h>
36 #include <math.h>
37 
38 #include <cairo-xlib.h>
39 
40 #include "pref.h"
41 #include "main-win.h"
42 
43 #include "gseal-gtk-compat.h"
44 
45 /* compatibility with LibFM < 1.0.2 */
46 #if !FM_CHECK_VERSION(1, 0, 2)
47 # define FM_FOLDER_MODEL_COL_INFO COL_FILE_INFO
48 # define FM_FOLDER_MODEL_COL_ICON COL_FILE_ICON
49 #endif
50 
51 #define SPACING 2
52 #define PADDING 6
53 #define MARGIN  2
54 
55 /* the search dialog timeout (in ms) */
56 #define DESKTOP_SEARCH_DIALOG_TIMEOUT (5000)
57 
58 struct _FmDesktopItem
59 {
60     FmFileInfo* fi;
61     GdkRectangle area; /* position of the item on the desktop */
62     GdkRectangle icon_rect;
63     GdkRectangle text_rect;
64     gboolean is_special : 1; /* is this a special item like "My Computer", mounted volume, or "Trash" */
65     gboolean is_mount : 1; /* is this a mounted volume*/
66     gboolean is_selected : 1;
67     gboolean is_rubber_banded : 1;
68     gboolean is_prelight : 1;
69     gboolean fixed_pos : 1;
70 };
71 
72 struct _FmBackgroundCache
73 {
74     FmBackgroundCache *next;
75     char *filename;
76 #if GTK_CHECK_VERSION(3, 0, 0)
77     cairo_surface_t *bg;
78 #else
79     GdkPixmap *bg;
80 #endif
81     FmWallpaperMode wallpaper_mode;
82     time_t mtime;
83 };
84 
85 static void queue_layout_items(FmDesktop* desktop);
86 static void redraw_item(FmDesktop* desktop, FmDesktopItem* item);
87 
88 static FmFileInfoList* _dup_selected_files(FmFolderView* fv);
89 static FmPathList* _dup_selected_file_paths(FmFolderView* fv);
90 static void _select_all(FmFolderView* fv);
91 static void _unselect_all(FmFolderView* fv);
92 
93 static FmDesktopItem* hit_test(FmDesktop* self, GtkTreeIter *it, int x, int y);
94 
95 static void fm_desktop_view_init(FmFolderViewInterface* iface);
96 
97 G_DEFINE_TYPE_WITH_CODE(FmDesktop, fm_desktop, GTK_TYPE_WINDOW,
98                         G_IMPLEMENT_INTERFACE(FM_TYPE_FOLDER_VIEW, fm_desktop_view_init))
99 
100 static GtkWindowGroup* win_group = NULL;
101 static FmDesktop **desktops = NULL;
102 static int n_screens = 0;
103 static guint icon_theme_changed = 0;
104 static GtkAccelGroup* acc_grp = NULL;
105 
106 static Atom XA_NET_WORKAREA = 0;
107 static Atom XA_NET_NUMBER_OF_DESKTOPS = 0;
108 static Atom XA_NET_CURRENT_DESKTOP = 0;
109 static Atom XA_XROOTMAP_ID = 0;
110 static Atom XA_XROOTPMAP_ID = 0;
111 
112 static GdkCursor* hand_cursor = NULL;
113 
114 static guint idle_config_save = 0;
115 
116 enum {
117 #if N_FM_DND_DEST_DEFAULT_TARGETS > N_FM_DND_SRC_DEFAULT_TARGETS
118     FM_DND_DEST_DESKTOP_ITEM = N_FM_DND_DEST_DEFAULT_TARGETS
119 #else
120     FM_DND_DEST_DESKTOP_ITEM = N_FM_DND_SRC_DEFAULT_TARGETS
121 #endif
122 };
123 
124 static GtkTargetEntry dnd_targets[] =
125 {
126     {"application/x-desktop-item", GTK_TARGET_SAME_WIDGET, FM_DND_DEST_DESKTOP_ITEM}
127 };
128 
129 static GdkAtom desktop_atom;
130 
131 enum
132 {
133     PROP_0,
134     PROP_MONITOR,
135     N_PROPERTIES
136 };
137 
138 /* popup menu callbacks */
139 static void on_open_in_new_tab(GtkAction* act, gpointer user_data);
140 static void on_open_in_new_win(GtkAction* act, gpointer user_data);
141 static void on_open_folder_in_terminal(GtkAction* act, gpointer user_data);
142 
143 static void on_fix_pos(GtkToggleAction* act, gpointer user_data);
144 static void on_snap_to_grid(GtkAction* act, gpointer user_data);
145 
146 #if FM_CHECK_VERSION(1, 2, 0)
147 static void on_disable(GtkAction* act, gpointer user_data);
148 #endif
149 
150 /* insert GtkUIManager XML definitions */
151 #include "desktop-ui.c"
152 
153 #if FM_CHECK_VERSION(1, 2, 0)
154 /* ---------------------------------------------------------------------
155     mounts handlers */
156 
157 #if FM_CHECK_VERSION(1, 2, 0)
158 typedef struct
159 {
160     GMount *mount; /* NULL for non-mounts */
161     FmPath *path;
162     FmFileInfo *fi;
163     FmFileInfoJob *job;
164 } FmDesktopExtraItem;
165 
166 static FmDesktopExtraItem *documents = NULL;
167 //static FmDesktopExtraItem *computer = NULL;
168 static FmDesktopExtraItem *trash_can = NULL;
169 //static FmDesktopExtraItem *applications = NULL;
170 
171 /* under GDK lock */
172 static GSList *mounts = NULL;
173 #endif
174 
175 
176 /* ---------------------------------------------------------------------
177     Items management and common functions */
178 
get_config_file(FmDesktop * desktop,gboolean create_dir)179 static char* get_config_file(FmDesktop* desktop, gboolean create_dir)
180 {
181     char *dir, *path;
182     int i;
183 
184     for(i = 0; i < n_screens; i++)
185         if(desktops[i] == desktop)
186             break;
187     if(i >= n_screens)
188         return NULL;
189     dir = pcmanfm_get_profile_dir(create_dir);
190     path = g_strdup_printf("%s/desktop-items-%u.conf", dir, i);
191     g_free(dir);
192     return path;
193 }
194 
desktop_item_new(FmFolderModel * model,GtkTreeIter * it)195 static inline FmDesktopItem* desktop_item_new(FmFolderModel* model, GtkTreeIter* it)
196 {
197     FmDesktopItem* item = g_slice_new0(FmDesktopItem);
198 #if FM_CHECK_VERSION(1, 2, 0)
199     GSList *sl;
200 #endif
201     fm_folder_model_set_item_userdata(model, it, item);
202     gtk_tree_model_get(GTK_TREE_MODEL(model), it, FM_FOLDER_MODEL_COL_INFO, &item->fi, -1);
203     fm_file_info_ref(item->fi);
204 #if FM_CHECK_VERSION(1, 2, 0)
205     if ((trash_can && trash_can->fi == item->fi) ||
206         (documents && documents->fi == item->fi))
207         item->is_special = TRUE;
208     else for (sl = mounts; sl; sl = sl->next)
209         if (((FmDesktopExtraItem *)sl->data)->fi == item->fi)
210         {
211             item->is_special = TRUE;
212             item->is_mount = TRUE;
213             break;
214         }
215 #endif
216     return item;
217 }
218 
desktop_item_free(FmDesktopItem * item)219 static inline void desktop_item_free(FmDesktopItem* item)
220 {
221     if(item->fi)
222         fm_file_info_unref(item->fi);
223     g_slice_free(FmDesktopItem, item);
224 }
225 
calc_item_size(FmDesktop * desktop,FmDesktopItem * item,GdkPixbuf * icon)226 static void calc_item_size(FmDesktop* desktop, FmDesktopItem* item, GdkPixbuf* icon)
227 {
228     PangoRectangle rc2;
229 
230     /* icon rect */
231     if(icon)
232     {
233         item->icon_rect.width = gdk_pixbuf_get_width(icon);
234         item->icon_rect.height = gdk_pixbuf_get_height(icon);
235     }
236     else
237     {
238         item->icon_rect.width = fm_config->big_icon_size;
239         item->icon_rect.height = fm_config->big_icon_size;
240     }
241     item->icon_rect.x = item->area.x + (desktop->cell_w - item->icon_rect.width) / 2;
242     item->icon_rect.y = item->area.y + desktop->ypad + (fm_config->big_icon_size - item->icon_rect.height) / 2;
243     item->icon_rect.height += desktop->spacing; // FIXME: this is probably wrong
244 
245     /* text label rect */
246     pango_layout_set_text(desktop->pl, NULL, 0);
247     pango_layout_set_height(desktop->pl, desktop->pango_text_h);
248     pango_layout_set_width(desktop->pl, desktop->pango_text_w);
249     pango_layout_set_text(desktop->pl, fm_file_info_get_disp_name(item->fi), -1);
250 
251     pango_layout_get_pixel_extents(desktop->pl, NULL, &rc2);
252     pango_layout_set_text(desktop->pl, NULL, 0);
253 
254     /* FIXME: RTL */
255     item->text_rect.x = item->area.x + (desktop->cell_w - rc2.width - 4) / 2;
256     item->text_rect.y = item->area.y + desktop->ypad + fm_config->big_icon_size + desktop->spacing;
257     item->text_rect.width = rc2.width + 4;
258     item->text_rect.height = rc2.y + rc2.height + 4;
259     item->area.width = (desktop->cell_w + MAX(item->icon_rect.width, item->text_rect.width)) / 2;
260     item->area.height = item->text_rect.y + item->text_rect.height - item->area.y;
261 }
262 
263 /* unfortunately we cannot load the "*" together with items because
264    otherwise we will update pango layout on each load_items() which
265    is resource wasting so we load config file once more instead */
load_config(FmDesktop * desktop)266 static inline void load_config(FmDesktop* desktop)
267 {
268     char* path;
269     GKeyFile* kf;
270 
271     path = get_config_file(desktop, FALSE);
272     if(!path)
273         return;
274     kf = g_key_file_new();
275     if(g_key_file_load_from_file(kf, path, 0, NULL))
276         /* item "*" is desktop config */
277         fm_app_config_load_desktop_config(kf, "*", &desktop->conf);
278     g_free(path);
279     g_key_file_free(kf);
280 }
281 
load_items(FmDesktop * desktop)282 static inline void load_items(FmDesktop* desktop)
283 {
284     GtkTreeIter it;
285     char* path;
286     GtkTreeModel* model;
287     GKeyFile* kf;
288 
289     if (desktop->model == NULL)
290         return;
291     model = GTK_TREE_MODEL(desktop->model);
292     if (!gtk_tree_model_get_iter_first(model, &it))
293         return;
294     path = get_config_file(desktop, FALSE);
295     if(!path)
296         return;
297     kf = g_key_file_new();
298     if(g_key_file_load_from_file(kf, path, 0, NULL))
299     {
300         do
301         {
302             FmDesktopItem* item;
303             const char* name;
304             GdkPixbuf* icon = NULL;
305             int out; /* out of bounds */
306 
307             item = fm_folder_model_get_item_userdata(desktop->model, &it);
308             name = fm_file_info_get_name(item->fi);
309             if(g_key_file_has_group(kf, name))
310             {
311                 gtk_tree_model_get(model, &it, FM_FOLDER_MODEL_COL_ICON, &icon, -1);
312                 desktop->fixed_items = g_list_prepend(desktop->fixed_items, item);
313                 item->fixed_pos = TRUE;
314                 item->area.x = g_key_file_get_integer(kf, name, "x", NULL);
315                 item->area.y = g_key_file_get_integer(kf, name, "y", NULL);
316                 /* pull item into screen bounds */
317                 if (item->area.x < desktop->xmargin + desktop->working_area.x)
318                     item->area.x = desktop->xmargin + desktop->working_area.x;
319                 if (item->area.y < desktop->ymargin + desktop->working_area.y)
320                     item->area.y = desktop->ymargin + desktop->working_area.y;
321                 calc_item_size(desktop, item, icon);
322                 /* check if item is in screen bounds and pull it if it's not */
323                 out = item->area.x + item->area.width + desktop->xmargin - desktop->working_area.width - desktop->working_area.x;
324                 if (out > 0)
325                 {
326                     if (out > item->area.x - desktop->xmargin)
327                         out = item->area.x - desktop->xmargin;
328                     item->area.x -= out;
329                     item->icon_rect.x -= out;
330                     item->text_rect.x -= out;
331                 }
332                 out = item->area.y + item->area.height + desktop->ymargin - desktop->working_area.height - desktop->working_area.y;
333                 if (out > 0)
334                 {
335                     if (out > item->area.y - desktop->ymargin)
336                         out = item->area.y - desktop->ymargin;
337                     item->area.y -= out;
338                     item->icon_rect.y -= out;
339                     item->text_rect.y -= out;
340                 }
341                 if(icon)
342                     g_object_unref(icon);
343             }
344         }
345         while(gtk_tree_model_iter_next(model, &it));
346     }
347     g_free(path);
348     g_key_file_free(kf);
349     queue_layout_items(desktop);
350 }
351 
unload_items(FmDesktop * desktop)352 static inline void unload_items(FmDesktop* desktop)
353 {
354     /* remove existing fixed items */
355     g_list_free(desktop->fixed_items);
356     desktop->fixed_items = NULL;
357     desktop->focus = NULL;
358     desktop->drop_hilight = NULL;
359     desktop->hover_item = NULL;
360     g_object_set(G_OBJECT(desktop), "tooltip-text", NULL, NULL);
361 }
362 
reload_items(FmDesktop * desktop)363 static inline void reload_items(FmDesktop *desktop)
364 {
365     unload_items(desktop);
366     load_items(desktop);
367 }
368 
get_desktop_for_root_window(GdkWindow * root)369 static gint get_desktop_for_root_window(GdkWindow *root)
370 {
371     gint desktop = -1;
372     Atom ret_type;
373     gulong len, after;
374     int format;
375     guchar* prop;
376 
377     if(XGetWindowProperty(GDK_WINDOW_XDISPLAY(root), GDK_WINDOW_XID(root),
378                           XA_NET_CURRENT_DESKTOP, 0, 1, False, XA_CARDINAL, &ret_type,
379                           &format, &len, &after, &prop) == Success &&
380        prop != NULL)
381     {
382         desktop = (gint)*(guint32*)prop;
383         XFree(prop);
384     }
385     return desktop;
386 }
387 
388 /* save position of desktop icons */
save_item_pos(FmDesktop * desktop)389 static void save_item_pos(FmDesktop* desktop)
390 {
391     GList* l;
392     GString* buf;
393     char* path = get_config_file(desktop, TRUE);
394 
395     if(!path)
396         return;
397     buf = g_string_sized_new(1024);
398 
399     /* save desktop config */
400     if (desktop->conf.configured)
401     {
402         fm_app_config_save_desktop_config(buf, "*", &desktop->conf);
403         g_string_append_c(buf, '\n');
404     }
405 
406     /* save all items positions */
407     for(l = desktop->fixed_items; l; l=l->next)
408     {
409         FmDesktopItem* item = (FmDesktopItem*)l->data;
410         FmPath* fi_path = fm_file_info_get_path(item->fi);
411         const char* p;
412         /* write the file basename as group name */
413         g_string_append_c(buf, '[');
414         for(p = fm_path_get_basename(fi_path); *p; ++p)
415         {
416             switch(*p)
417             {
418             case '\r':
419                 g_string_append(buf, "\\r");
420                 break;
421             case '\n':
422                 g_string_append(buf, "\\n");
423                 break;
424             case '\\':
425                 g_string_append(buf, "\\\\");
426                 break;
427             case '[':
428                 g_string_append(buf, "\\[");
429                 break;
430             case ']':
431                 g_string_append(buf, "\\]");
432                 break;
433             default:
434                 g_string_append_c(buf, *p);
435             }
436         }
437         g_string_append(buf, "]\n");
438         g_string_append_printf(buf, "x=%d\n"
439                                     "y=%d\n\n",
440                                     item->area.x, item->area.y);
441     }
442     g_file_set_contents(path, buf->str, buf->len, NULL);
443     g_free(path);
444     g_string_free(buf, TRUE);
445     desktop->conf.changed = FALSE; /* reset it since we saved it */
446 }
447 
on_config_save_idle(gpointer _unused)448 static gboolean on_config_save_idle(gpointer _unused)
449 {
450     int i;
451 
452     if (g_source_is_destroyed(g_main_current_source()))
453         return FALSE;
454 
455     for (i = 0; i < n_screens; i++)
456         if (desktops[i]->conf.changed)
457             save_item_pos(desktops[i]);
458     idle_config_save = 0;
459     return FALSE;
460 }
461 
queue_config_save(FmDesktop * desktop)462 static void queue_config_save(FmDesktop *desktop)
463 {
464     desktop->conf.configured = TRUE;
465     desktop->conf.changed = TRUE;
466     if (idle_config_save == 0)
467         idle_config_save = gdk_threads_add_idle(on_config_save_idle, NULL);
468 }
469 
get_selected_items(FmDesktop * desktop,int * n_items)470 static GList* get_selected_items(FmDesktop* desktop, int* n_items)
471 {
472     GList* items = NULL;
473     int n = 0;
474     FmDesktopItem* focus = NULL;
475     GtkTreeModel* model = desktop->model ? GTK_TREE_MODEL(desktop->model) : NULL;
476     GtkTreeIter it;
477     if(model && gtk_tree_model_get_iter_first(model, &it)) do
478     {
479         FmDesktopItem* item = fm_folder_model_get_item_userdata(desktop->model, &it);
480         if(item->is_selected)
481         {
482             if(G_LIKELY(item != desktop->focus))
483             {
484                 items = g_list_prepend(items, item);
485                 ++n;
486             }
487             else
488                 focus = item;
489         }
490     }
491     while(gtk_tree_model_iter_next(model, &it));
492     items = g_list_reverse(items);
493     if(focus)
494     {
495         items = g_list_prepend(items, focus);
496         ++n;
497     }
498     if(n_items)
499         *n_items = n;
500     return items;
501 }
502 
copy_desktop_config(FmDesktopConfig * dst,FmDesktopConfig * src)503 static void copy_desktop_config(FmDesktopConfig *dst, FmDesktopConfig *src)
504 {
505     int i;
506 
507     dst->wallpaper_mode = src->wallpaper_mode;
508     dst->wallpaper = g_strdup(src->wallpaper);
509     if (src->wallpapers_configured > 0)
510     {
511         dst->wallpapers = g_new(char *, src->wallpapers_configured);
512         for (i = 0; i < src->wallpapers_configured; i++)
513             dst->wallpapers[i] = g_strdup(src->wallpapers[i]);
514     }
515     else
516         dst->wallpapers = NULL;
517     dst->wallpapers_configured = src->wallpapers_configured;
518     dst->wallpaper_common = src->wallpaper_common;
519     dst->show_wm_menu = src->show_wm_menu;
520     dst->configured = TRUE;
521     dst->changed = FALSE;
522     dst->desktop_bg = src->desktop_bg;
523     dst->desktop_fg = src->desktop_fg;
524     dst->desktop_shadow = src->desktop_shadow;
525     dst->desktop_font = g_strdup(src->desktop_font);
526     dst->desktop_sort_type = src->desktop_sort_type;
527     dst->desktop_sort_by = src->desktop_sort_by;
528     dst->folder = g_strdup(src->folder);
529 #if FM_CHECK_VERSION(1, 2, 0)
530     dst->show_documents = src->show_documents;
531     dst->show_trash = src->show_trash;
532     dst->show_mounts = src->show_mounts;
533 #endif
534 
535 }
536 
537 static GVolumeMonitor *vol_mon = NULL;
538 
539 static void _free_extra_item(FmDesktopExtraItem *item);
540 
on_idle_extra_item_add(gpointer user_data)541 static gboolean on_idle_extra_item_add(gpointer user_data)
542 {
543     FmDesktopExtraItem *item = user_data;
544     int i;
545 
546     /* if mount is not NULL then it's new mount so add it to the list */
547     if (item->mount)
548     {
549         mounts = g_slist_append(mounts, item);
550         /* add it to all models that watch mounts */
551         for (i = 0; i < n_screens; i++)
552             if (desktops[i]->monitor >= 0 && desktops[i]->conf.show_mounts
553                 && desktops[i]->model)
554                 fm_folder_model_extra_file_add(desktops[i]->model, item->fi,
555                                                FM_FOLDER_MODEL_ITEMPOS_POST);
556     }
557     else if (item == documents)
558     {
559         /* add it to all models that watch documents */
560         for (i = 0; i < n_screens; i++)
561             if (desktops[i]->monitor >= 0 && desktops[i]->conf.show_documents
562                 && desktops[i]->model)
563             {
564                 fm_folder_model_extra_file_add(desktops[i]->model, item->fi,
565                                                FM_FOLDER_MODEL_ITEMPOS_PRE);
566                 /* if this is extra item it might be loaded after the folder
567                    therefore we have to reload fixed positions again to apply */
568                 reload_items(desktops[i]);
569             }
570     }
571     else if (item == trash_can)
572     {
573         /* add it to all models that watch trash can */
574         for (i = 0; i < n_screens; i++)
575             if (desktops[i]->monitor >= 0 && desktops[i]->conf.show_trash
576                 && desktops[i]->model)
577             {
578                 fm_folder_model_extra_file_add(desktops[i]->model, item->fi,
579                                                FM_FOLDER_MODEL_ITEMPOS_PRE);
580                 reload_items(desktops[i]);
581             }
582     }
583     else
584     {
585         g_critical("got file info for unknown desktop item %s",
586                    fm_path_get_basename(item->path));
587         _free_extra_item(item);
588     }
589     return FALSE;
590 }
591 
592 static gboolean trash_is_empty = FALSE; /* startup default */
593 
594 /* returns TRUE if model should be updated */
_update_trash_icon(FmDesktopExtraItem * item)595 static gboolean _update_trash_icon(FmDesktopExtraItem *item)
596 {
597     GFile *gf = fm_file_new_for_uri("trash:///");
598     GFileInfo *inf = g_file_query_info(gf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, 0, NULL, NULL);
599     const char *icon_name;
600     GIcon *icon;
601     guint32 n;
602 
603     g_object_unref(gf);
604     if (!inf)
605         return FALSE;
606 
607     n = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT);
608     g_object_unref(inf);
609     if (n > 0 && trash_is_empty)
610         icon_name = "user-trash-full";
611     else if (n == 0 && !trash_is_empty)
612         icon_name = "user-trash";
613     else /* not changed */
614         return FALSE;
615     trash_is_empty = (n == 0);
616     icon = g_themed_icon_new_with_default_fallbacks(icon_name);
617     fm_file_info_set_icon(item->fi, icon);
618     g_object_unref(icon);
619     return TRUE;
620 }
621 
on_file_info_job_finished(FmFileInfoJob * job,gpointer user_data)622 static void on_file_info_job_finished(FmFileInfoJob* job, gpointer user_data)
623 {
624     FmDesktopExtraItem *item = user_data;
625     FmFileInfo *fi;
626     char *name;
627     GIcon *icon;
628 
629     if(fm_file_info_list_is_empty(job->file_infos))
630     {
631         /* failed */
632         g_critical("FmFileInfoJob failed on desktop mount update");
633         _free_extra_item(item);
634         return;
635     }
636     fi = fm_file_info_list_peek_head(job->file_infos);
637     /* FIXME: check for duplicates? */
638     item->fi = fm_file_info_ref(fi);
639     item->job = NULL;
640     g_object_unref(job);
641     /* update some data with those from the mount */
642     if (item->mount)
643     {
644         name = g_mount_get_name(item->mount);
645         fm_file_info_set_disp_name(fi, name);
646         g_free(name);
647         icon = g_mount_get_icon(item->mount);
648         fm_file_info_set_icon(fi, icon);
649         g_object_unref(icon);
650     }
651     /* update trash can icon */
652     else if (item == trash_can)
653         _update_trash_icon(item);
654     /* queue adding item to the list and folder models */
655     gdk_threads_add_idle(on_idle_extra_item_add, item);
656 }
657 
_free_extra_item(FmDesktopExtraItem * item)658 static void _free_extra_item(FmDesktopExtraItem *item)
659 {
660     if (item->mount)
661         g_object_unref(item->mount);
662     fm_path_unref(item->path);
663     if (item->fi)
664         fm_file_info_unref(item->fi);
665     if (item->job)
666     {
667         g_signal_handlers_disconnect_by_func(item->job, on_file_info_job_finished, item);
668         fm_job_cancel(FM_JOB(item->job));
669         g_object_unref(item->job);
670     }
671     g_slice_free(FmDesktopExtraItem, item);
672 }
673 
on_mount_added(GVolumeMonitor * volume_monitor,GMount * mount,gpointer _unused)674 static void on_mount_added(GVolumeMonitor *volume_monitor, GMount *mount,
675                            gpointer _unused)
676 {
677     GFile *file;
678     FmDesktopExtraItem *item;
679 
680     /* get file info for the mount point */
681     item = g_slice_new(FmDesktopExtraItem);
682     item->mount = g_object_ref(mount);
683     file = g_mount_get_root(mount);
684     item->path = fm_path_new_for_gfile(file);
685     g_object_unref(file);
686     item->fi = NULL;
687     item->job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_NONE);
688     fm_file_info_job_add(item->job, item->path);
689     g_signal_connect(item->job, "finished", G_CALLBACK(on_file_info_job_finished), item);
690     if (!fm_job_run_async(FM_JOB(item->job)))
691     {
692         g_critical("fm_job_run_async() failed on desktop mount update");
693         _free_extra_item(item);
694     }
695 }
696 
on_idle_extra_item_remove(gpointer user_data)697 static gboolean on_idle_extra_item_remove(gpointer user_data)
698 {
699     GMount *mount = user_data;
700     GSList *sl;
701     FmDesktopExtraItem *item;
702     int i;
703 
704     for (sl = mounts; sl; sl = sl->next)
705     {
706         item = sl->data;
707         if (item->mount == mount)
708             break;
709     }
710     if (sl)
711     {
712         for (i = 0; i < n_screens; i++)
713             if (desktops[i]->monitor >= 0 && desktops[i]->conf.show_mounts
714                 && desktops[i]->model)
715                 fm_folder_model_extra_file_remove(desktops[i]->model, item->fi);
716         mounts = g_slist_delete_link(mounts, sl);
717         _free_extra_item(item);
718     }
719     else
720         g_warning("got unmount for unknown desktop item");
721     g_object_unref(mount);
722     return FALSE;
723 }
724 
on_mount_removed(GVolumeMonitor * volume_monitor,GMount * mount,gpointer _unused)725 static void on_mount_removed(GVolumeMonitor *volume_monitor, GMount *mount,
726                              gpointer _unused)
727 {
728     gdk_threads_add_idle(on_idle_extra_item_remove, g_object_ref(mount));
729 }
730 
731 
732 /* ---------------------------------------------------------------------
733     special items handlers */
734 
735 static GFileMonitor *trash_monitor = NULL;
736 
on_trash_changed(GFileMonitor * monitor,GFile * gf,GFile * other,GFileMonitorEvent evt,FmDesktopExtraItem * item)737 static void on_trash_changed(GFileMonitor *monitor, GFile *gf, GFile *other,
738                              GFileMonitorEvent evt, FmDesktopExtraItem *item)
739 {
740     int i;
741 
742     if (!_update_trash_icon(item))
743         return;
744     for (i = 0; i < n_screens; i++)
745         if (desktops[i]->monitor >= 0 && desktops[i]->conf.show_trash
746             && desktops[i]->model)
747             fm_folder_model_file_changed(desktops[i]->model, item->fi);
748 }
749 
_add_extra_item(const char * path_str)750 static FmDesktopExtraItem *_add_extra_item(const char *path_str)
751 {
752     FmDesktopExtraItem *item;
753 
754     if (path_str == NULL) /* special directory does not exist */
755         return NULL;
756 
757     item = g_slice_new(FmDesktopExtraItem);
758     item->mount = NULL;
759     item->path = fm_path_new_for_str(path_str);
760     item->fi = NULL;
761     item->job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_NONE);
762     fm_file_info_job_add(item->job, item->path);
763     g_signal_connect(item->job, "finished", G_CALLBACK(on_file_info_job_finished), item);
764     if (!fm_job_run_async(FM_JOB(item->job)))
765     {
766         g_critical("fm_job_run_async() failed on desktop special item update");
767         _free_extra_item(item);
768         item = NULL;
769     }
770     return item;
771 }
772 #endif
773 
774 /* ---------------------------------------------------------------------
775     accessibility handlers */
776 
777 static void set_focused_item(FmDesktop* desktop, FmDesktopItem* item);
778 static inline gint fm_desktop_accessible_index(GtkWidget *desktop, gpointer item);
779 
780 /* ---- accessible item mirror ---- */
781 typedef struct _FmDesktopItemAccessible FmDesktopItemAccessible;
782 typedef struct _FmDesktopItemAccessibleClass FmDesktopItemAccessibleClass;
783 #define FM_TYPE_DESKTOP_ITEM_ACCESSIBLE      (fm_desktop_item_accessible_get_type ())
784 #define FM_DESKTOP_ITEM_ACCESSIBLE(obj)      (G_TYPE_CHECK_INSTANCE_CAST((obj), \
785                                               FM_TYPE_DESKTOP_ITEM_ACCESSIBLE, FmDesktopItemAccessible))
786 #define FM_IS_DESKTOP_ITEM_ACCESSIBLE(obj)   (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
787                                               FM_TYPE_DESKTOP_ITEM_ACCESSIBLE))
788 
789 static GType fm_desktop_item_accessible_get_type (void);
790 
791 struct _FmDesktopItemAccessible
792 {
793     AtkObject parent;
794     FmDesktopItem *item;
795     GtkWidget *widget;
796     AtkStateSet *state_set;
797     guint action_idle_handler;
798     gint action_type;
799 };
800 
801 struct _FmDesktopItemAccessibleClass
802 {
803     AtkObjectClass parent_class;
804 };
805 
806 static void atk_component_item_interface_init(AtkComponentIface *iface);
807 static void atk_action_item_interface_init(AtkActionIface *iface);
808 static void atk_image_item_interface_init(AtkImageIface *iface);
809 
G_DEFINE_TYPE_WITH_CODE(FmDesktopItemAccessible,fm_desktop_item_accessible,ATK_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT,atk_component_item_interface_init)G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION,atk_action_item_interface_init)G_IMPLEMENT_INTERFACE (ATK_TYPE_IMAGE,atk_image_item_interface_init))810 G_DEFINE_TYPE_WITH_CODE(FmDesktopItemAccessible, fm_desktop_item_accessible, ATK_TYPE_OBJECT,
811                         G_IMPLEMENT_INTERFACE(ATK_TYPE_COMPONENT, atk_component_item_interface_init)
812                         G_IMPLEMENT_INTERFACE(ATK_TYPE_ACTION, atk_action_item_interface_init)
813                         G_IMPLEMENT_INTERFACE(ATK_TYPE_IMAGE, atk_image_item_interface_init))
814 
815 static void fm_desktop_item_accessible_init(FmDesktopItemAccessible *item)
816 {
817     item->state_set = atk_state_set_new();
818     atk_state_set_add_state(item->state_set, ATK_STATE_ENABLED);
819     atk_state_set_add_state(item->state_set, ATK_STATE_FOCUSABLE);
820     atk_state_set_add_state(item->state_set, ATK_STATE_SENSITIVE);
821     atk_state_set_add_state(item->state_set, ATK_STATE_SELECTABLE);
822     atk_state_set_add_state(item->state_set, ATK_STATE_VISIBLE);
823     item->action_idle_handler = 0;
824 }
825 
fm_desktop_item_accessible_finalize(GObject * object)826 static void fm_desktop_item_accessible_finalize(GObject *object)
827 {
828     FmDesktopItemAccessible *item = (FmDesktopItemAccessible *)object;
829 
830     g_return_if_fail(item != NULL);
831     g_return_if_fail(FM_IS_DESKTOP_ITEM_ACCESSIBLE(item));
832 
833     if (item->widget)
834         g_object_remove_weak_pointer(G_OBJECT(item->widget), (gpointer)&item->widget);
835     if (item->state_set)
836         g_object_unref (item->state_set);
837     if (item->action_idle_handler)
838     {
839         g_source_remove(item->action_idle_handler);
840         item->action_idle_handler = 0;
841     }
842 }
843 
fm_desktop_item_accessible_new(FmDesktop * desktop,FmDesktopItem * item)844 static FmDesktopItemAccessible *fm_desktop_item_accessible_new(FmDesktop *desktop,
845                                                                FmDesktopItem *item)
846 {
847     const char *name;
848     FmDesktopItemAccessible *atk_item;
849 
850     name = fm_file_info_get_disp_name(item->fi);
851     atk_item = g_object_new(FM_TYPE_DESKTOP_ITEM_ACCESSIBLE,
852                             "accessible-name", name, NULL);
853     atk_item->item = item;
854     atk_item->widget = GTK_WIDGET(desktop);
855     g_object_add_weak_pointer(G_OBJECT(desktop), (gpointer)&atk_item->widget);
856     return atk_item;
857 }
858 
859 /* item interfaces */
fm_desktop_item_accessible_get_extents(AtkComponent * component,gint * x,gint * y,gint * width,gint * height,AtkCoordType coord_type)860 static void fm_desktop_item_accessible_get_extents(AtkComponent *component,
861                                                    gint *x, gint *y,
862                                                    gint *width, gint *height,
863                                                    AtkCoordType coord_type)
864 {
865     FmDesktopItemAccessible *item;
866     AtkObject *parent_obj;
867     gint l_x, l_y;
868 
869     g_return_if_fail(FM_IS_DESKTOP_ITEM_ACCESSIBLE(component));
870     item = FM_DESKTOP_ITEM_ACCESSIBLE(component);
871     if (item->widget == NULL)
872         return;
873     if (atk_state_set_contains_state(item->state_set, ATK_STATE_DEFUNCT))
874         return;
875 
876     *width = item->item->area.width;
877     *height = item->item->area.height;
878     parent_obj = gtk_widget_get_accessible(item->widget);
879     atk_component_get_extents(ATK_COMPONENT(parent_obj), &l_x, &l_y, NULL, NULL,
880                               coord_type);
881     *x = l_x + item->item->area.x;
882     *y = l_y + item->item->area.y;
883 }
884 
fm_desktop_item_accessible_grab_focus(AtkComponent * component)885 static gboolean fm_desktop_item_accessible_grab_focus(AtkComponent *component)
886 {
887     FmDesktopItemAccessible *item;
888 
889     g_return_val_if_fail(FM_IS_DESKTOP_ITEM_ACCESSIBLE(component), FALSE);
890     item = FM_DESKTOP_ITEM_ACCESSIBLE(component);
891     if (item->widget == NULL)
892         return FALSE;
893     if (atk_state_set_contains_state(item->state_set, ATK_STATE_DEFUNCT))
894         return FALSE;
895 
896     gtk_widget_grab_focus(item->widget);
897     set_focused_item(FM_DESKTOP(item->widget), item->item);
898     return TRUE;
899 }
900 
atk_component_item_interface_init(AtkComponentIface * iface)901 static void atk_component_item_interface_init(AtkComponentIface *iface)
902 {
903     iface->get_extents = fm_desktop_item_accessible_get_extents;
904     iface->grab_focus = fm_desktop_item_accessible_grab_focus;
905 }
906 
907 /* NOTE: this is not very fast */
fm_desktop_item_get_tree_path(FmDesktop * self,FmDesktopItem * item)908 static GtkTreePath *fm_desktop_item_get_tree_path(FmDesktop *self, FmDesktopItem *item)
909 {
910     GtkTreeModel *model = self->model ? GTK_TREE_MODEL(self->model) : NULL;
911     GtkTreeIter it;
912 
913     if(model && gtk_tree_model_get_iter_first(model, &it)) do
914     {
915         if (fm_folder_model_get_item_userdata(self->model, &it) == item)
916             return gtk_tree_model_get_path(model, &it);
917     }
918     while (gtk_tree_model_iter_next(model, &it));
919     return NULL;
920 }
921 
fm_desktop_item_accessible_idle_do_action(gpointer data)922 static gboolean fm_desktop_item_accessible_idle_do_action(gpointer data)
923 {
924     FmDesktopItemAccessible *item;
925     GtkTreePath *tp;
926 
927     if(g_source_is_destroyed(g_main_current_source()))
928         return FALSE;
929 
930     item = FM_DESKTOP_ITEM_ACCESSIBLE(data);
931     item->action_idle_handler = 0;
932     if (item->widget != NULL)
933     {
934         tp = fm_desktop_item_get_tree_path(FM_DESKTOP(item->widget), item->item);
935         if (tp)
936         {
937             fm_folder_view_item_clicked(FM_FOLDER_VIEW(item->widget), tp,
938                                         item->action_type == 0 ? FM_FV_ACTIVATED : FM_FV_CONTEXT_MENU);
939             gtk_tree_path_free(tp);
940         }
941     }
942     return FALSE;
943 }
944 
fm_desktop_item_accessible_action_do_action(AtkAction * action,gint i)945 static gboolean fm_desktop_item_accessible_action_do_action(AtkAction *action,
946                                                             gint i)
947 {
948     FmDesktopItemAccessible *item;
949 
950     if (i != 0 && i != 1)
951         return FALSE;
952 
953     item = FM_DESKTOP_ITEM_ACCESSIBLE(action);
954     if (item->widget == NULL)
955         return FALSE;
956     if (atk_state_set_contains_state(item->state_set, ATK_STATE_DEFUNCT))
957         return FALSE;
958     if (!item->action_idle_handler)
959     {
960         item->action_type = i;
961         item->action_idle_handler = gdk_threads_add_idle(fm_desktop_item_accessible_idle_do_action, item);
962     }
963     return TRUE;
964 }
965 
fm_desktop_item_accessible_action_get_n_actions(AtkAction * action)966 static gint fm_desktop_item_accessible_action_get_n_actions(AtkAction *action)
967 {
968     return 2;
969 }
970 
fm_desktop_item_accessible_action_get_description(AtkAction * action,gint i)971 static const gchar *fm_desktop_item_accessible_action_get_description(AtkAction *action, gint i)
972 {
973     if (i == 0)
974         return _("Activate file");
975     else if (i == 1)
976         return _("Show file menu");
977     return NULL;
978 }
979 
fm_desktop_item_accessible_action_get_name(AtkAction * action,gint i)980 static const gchar *fm_desktop_item_accessible_action_get_name(AtkAction *action, gint i)
981 {
982     if (i == 0)
983         return "activate";
984     if (i == 1)
985         return "menu";
986     return NULL;
987 }
988 
atk_action_item_interface_init(AtkActionIface * iface)989 static void atk_action_item_interface_init(AtkActionIface *iface)
990 {
991     iface->do_action = fm_desktop_item_accessible_action_do_action;
992     iface->get_n_actions = fm_desktop_item_accessible_action_get_n_actions;
993     iface->get_description = fm_desktop_item_accessible_action_get_description;
994     iface->get_name = fm_desktop_item_accessible_action_get_name;
995     /* NOTE: we don't support descriptions change */
996 }
997 
fm_desktop_item_accessible_image_get_image_description(AtkImage * image)998 static const gchar *fm_desktop_item_accessible_image_get_image_description(AtkImage *image)
999 {
1000     FmDesktopItemAccessible *item = FM_DESKTOP_ITEM_ACCESSIBLE(image);
1001 
1002     /* FIXME: is there a better way to handle this? */
1003     return fm_file_info_get_desc(item->item->fi);
1004 }
1005 
fm_desktop_item_accessible_image_get_image_size(AtkImage * image,gint * width,gint * height)1006 static void fm_desktop_item_accessible_image_get_image_size(AtkImage *image,
1007                                                             gint *width,
1008                                                             gint *height)
1009 {
1010     FmDesktopItemAccessible *item = FM_DESKTOP_ITEM_ACCESSIBLE(image);
1011 
1012     if (item->widget == NULL)
1013         return;
1014     if (atk_state_set_contains_state(item->state_set, ATK_STATE_DEFUNCT))
1015         return;
1016 
1017     *width = item->item->icon_rect.width;
1018     *height = item->item->icon_rect.height;
1019 }
1020 
fm_desktop_item_accessible_image_get_image_position(AtkImage * image,gint * x,gint * y,AtkCoordType coord_type)1021 static void fm_desktop_item_accessible_image_get_image_position(AtkImage *image,
1022                                                                 gint *x,
1023                                                                 gint *y,
1024                                                                 AtkCoordType coord_type)
1025 {
1026     FmDesktopItemAccessible *item = FM_DESKTOP_ITEM_ACCESSIBLE(image);
1027     AtkObject *parent_obj;
1028 
1029     if (item->widget == NULL)
1030         return;
1031     if (atk_state_set_contains_state(item->state_set, ATK_STATE_DEFUNCT))
1032         return;
1033 
1034     parent_obj = gtk_widget_get_accessible(item->widget);
1035     atk_component_get_extents(ATK_COMPONENT(parent_obj), x, y, NULL, NULL, coord_type);
1036     *x += item->item->icon_rect.x - item->item->area.x;
1037     *y += item->item->icon_rect.y - item->item->area.y;
1038 }
1039 
atk_image_item_interface_init(AtkImageIface * iface)1040 static void atk_image_item_interface_init(AtkImageIface *iface)
1041 {
1042     iface->get_image_description = fm_desktop_item_accessible_image_get_image_description;
1043     /* NOTE: we don't support descriptions change */
1044     iface->get_image_size = fm_desktop_item_accessible_image_get_image_size;
1045     iface->get_image_position = fm_desktop_item_accessible_image_get_image_position;
1046 }
1047 
fm_desktop_item_accessible_get_parent(AtkObject * obj)1048 static AtkObject *fm_desktop_item_accessible_get_parent(AtkObject *obj)
1049 {
1050     FmDesktopItemAccessible *item = FM_DESKTOP_ITEM_ACCESSIBLE(obj);
1051 
1052     return item->widget ? gtk_widget_get_accessible(item->widget) : NULL;
1053 }
1054 
fm_desktop_item_accessible_get_index_in_parent(AtkObject * obj)1055 static gint fm_desktop_item_accessible_get_index_in_parent(AtkObject *obj)
1056 {
1057     FmDesktopItemAccessible *item = FM_DESKTOP_ITEM_ACCESSIBLE(obj);
1058 
1059     return item->widget ? fm_desktop_accessible_index(item->widget, item) : -1;
1060 }
1061 
fm_desktop_item_accessible_ref_state_set(AtkObject * obj)1062 static AtkStateSet *fm_desktop_item_accessible_ref_state_set(AtkObject *obj)
1063 {
1064     FmDesktopItemAccessible *item = FM_DESKTOP_ITEM_ACCESSIBLE(obj);
1065     FmDesktop *desktop;
1066 
1067     g_return_val_if_fail(item->state_set, NULL);
1068 
1069     /* update states first */
1070     if (item->widget != NULL)
1071     {
1072         desktop = (FmDesktop *)item->widget;
1073         if (desktop->focus == item->item)
1074             atk_state_set_add_state(item->state_set, ATK_STATE_FOCUSED);
1075         else
1076             atk_state_set_remove_state(item->state_set, ATK_STATE_FOCUSED);
1077     }
1078     if (item->item->is_selected)
1079         atk_state_set_add_state(item->state_set, ATK_STATE_SELECTED);
1080     else
1081         atk_state_set_remove_state(item->state_set, ATK_STATE_SELECTED);
1082     return g_object_ref(item->state_set);
1083 }
1084 
fm_desktop_item_accessible_class_init(FmDesktopItemAccessibleClass * klass)1085 static void fm_desktop_item_accessible_class_init(FmDesktopItemAccessibleClass *klass)
1086 {
1087     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1088     AtkObjectClass *atk_class = ATK_OBJECT_CLASS(klass);
1089 
1090     gobject_class->finalize = fm_desktop_item_accessible_finalize;
1091 
1092     atk_class->get_index_in_parent = fm_desktop_item_accessible_get_index_in_parent;
1093     atk_class->get_parent = fm_desktop_item_accessible_get_parent;
1094     atk_class->ref_state_set = fm_desktop_item_accessible_ref_state_set;
1095 }
1096 
fm_desktop_item_accessible_add_state(FmDesktopItemAccessible * item,AtkStateType state_type)1097 static gboolean fm_desktop_item_accessible_add_state(FmDesktopItemAccessible *item,
1098                                                      AtkStateType state_type)
1099 {
1100     gboolean rc;
1101 
1102     rc = atk_state_set_add_state(item->state_set, state_type);
1103     atk_object_notify_state_change(ATK_OBJECT (item), state_type, TRUE);
1104     /* If state_type is ATK_STATE_VISIBLE, additional notification */
1105     if (state_type == ATK_STATE_VISIBLE)
1106         g_signal_emit_by_name(item, "visible-data-changed");
1107     return rc;
1108 }
1109 
1110 /* ---- accessible widget mirror ---- */
1111 typedef struct _FmDesktopAccessible FmDesktopAccessible;
1112 typedef struct _FmDesktopAccessibleClass FmDesktopAccessibleClass;
1113 #define FM_TYPE_DESKTOP_ACCESSIBLE      (fm_desktop_accessible_get_type())
1114 #define FM_DESKTOP_ACCESSIBLE(obj)      (G_TYPE_CHECK_INSTANCE_CAST((obj), \
1115                                          FM_TYPE_DESKTOP_ACCESSIBLE, FmDesktopAccessible))
1116 #define FM_IS_DESKTOP_ACCESSIBLE(obj)   (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
1117                                          FM_TYPE_DESKTOP_ACCESSIBLE))
1118 
1119 static GType fm_desktop_accessible_get_type (void);
1120 
1121 typedef struct _FmDesktopAccessiblePriv FmDesktopAccessiblePriv;
1122 struct _FmDesktopAccessiblePriv
1123 {
1124     /* we don't catch model index but have own index in items list instead */
1125     GList *items;
1126     guint action_idle_handler;
1127 };
1128 
1129 #define FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(_d_) G_TYPE_INSTANCE_GET_PRIVATE(_d_, FM_TYPE_DESKTOP_ACCESSIBLE, FmDesktopAccessiblePriv)
1130 
1131 static void atk_component_interface_init(AtkComponentIface *iface);
1132 static void atk_selection_interface_init(AtkSelectionIface *iface);
1133 static void atk_action_interface_init(AtkActionIface *iface);
1134 
1135 /* we cannot use G_DEFINE_TYPE_WITH_CODE - no GtkWindowAccessible before 3.2.0 */
1136 static void fm_desktop_accessible_init(FmDesktopAccessible *self);
1137 static void fm_desktop_accessible_class_init(FmDesktopAccessibleClass *klass);
1138 static gpointer fm_desktop_accessible_parent_class = NULL;
1139 
fm_desktop_accessible_class_intern_init(gpointer klass)1140 static void fm_desktop_accessible_class_intern_init(gpointer klass)
1141 {
1142     fm_desktop_accessible_parent_class = g_type_class_peek_parent(klass);
1143     fm_desktop_accessible_class_init((FmDesktopAccessibleClass*)klass);
1144 }
1145 
fm_desktop_accessible_get_type(void)1146 GType fm_desktop_accessible_get_type(void)
1147 {
1148     static volatile gsize type_id_volatile = 0;
1149 
1150     if (g_once_init_enter(&type_id_volatile))
1151     {
1152         /*
1153          * Figure out the size of the class and instance
1154          * we are deriving from
1155          */
1156         AtkObjectFactory *factory;
1157         GTypeQuery query;
1158         GType derived_atk_type;
1159         GType g_define_type_id;
1160 
1161         factory = atk_registry_get_factory(atk_get_default_registry(),
1162                                            g_type_parent(FM_TYPE_DESKTOP));
1163         derived_atk_type = atk_object_factory_get_accessible_type(factory);
1164         g_type_query(derived_atk_type, &query);
1165         g_define_type_id = g_type_register_static_simple(derived_atk_type,
1166                                 g_intern_static_string("FmDesktopAccessible"),
1167                                 query.class_size,
1168                                 (GClassInitFunc)fm_desktop_accessible_class_intern_init,
1169                                 query.instance_size,
1170                                 (GInstanceInitFunc)fm_desktop_accessible_init,
1171                                 0);
1172         G_IMPLEMENT_INTERFACE(ATK_TYPE_COMPONENT, atk_component_interface_init)
1173         G_IMPLEMENT_INTERFACE(ATK_TYPE_SELECTION, atk_selection_interface_init)
1174         G_IMPLEMENT_INTERFACE(ATK_TYPE_ACTION, atk_action_interface_init)
1175         g_once_init_leave(&type_id_volatile, g_define_type_id);
1176     }
1177     return type_id_volatile;
1178 }
1179 
fm_desktop_find_accessible_for_item(FmDesktopAccessiblePriv * priv,FmDesktopItem * item)1180 static inline GList *fm_desktop_find_accessible_for_item(FmDesktopAccessiblePriv *priv, FmDesktopItem *item)
1181 {
1182     GList *items = priv->items;
1183 
1184     while (items)
1185     {
1186         if (((FmDesktopItemAccessible *)items->data)->item == item)
1187             return items;
1188         items = items->next;
1189     }
1190     return NULL;
1191 }
1192 
1193 /* widget interfaces */
fm_desktop_accessible_ref_accessible_at_point(AtkComponent * component,gint x,gint y,AtkCoordType coord_type)1194 static AtkObject *fm_desktop_accessible_ref_accessible_at_point(AtkComponent *component,
1195                                                                 gint x, gint y,
1196                                                                 AtkCoordType coord_type)
1197 {
1198     GtkWidget *widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(component));
1199     FmDesktop *desktop;
1200     gint x_pos, y_pos;
1201     FmDesktopItem *item;
1202     GList *obj_l = NULL;
1203     GtkTreeIter it;
1204 
1205     if (widget == NULL)
1206         return NULL;
1207     desktop = FM_DESKTOP(widget);
1208     atk_component_get_extents(component, &x_pos, &y_pos, NULL, NULL, coord_type);
1209     item = hit_test(desktop, &it, x - x_pos, y - y_pos);
1210     if (item)
1211         obj_l = fm_desktop_find_accessible_for_item(FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(component), item);
1212     if (obj_l)
1213         return g_object_ref(obj_l->data);
1214     return NULL;
1215 }
1216 
atk_component_interface_init(AtkComponentIface * iface)1217 static void atk_component_interface_init(AtkComponentIface *iface)
1218 {
1219     iface->ref_accessible_at_point = fm_desktop_accessible_ref_accessible_at_point;
1220 }
1221 
fm_desktop_accessible_add_selection(AtkSelection * selection,gint i)1222 static gboolean fm_desktop_accessible_add_selection(AtkSelection *selection, gint i)
1223 {
1224     GtkWidget *widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(selection));
1225     FmDesktop *desktop;
1226     FmDesktopAccessiblePriv *priv;
1227     FmDesktopItemAccessible *item;
1228 
1229     if (widget == NULL)
1230         return FALSE;
1231 
1232     desktop = FM_DESKTOP(widget);
1233     priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(selection);
1234     item = g_list_nth_data(priv->items, i);
1235     if (!item)
1236         return FALSE;
1237     item->item->is_selected = TRUE;
1238     redraw_item(desktop, item->item);
1239     atk_object_notify_state_change(ATK_OBJECT(item), ATK_STATE_SELECTED, TRUE);
1240     return TRUE;
1241 }
1242 
fm_desktop_accessible_clear_selection(AtkSelection * selection)1243 static gboolean fm_desktop_accessible_clear_selection(AtkSelection *selection)
1244 {
1245     GtkWidget *widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(selection));
1246 
1247     if (widget == NULL)
1248         return FALSE;
1249 
1250     _unselect_all(FM_FOLDER_VIEW(widget));
1251     return TRUE;
1252 }
1253 
fm_desktop_accessible_ref_selection(AtkSelection * selection,gint i)1254 static AtkObject *fm_desktop_accessible_ref_selection(AtkSelection *selection,
1255                                                       gint i)
1256 {
1257     FmDesktopAccessiblePriv *priv;
1258     FmDesktopItemAccessible *item;
1259     GList *items;
1260 
1261     if (i < 0)
1262         return NULL;
1263 
1264     priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(selection);
1265     for (items = priv->items; items; items = items->next)
1266     {
1267         item = items->data;
1268         if (item->item->is_selected)
1269             if (i-- == 0)
1270                 return g_object_ref(item);
1271     }
1272     return NULL;
1273 }
1274 
fm_desktop_accessible_get_selection_count(AtkSelection * selection)1275 static gint fm_desktop_accessible_get_selection_count(AtkSelection *selection)
1276 {
1277     FmDesktopAccessiblePriv *priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(selection);
1278     FmDesktopItemAccessible *item;
1279     GList *items;
1280     gint i = 0;
1281 
1282     for (items = priv->items; items; items = items->next)
1283     {
1284         item = items->data;
1285         if (item->item->is_selected)
1286             i++;
1287     }
1288     return i;
1289 }
1290 
fm_desktop_accessible_is_child_selected(AtkSelection * selection,gint i)1291 static gboolean fm_desktop_accessible_is_child_selected(AtkSelection *selection,
1292                                                         gint i)
1293 {
1294     FmDesktopAccessiblePriv *priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(selection);
1295     FmDesktopItemAccessible *item = g_list_nth_data(priv->items, i);
1296 
1297     if (item == NULL)
1298         return FALSE;
1299     return item->item->is_selected;
1300 }
1301 
fm_desktop_accessible_remove_selection(AtkSelection * selection,gint i)1302 static gboolean fm_desktop_accessible_remove_selection(AtkSelection *selection,
1303                                                        gint i)
1304 {
1305     GtkWidget *widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(selection));
1306     FmDesktop *desktop;
1307     FmDesktopAccessiblePriv *priv;
1308     FmDesktopItemAccessible *item;
1309     GList *items;
1310 
1311     if (i < 0)
1312         return FALSE;
1313     if (widget == NULL)
1314         return FALSE;
1315     desktop = FM_DESKTOP(widget);
1316 
1317     priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(selection);
1318     for (items = priv->items; items; items = items->next)
1319     {
1320         item = items->data;
1321         if (item->item->is_selected)
1322             if (i-- == 0)
1323             {
1324                 item->item->is_selected = FALSE;
1325                 redraw_item(desktop, item->item);
1326                 atk_object_notify_state_change(ATK_OBJECT(item), ATK_STATE_SELECTED, FALSE);
1327                 return TRUE;
1328             }
1329     }
1330     return FALSE;
1331 }
1332 
fm_desktop_accessible_select_all_selection(AtkSelection * selection)1333 static gboolean fm_desktop_accessible_select_all_selection(AtkSelection *selection)
1334 {
1335     GtkWidget *widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(selection));
1336 
1337     if (widget == NULL)
1338         return FALSE;
1339 
1340     _select_all(FM_FOLDER_VIEW(widget));
1341     return TRUE;
1342 }
1343 
atk_selection_interface_init(AtkSelectionIface * iface)1344 static void atk_selection_interface_init(AtkSelectionIface *iface)
1345 {
1346     iface->add_selection = fm_desktop_accessible_add_selection;
1347     iface->clear_selection = fm_desktop_accessible_clear_selection;
1348     iface->ref_selection = fm_desktop_accessible_ref_selection;
1349     iface->get_selection_count = fm_desktop_accessible_get_selection_count;
1350     iface->is_child_selected = fm_desktop_accessible_is_child_selected;
1351     iface->remove_selection = fm_desktop_accessible_remove_selection;
1352     iface->select_all_selection = fm_desktop_accessible_select_all_selection;
1353 }
1354 
fm_desktop_accessible_idle_do_action(gpointer data)1355 static gboolean fm_desktop_accessible_idle_do_action(gpointer data)
1356 {
1357     GtkWidget *widget;
1358     FmDesktopAccessiblePriv *priv;
1359 
1360     if(g_source_is_destroyed(g_main_current_source()))
1361         return FALSE;
1362 
1363     priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(data);
1364     priv->action_idle_handler = 0;
1365     widget = gtk_accessible_get_widget(GTK_ACCESSIBLE(data));
1366     if (widget)
1367         fm_folder_view_item_clicked(FM_FOLDER_VIEW(widget), NULL, FM_FV_CONTEXT_MENU);
1368     return FALSE;
1369 }
1370 
fm_desktop_accessible_action_do_action(AtkAction * action,gint i)1371 static gboolean fm_desktop_accessible_action_do_action(AtkAction *action, gint i)
1372 {
1373     FmDesktopAccessiblePriv *priv;
1374 
1375     if (i != 0)
1376         return FALSE;
1377     if (gtk_accessible_get_widget(GTK_ACCESSIBLE(action)) == NULL)
1378         return FALSE;
1379 
1380     priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(action);
1381     if (!priv->action_idle_handler)
1382         priv->action_idle_handler = gdk_threads_add_idle(fm_desktop_accessible_idle_do_action, action);
1383     return TRUE;
1384 }
1385 
fm_desktop_accessible_action_get_n_actions(AtkAction * action)1386 static gint fm_desktop_accessible_action_get_n_actions(AtkAction *action)
1387 {
1388     return 1;
1389 }
1390 
fm_desktop_accessible_action_get_description(AtkAction * action,gint i)1391 static const gchar *fm_desktop_accessible_action_get_description(AtkAction *action, gint i)
1392 {
1393     if (i == 0)
1394         return _("Show desktop menu");
1395     return NULL;
1396 }
1397 
fm_desktop_accessible_action_get_name(AtkAction * action,gint i)1398 static const gchar *fm_desktop_accessible_action_get_name(AtkAction *action, gint i)
1399 {
1400     if (i == 0)
1401         return "menu";
1402     return NULL;
1403 }
1404 
atk_action_interface_init(AtkActionIface * iface)1405 static void atk_action_interface_init(AtkActionIface *iface)
1406 {
1407     iface->do_action = fm_desktop_accessible_action_do_action;
1408     iface->get_n_actions = fm_desktop_accessible_action_get_n_actions;
1409     iface->get_description = fm_desktop_accessible_action_get_description;
1410     iface->get_name = fm_desktop_accessible_action_get_name;
1411     /* NOTE: we don't support descriptions change */
1412 }
1413 
fm_desktop_accessible_finalize(GObject * object)1414 static void fm_desktop_accessible_finalize(GObject *object)
1415 {
1416     FmDesktopAccessiblePriv *priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(object);
1417     FmDesktopItemAccessible *item;
1418 
1419     /* FIXME: should we g_assert(priv->items) here instead? */
1420     while (priv->items)
1421     {
1422         item = priv->items->data;
1423         item->item = NULL;
1424         fm_desktop_item_accessible_add_state(item, ATK_STATE_DEFUNCT);
1425         g_signal_emit_by_name(item, "children-changed::remove", 0, NULL, NULL);
1426         priv->items = g_list_remove_link(priv->items, priv->items);
1427         g_object_unref(item);
1428     }
1429     if (priv->action_idle_handler)
1430     {
1431         g_source_remove(priv->action_idle_handler);
1432         priv->action_idle_handler = 0;
1433     }
1434     G_OBJECT_CLASS(fm_desktop_accessible_parent_class)->finalize(object);
1435 }
1436 
fm_desktop_accessible_get_n_children(AtkObject * accessible)1437 static gint fm_desktop_accessible_get_n_children(AtkObject *accessible)
1438 {
1439     FmDesktopAccessiblePriv *priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(accessible);
1440 
1441     return g_list_length(priv->items);
1442 }
1443 
fm_desktop_accessible_ref_child(AtkObject * accessible,gint index)1444 static AtkObject *fm_desktop_accessible_ref_child(AtkObject *accessible,
1445                                                   gint index)
1446 {
1447     FmDesktopAccessiblePriv *priv;
1448     FmDesktopItemAccessible *item;
1449 
1450     if (index < 0)
1451         return NULL;
1452 
1453     priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(accessible);
1454     item = g_list_nth_data(priv->items, index);
1455     if (!item)
1456         return NULL;
1457     return g_object_ref(item);
1458 }
1459 
fm_desktop_accessible_initialize(AtkObject * accessible,gpointer data)1460 static void fm_desktop_accessible_initialize(AtkObject *accessible, gpointer data)
1461 {
1462     if (ATK_OBJECT_CLASS(fm_desktop_accessible_parent_class)->initialize)
1463         ATK_OBJECT_CLASS(fm_desktop_accessible_parent_class)->initialize(accessible, data);
1464     atk_object_set_role(accessible, ATK_ROLE_WINDOW);
1465     /* FIXME: set name by monitor */
1466     atk_object_set_name(accessible, _("Desktop"));
1467 }
1468 
fm_desktop_accessible_init(FmDesktopAccessible * object)1469 static void fm_desktop_accessible_init(FmDesktopAccessible *object)
1470 {
1471     FmDesktopAccessiblePriv *priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(object);
1472 
1473     priv->items = NULL;
1474 }
1475 
fm_desktop_accessible_class_init(FmDesktopAccessibleClass * klass)1476 static void fm_desktop_accessible_class_init(FmDesktopAccessibleClass *klass)
1477 {
1478     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1479     AtkObjectClass *atk_class = ATK_OBJECT_CLASS(klass);
1480 
1481     gobject_class->finalize = fm_desktop_accessible_finalize;
1482 
1483     atk_class->get_n_children = fm_desktop_accessible_get_n_children;
1484     atk_class->ref_child = fm_desktop_accessible_ref_child;
1485     atk_class->initialize = fm_desktop_accessible_initialize;
1486 
1487     g_type_class_add_private(klass, sizeof(FmDesktopAccessiblePriv));
1488 }
1489 
1490 /* ---- interface implementation ---- */
1491 /* handy ATK support is added only in 3.2.0 so we should handle it manually */
1492 static GType fm_desktop_accessible_factory_get_type (void);
1493 
1494 typedef struct {
1495     AtkObjectFactory parent;
1496 } FmDesktopAccessibleFactory;
1497 
1498 typedef struct {
1499     AtkObjectFactoryClass parent_class;
1500 } FmDesktopAccessibleFactoryClass;
1501 
G_DEFINE_TYPE_WITH_CODE(FmDesktopAccessibleFactory,fm_desktop_accessible_factory,ATK_TYPE_OBJECT_FACTORY,atk_registry_set_factory_type (atk_get_default_registry (),FM_TYPE_DESKTOP,g_define_type_id);)1502 G_DEFINE_TYPE_WITH_CODE(FmDesktopAccessibleFactory, fm_desktop_accessible_factory, ATK_TYPE_OBJECT_FACTORY,
1503                         atk_registry_set_factory_type(atk_get_default_registry(),
1504                                                       FM_TYPE_DESKTOP,
1505                                                       g_define_type_id); )
1506 
1507 static GType fm_desktop_accessible_factory_get_accessible_type(void)
1508 {
1509     return FM_TYPE_DESKTOP_ACCESSIBLE;
1510 }
1511 
fm_desktop_accessible_factory_create_accessible(GObject * object)1512 static AtkObject *fm_desktop_accessible_factory_create_accessible(GObject *object)
1513 {
1514     AtkObject *accessible;
1515 
1516     g_return_val_if_fail(FM_IS_DESKTOP(object), NULL);
1517 
1518     accessible = g_object_new(FM_TYPE_DESKTOP_ACCESSIBLE, NULL);
1519     atk_object_initialize(accessible, object);
1520     return accessible;
1521 }
1522 
fm_desktop_accessible_factory_class_init(FmDesktopAccessibleFactoryClass * klass)1523 static void fm_desktop_accessible_factory_class_init(FmDesktopAccessibleFactoryClass *klass)
1524 {
1525     AtkObjectFactoryClass *factory_class = (AtkObjectFactoryClass *)klass;
1526 
1527     factory_class->create_accessible = fm_desktop_accessible_factory_create_accessible;
1528     factory_class->get_accessible_type = fm_desktop_accessible_factory_get_accessible_type;
1529 }
1530 
fm_desktop_accessible_factory_init(FmDesktopAccessibleFactory * factory)1531 static void fm_desktop_accessible_factory_init(FmDesktopAccessibleFactory *factory)
1532 {
1533 }
1534 
fm_desktop_get_accessible(GtkWidget * widget)1535 static AtkObject *fm_desktop_get_accessible(GtkWidget *widget)
1536 {
1537     fm_desktop_accessible_factory_get_type(); /* just to activate it */
1538     return GTK_WIDGET_CLASS(fm_desktop_parent_class)->get_accessible(widget);
1539 }
1540 
fm_desktop_accessible_index(GtkWidget * desktop,gpointer item)1541 static inline gint fm_desktop_accessible_index(GtkWidget *desktop, gpointer item)
1542 {
1543     AtkObject *desktop_atk = gtk_widget_get_accessible(desktop);
1544     FmDesktopAccessiblePriv *priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(desktop_atk);
1545 
1546     return g_list_index(priv->items, item);
1547 }
1548 
fm_desktop_accessible_item_deleted(FmDesktop * desktop,FmDesktopItem * item)1549 static void fm_desktop_accessible_item_deleted(FmDesktop *desktop, FmDesktopItem *item)
1550 {
1551     AtkObject *obj;
1552     FmDesktopAccessiblePriv *priv;
1553     GList *item_atk_l;
1554     FmDesktopItemAccessible *item_atk;
1555     gint index;
1556 
1557     obj = gtk_widget_get_accessible(GTK_WIDGET(desktop));
1558     if (obj != NULL && FM_IS_DESKTOP_ACCESSIBLE(obj))
1559     {
1560         priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(obj);
1561         item_atk_l = fm_desktop_find_accessible_for_item(priv, item);
1562         g_return_if_fail(item_atk_l != NULL);
1563         item_atk = item_atk_l->data;
1564         item_atk->item = NULL;
1565         index = g_list_position(priv->items, item_atk_l);
1566         fm_desktop_item_accessible_add_state(item_atk, ATK_STATE_DEFUNCT);
1567         g_signal_emit_by_name(obj, "children-changed::remove", index, NULL, NULL);
1568         priv->items = g_list_remove_link(priv->items, item_atk_l);
1569         g_object_unref(item_atk);
1570     }
1571 }
1572 
fm_desktop_accessible_item_added(FmDesktop * desktop,FmDesktopItem * item,guint index)1573 static void fm_desktop_accessible_item_added(FmDesktop *desktop, FmDesktopItem *item,
1574                                              guint index)
1575 {
1576     AtkObject *obj;
1577     FmDesktopAccessiblePriv *priv;
1578     FmDesktopItemAccessible *item_atk;
1579 
1580     obj = gtk_widget_get_accessible(GTK_WIDGET(desktop));
1581     if (obj != NULL && FM_IS_DESKTOP_ACCESSIBLE(obj))
1582     {
1583         priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(obj);
1584         item_atk = fm_desktop_item_accessible_new(desktop, item);
1585         g_warn_if_fail(index <= g_list_length(priv->items));
1586         priv->items = g_list_insert(priv->items, g_object_ref(item_atk), index);
1587         g_signal_emit_by_name(obj, "children-changed::add", index, NULL, NULL);
1588     }
1589 }
1590 
fm_desktop_accessible_items_reordered(FmDesktop * desktop,GtkTreeModel * model,gint * new_order)1591 static void fm_desktop_accessible_items_reordered(FmDesktop *desktop,
1592                                                   GtkTreeModel *model,
1593                                                   gint *new_order)
1594 {
1595     AtkObject *obj;
1596     FmDesktopAccessiblePriv *priv;
1597     GList *new_list = NULL;
1598     int length, i;
1599 
1600     obj = gtk_widget_get_accessible(GTK_WIDGET(desktop));
1601     if (obj != NULL && FM_IS_DESKTOP_ACCESSIBLE(obj))
1602     {
1603         priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(obj);
1604         length = gtk_tree_model_iter_n_children(model, NULL);
1605         g_return_if_fail(length == (gint)g_list_length(priv->items));
1606         for (i = 0; i < length; i++)
1607         {
1608             g_assert(new_order[i] >= 0 && new_order[i] < length);
1609             new_list = g_list_prepend(new_list,
1610                                       g_list_nth_data(priv->items, new_order[i]));
1611         }
1612         g_list_free(priv->items);
1613         priv->items = g_list_reverse(new_list);
1614     }
1615 }
1616 
fm_desktop_item_selected_changed(FmDesktop * desktop,FmDesktopItem * item)1617 static void fm_desktop_item_selected_changed(FmDesktop *desktop, FmDesktopItem *item)
1618 {
1619     AtkObject *obj;
1620     FmDesktopAccessiblePriv *priv;
1621     GList *item_atk_l;
1622 
1623     obj = gtk_widget_get_accessible(GTK_WIDGET(desktop));
1624     if (obj != NULL && FM_IS_DESKTOP_ACCESSIBLE(obj))
1625     {
1626         priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(obj);
1627         item_atk_l = fm_desktop_find_accessible_for_item(priv, item);
1628         g_return_if_fail(item_atk_l != NULL);
1629         atk_object_notify_state_change(item_atk_l->data, ATK_STATE_SELECTED,
1630                                        item->is_selected);
1631     }
1632 }
1633 
fm_desktop_accessible_focus_set(FmDesktop * desktop,FmDesktopItem * item)1634 static void fm_desktop_accessible_focus_set(FmDesktop *desktop, FmDesktopItem *item)
1635 {
1636     AtkObject *obj;
1637     FmDesktopAccessiblePriv *priv;
1638     GList *item_atk_l;
1639 
1640     obj = gtk_widget_get_accessible(GTK_WIDGET(desktop));
1641     if (obj != NULL && FM_IS_DESKTOP_ACCESSIBLE(obj))
1642     {
1643         priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(obj);
1644         item_atk_l = fm_desktop_find_accessible_for_item(priv, item);
1645         g_return_if_fail(item_atk_l != NULL);
1646         atk_object_notify_state_change(item_atk_l->data, ATK_STATE_FOCUSED, TRUE);
1647     }
1648 }
1649 
fm_desktop_accessible_focus_unset(FmDesktop * desktop,FmDesktopItem * item)1650 static void fm_desktop_accessible_focus_unset(FmDesktop *desktop, FmDesktopItem *item)
1651 {
1652     AtkObject *obj;
1653     FmDesktopAccessiblePriv *priv;
1654     GList *item_atk_l;
1655 
1656     obj = gtk_widget_get_accessible(GTK_WIDGET(desktop));
1657     if (obj != NULL && FM_IS_DESKTOP_ACCESSIBLE(obj))
1658     {
1659         priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(obj);
1660         item_atk_l = fm_desktop_find_accessible_for_item(priv, item);
1661         g_return_if_fail(item_atk_l != NULL);
1662         atk_object_notify_state_change(item_atk_l->data, ATK_STATE_FOCUSED, FALSE);
1663     }
1664 }
1665 
fm_desktop_accessible_model_removed(FmDesktop * desktop)1666 static void fm_desktop_accessible_model_removed(FmDesktop *desktop)
1667 {
1668     AtkObject *obj;
1669     FmDesktopAccessiblePriv *priv;
1670     FmDesktopItemAccessible *item_atk;
1671 
1672     obj = gtk_widget_get_accessible(GTK_WIDGET(desktop));
1673     if (obj != NULL && FM_IS_DESKTOP_ACCESSIBLE(obj))
1674     {
1675         priv = FM_DESKTOP_ACCESSIBLE_GET_PRIVATE(obj);
1676         while (priv->items)
1677         {
1678             item_atk = priv->items->data;
1679             item_atk->item = NULL;
1680             fm_desktop_item_accessible_add_state(item_atk, ATK_STATE_DEFUNCT);
1681             g_signal_emit_by_name(obj, "children-changed::remove", 0, NULL, NULL);
1682             priv->items = g_list_remove_link(priv->items, priv->items);
1683             g_object_unref(item_atk);
1684         }
1685     }
1686 }
1687 
1688 
1689 /* ---------------------------------------------------------------------
1690     Desktop drawing */
1691 
get_item_rect(FmDesktopItem * item,GdkRectangle * rect)1692 static inline void get_item_rect(FmDesktopItem* item, GdkRectangle* rect)
1693 {
1694     gdk_rectangle_union(&item->icon_rect, &item->text_rect, rect);
1695 }
1696 
is_pos_occupied(FmDesktop * desktop,FmDesktopItem * item)1697 static gboolean is_pos_occupied(FmDesktop* desktop, FmDesktopItem* item)
1698 {
1699     GList* l;
1700     for(l = desktop->fixed_items; l; l=l->next)
1701     {
1702         FmDesktopItem* fixed = (FmDesktopItem*)l->data;
1703         GdkRectangle rect;
1704         get_item_rect(fixed, &rect);
1705         if(gdk_rectangle_intersect(&rect, &item->icon_rect, NULL)
1706          ||gdk_rectangle_intersect(&rect, &item->text_rect, NULL))
1707             return TRUE;
1708     }
1709     return FALSE;
1710 }
1711 
layout_items(FmDesktop * self)1712 static void layout_items(FmDesktop* self)
1713 {
1714     FmDesktopItem* item;
1715     GtkTreeModel* model = self->model ? GTK_TREE_MODEL(self->model) : NULL;
1716     GdkPixbuf* icon;
1717     GtkTreeIter it;
1718     int x, y, bottom;
1719     GtkTextDirection direction = gtk_widget_get_direction(GTK_WIDGET(self));
1720 
1721     y = self->ymargin;
1722     bottom = self->working_area.height - self->ymargin;
1723 
1724     if(!model || !gtk_tree_model_get_iter_first(model, &it))
1725     {
1726         gtk_widget_queue_draw(GTK_WIDGET(self));
1727         return;
1728     }
1729     if(direction != GTK_TEXT_DIR_RTL) /* LTR or NONE */
1730     {
1731         x = self->xmargin;
1732         do
1733         {
1734             item = fm_folder_model_get_item_userdata(self->model, &it);
1735             icon = NULL;
1736             gtk_tree_model_get(model, &it, FM_FOLDER_MODEL_COL_ICON, &icon, -1);
1737             if(item->fixed_pos)
1738                 calc_item_size(self, item, icon);
1739             else
1740             {
1741 _next_position:
1742                 item->area.x = self->working_area.x + x;
1743                 item->area.y = self->working_area.y + y;
1744                 calc_item_size(self, item, icon);
1745                 /* check if item does not fit into space that left */
1746                 if (item->area.y + item->area.height > bottom && y > self->ymargin)
1747                 {
1748                     x += self->cell_w;
1749                     y = self->ymargin;
1750                     goto _next_position;
1751                 }
1752                 /* prepare position for next item */
1753                 while (self->working_area.y + y < item->area.y + item->area.height)
1754                     y += self->cell_h;
1755                 /* check if this position is occupied by a fixed item */
1756                 if(is_pos_occupied(self, item))
1757                     goto _next_position;
1758             }
1759             if(icon)
1760                 g_object_unref(icon);
1761         }
1762         while(gtk_tree_model_iter_next(model, &it));
1763     }
1764     else /* RTL */
1765     {
1766         x = self->working_area.width - self->xmargin - self->cell_w;
1767         do
1768         {
1769             item = fm_folder_model_get_item_userdata(self->model, &it);
1770             icon = NULL;
1771             gtk_tree_model_get(model, &it, FM_FOLDER_MODEL_COL_ICON, &icon, -1);
1772             if(item->fixed_pos)
1773                 calc_item_size(self, item, icon);
1774             else
1775             {
1776 _next_position_rtl:
1777                 item->area.x = self->working_area.x + x;
1778                 item->area.y = self->working_area.y + y;
1779                 calc_item_size(self, item, icon);
1780                 /* check if item does not fit into space that left */
1781                 if (item->area.y + item->area.height > bottom && y > self->ymargin)
1782                 {
1783                     x -= self->cell_w;
1784                     y = self->ymargin;
1785                     goto _next_position_rtl;
1786                 }
1787                 /* prepare position for next item */
1788                 while (self->working_area.y + y < item->area.y + item->area.height)
1789                     y += self->cell_h;
1790                 /* check if this position is occupied by a fixed item */
1791                 if(is_pos_occupied(self, item))
1792                     goto _next_position_rtl;
1793             }
1794             if(icon)
1795                 g_object_unref(icon);
1796         }
1797         while(gtk_tree_model_iter_next(model, &it));
1798     }
1799     gtk_widget_queue_draw(GTK_WIDGET(self));
1800 }
1801 
on_idle_layout(FmDesktop * desktop)1802 static gboolean on_idle_layout(FmDesktop* desktop)
1803 {
1804     desktop->idle_layout = 0;
1805     desktop->layout_pending = FALSE;
1806     layout_items(desktop);
1807     return FALSE;
1808 }
1809 
queue_layout_items(FmDesktop * desktop)1810 static void queue_layout_items(FmDesktop* desktop)
1811 {
1812     /* don't try to layout items until config is loaded,
1813        this may be cause of the bug #927 on SF.net */
1814     if (!gtk_widget_get_realized(GTK_WIDGET(desktop)))
1815         desktop->layout_pending = TRUE;
1816     else if (0 == desktop->idle_layout)
1817         desktop->idle_layout = gdk_threads_add_idle((GSourceFunc)on_idle_layout, desktop);
1818 }
1819 
paint_item(FmDesktop * self,FmDesktopItem * item,cairo_t * cr,GdkRectangle * expose_area,GdkPixbuf * icon)1820 static void paint_item(FmDesktop* self, FmDesktopItem* item, cairo_t* cr, GdkRectangle* expose_area, GdkPixbuf* icon)
1821 {
1822 #if GTK_CHECK_VERSION(3, 0, 0)
1823     GtkStyleContext* style;
1824 #else
1825     GtkStyle* style;
1826 #endif
1827     GtkWidget* widget = (GtkWidget*)self;
1828     GtkCellRendererState state = 0;
1829 #if GTK_CHECK_VERSION(3, 0, 0)
1830     GdkRGBA rgba;
1831 #else
1832     GdkWindow* window;
1833 #endif
1834     int text_x, text_y;
1835 
1836     /* don't draw dragged items on desktop, they are moved with mouse */
1837     if (item->is_selected && self->dragging)
1838         return;
1839 
1840 #if GTK_CHECK_VERSION(3, 0, 0)
1841     style = gtk_widget_get_style_context(widget);
1842 #else
1843     style = gtk_widget_get_style(widget);
1844     window = gtk_widget_get_window(widget);
1845 #endif
1846 
1847     pango_layout_set_text(self->pl, NULL, 0);
1848     pango_layout_set_width(self->pl, self->pango_text_w);
1849     pango_layout_set_height(self->pl, self->pango_text_h);
1850 
1851     pango_layout_set_text(self->pl, fm_file_info_get_disp_name(item->fi), -1);
1852 
1853     /* FIXME: do we need to cache this? */
1854     text_x = item->area.x + (self->cell_w - self->text_w)/2 + 2;
1855     text_y = item->text_rect.y + 2;
1856 
1857     if(item->is_selected || item == self->drop_hilight) /* draw background for text label */
1858     {
1859         state = GTK_CELL_RENDERER_SELECTED;
1860 
1861         cairo_save(cr);
1862         gdk_cairo_rectangle(cr, &item->text_rect);
1863 #if GTK_CHECK_VERSION(3, 0, 0)
1864         gtk_style_context_get_background_color(style, GTK_STATE_FLAG_SELECTED, &rgba);
1865         gdk_cairo_set_source_rgba(cr, &rgba);
1866 #else
1867         gdk_cairo_set_source_color(cr, &style->bg[GTK_STATE_SELECTED]);
1868 #endif
1869         cairo_clip(cr);
1870         cairo_paint(cr);
1871         cairo_restore(cr);
1872 #if GTK_CHECK_VERSION(3, 0, 0)
1873         gtk_style_context_get_color(style, GTK_STATE_FLAG_SELECTED, &rgba);
1874         gdk_cairo_set_source_rgba(cr, &rgba);
1875 #else
1876         gdk_cairo_set_source_color(cr, &style->fg[GTK_STATE_SELECTED]);
1877 #endif
1878     }
1879     else
1880     {
1881         /* the shadow */
1882         gdk_cairo_set_source_color(cr, &self->conf.desktop_shadow);
1883         cairo_move_to(cr, text_x + 1, text_y + 1);
1884         pango_cairo_show_layout(cr, self->pl);
1885         gdk_cairo_set_source_color(cr, &self->conf.desktop_fg);
1886     }
1887     /* real text */
1888     cairo_move_to(cr, text_x, text_y);
1889     /* FIXME: should we check if pango is 1.10 at least? */
1890     pango_cairo_show_layout(cr, self->pl);
1891     pango_layout_set_text(self->pl, NULL, 0);
1892 
1893     if(item == self->focus && gtk_widget_has_focus(widget))
1894 #if GTK_CHECK_VERSION(3, 0, 0)
1895         gtk_render_focus(style, cr,
1896 #else
1897         gtk_paint_focus(style, window, gtk_widget_get_state(widget),
1898                         expose_area, widget, "icon_view",
1899 #endif
1900                         item->text_rect.x, item->text_rect.y, item->text_rect.width, item->text_rect.height);
1901 
1902     if(item == self->hover_item) /* hovered */
1903         g_object_set(G_OBJECT(self), "tooltip-text", fm_file_info_get_disp_name(item->fi), NULL);
1904     else
1905         g_object_set(G_OBJECT(self), "tooltip-text", NULL, NULL);
1906 
1907     /* draw the icon */
1908     g_object_set(self->icon_render, "pixbuf", icon, "info", item->fi, NULL);
1909 #if GTK_CHECK_VERSION(3, 0, 0)
1910     gtk_cell_renderer_render(GTK_CELL_RENDERER(self->icon_render), cr, widget, &item->icon_rect, &item->icon_rect, state);
1911 #else
1912     gtk_cell_renderer_render(GTK_CELL_RENDERER(self->icon_render), window, widget, &item->icon_rect, &item->icon_rect, expose_area, state);
1913 #endif
1914 }
1915 
redraw_item(FmDesktop * desktop,FmDesktopItem * item)1916 static void redraw_item(FmDesktop* desktop, FmDesktopItem* item)
1917 {
1918     GdkRectangle rect;
1919     get_item_rect(item, &rect);
1920     --rect.x;
1921     --rect.y;
1922     rect.width += 2;
1923     rect.height += 2;
1924     gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(desktop)), &rect, FALSE);
1925 }
1926 
move_item(FmDesktop * desktop,FmDesktopItem * item,int x,int y,gboolean redraw)1927 static void move_item(FmDesktop* desktop, FmDesktopItem* item, int x, int y, gboolean redraw)
1928 {
1929     int dx, dy;
1930     /* this call invalid the area occupied by the item and a redraw
1931      * is queued. */
1932     if(redraw)
1933         redraw_item(desktop, item);
1934 
1935     /* correct coords to put item within working area still */
1936     if (x > desktop->working_area.x + desktop->working_area.width - desktop->xmargin - item->area.width)
1937         x = desktop->working_area.x + desktop->working_area.width - desktop->xmargin - item->area.width;
1938     if (x < desktop->working_area.x + desktop->xmargin)
1939         x = desktop->working_area.x + desktop->xmargin;
1940     if (y > desktop->working_area.y + desktop->working_area.height - desktop->ymargin - item->area.height)
1941         y = desktop->working_area.y + desktop->working_area.height - desktop->ymargin - item->area.height;
1942     if (y < desktop->working_area.y + desktop->ymargin)
1943         y = desktop->working_area.y + desktop->ymargin;
1944 
1945     dx = x - item->area.x;
1946     dy = y - item->area.y;
1947 
1948     item->area.x = x;
1949     item->area.y = y;
1950 
1951     /* calc_item_size(desktop, item); */
1952     item->icon_rect.x += dx;
1953     item->icon_rect.y += dy;
1954     item->text_rect.x += dx;
1955     item->text_rect.y += dy;
1956 
1957     /* make the item use customized fixed position. */
1958     if(!item->fixed_pos)
1959     {
1960         item->fixed_pos = TRUE;
1961         desktop->fixed_items = g_list_prepend(desktop->fixed_items, item);
1962     }
1963 
1964     /* move the item to a new place, and queue a redraw for the new rect. */
1965     if(redraw)
1966         redraw_item(desktop, item);
1967 }
1968 
calc_rubber_banding_rect(FmDesktop * self,int x,int y,GdkRectangle * rect)1969 static void calc_rubber_banding_rect(FmDesktop* self, int x, int y, GdkRectangle* rect)
1970 {
1971     int x1, x2, y1, y2;
1972     if(self->drag_start_x < x)
1973     {
1974         x1 = self->drag_start_x;
1975         x2 = x;
1976     }
1977     else
1978     {
1979         x1 = x;
1980         x2 = self->drag_start_x;
1981     }
1982 
1983     if(self->drag_start_y < y)
1984     {
1985         y1 = self->drag_start_y;
1986         y2 = y;
1987     }
1988     else
1989     {
1990         y1 = y;
1991         y2 = self->drag_start_y;
1992     }
1993 
1994     rect->x = x1;
1995     rect->y = y1;
1996     rect->width = x2 - x1;
1997     rect->height = y2 - y1;
1998 }
1999 
update_rubberbanding(FmDesktop * self,int newx,int newy)2000 static void update_rubberbanding(FmDesktop* self, int newx, int newy)
2001 {
2002     GtkTreeModel* model = self->model ? GTK_TREE_MODEL(self->model) : NULL;
2003     GtkTreeIter it;
2004     GdkRectangle old_rect, new_rect;
2005     //GdkRegion *region;
2006     GdkWindow *window;
2007 
2008     window = gtk_widget_get_window(GTK_WIDGET(self));
2009 
2010     calc_rubber_banding_rect(self, self->rubber_bending_x, self->rubber_bending_y, &old_rect);
2011     calc_rubber_banding_rect(self, newx, newy, &new_rect);
2012 
2013     gdk_window_invalidate_rect(window, &old_rect, FALSE);
2014     gdk_window_invalidate_rect(window, &new_rect, FALSE);
2015 //    gdk_window_clear_area(((GtkWidget*)self)->window, new_rect.x, new_rect.y, new_rect.width, new_rect.height);
2016 /*
2017     region = gdk_region_rectangle(&old_rect);
2018     gdk_region_union_with_rect(region, &new_rect);
2019 
2020 //    gdk_window_invalidate_region(((GtkWidget*)self)->window, &region, TRUE);
2021 
2022     gdk_region_destroy(region);
2023 */
2024     self->rubber_bending_x = newx;
2025     self->rubber_bending_y = newy;
2026 
2027     /* update selection */
2028     if(model && gtk_tree_model_get_iter_first(model, &it)) do
2029     {
2030         FmDesktopItem* item = fm_folder_model_get_item_userdata(self->model, &it);
2031         gboolean selected;
2032         if(gdk_rectangle_intersect(&new_rect, &item->icon_rect, NULL) ||
2033             gdk_rectangle_intersect(&new_rect, &item->text_rect, NULL))
2034             selected = TRUE;
2035         else
2036             selected = FALSE;
2037 
2038         /* we cannot compare booleans, TRUE may be 1 or -1 */
2039         if ((item->is_rubber_banded && !selected) ||
2040             (!item->is_rubber_banded && selected))
2041         {
2042             item->is_selected = selected;
2043             redraw_item(self, item);
2044             fm_desktop_item_selected_changed(self, item);
2045         }
2046         item->is_rubber_banded = self->rubber_bending && selected;
2047     }
2048     while(gtk_tree_model_iter_next(model, &it));
2049 }
2050 
2051 
paint_rubber_banding_rect(FmDesktop * self,cairo_t * cr,GdkRectangle * expose_area)2052 static void paint_rubber_banding_rect(FmDesktop* self, cairo_t* cr, GdkRectangle* expose_area)
2053 {
2054     GtkWidget* widget = (GtkWidget*)self;
2055     GdkRectangle rect;
2056     GdkColor clr;
2057     guchar alpha;
2058 
2059     calc_rubber_banding_rect(self, self->rubber_bending_x, self->rubber_bending_y, &rect);
2060 
2061     if(rect.width <= 0 || rect.height <= 0)
2062         return;
2063 
2064     if(!gdk_rectangle_intersect(expose_area, &rect, &rect))
2065         return;
2066 /*
2067     gtk_widget_style_get(icon_view,
2068                         "selection-box-color", &clr,
2069                         "selection-box-alpha", &alpha,
2070                         NULL);
2071 */
2072     clr = gtk_widget_get_style (widget)->base[GTK_STATE_SELECTED];
2073     alpha = 64;  /* FIXME: should be themable in the future */
2074 
2075     cairo_save(cr);
2076     cairo_set_source_rgba(cr, (gdouble)clr.red/65535, (gdouble)clr.green/65536, (gdouble)clr.blue/65535, (gdouble)alpha/100);
2077     gdk_cairo_rectangle(cr, &rect);
2078     cairo_clip (cr);
2079     cairo_paint (cr);
2080     gdk_cairo_set_source_color(cr, &clr);
2081     cairo_rectangle (cr, rect.x + 0.5, rect.y + 0.5, rect.width - 1, rect.height - 1);
2082     cairo_stroke(cr);
2083     cairo_restore(cr);
2084 }
2085 
_free_cache_image(FmBackgroundCache * cache)2086 static void _free_cache_image(FmBackgroundCache *cache)
2087 {
2088 #if GTK_CHECK_VERSION(3, 0, 0)
2089     XFreePixmap(cairo_xlib_surface_get_display(cache->bg),
2090                 cairo_xlib_surface_get_drawable(cache->bg));
2091     cairo_surface_destroy(cache->bg);
2092 #else
2093     g_object_unref(cache->bg);
2094 #endif
2095     cache->bg = NULL;
2096     cache->wallpaper_mode = FM_WP_COLOR; /* for cache check */
2097 }
2098 
_clear_bg_cache(FmDesktop * self)2099 static void _clear_bg_cache(FmDesktop *self)
2100 {
2101     while(self->cache)
2102     {
2103         FmBackgroundCache *bg = self->cache;
2104 
2105         self->cache = bg->next;
2106         _free_cache_image(bg);
2107         g_free(bg->filename);
2108         g_free(bg);
2109     }
2110 }
2111 
update_background(FmDesktop * desktop,int is_it)2112 static void update_background(FmDesktop* desktop, int is_it)
2113 {
2114     GtkWidget* widget = (GtkWidget*)desktop;
2115     GdkPixbuf* pix, *scaled;
2116     cairo_t* cr;
2117     GdkScreen *screen = gtk_widget_get_screen(widget);
2118     GdkWindow* root = gdk_screen_get_root_window(screen);
2119     GdkWindow *window = gtk_widget_get_window(widget);
2120     FmBackgroundCache *cache;
2121 #if GTK_CHECK_VERSION(3, 0, 0)
2122     cairo_pattern_t *pattern;
2123 #endif
2124 
2125     Display* xdisplay;
2126     Pixmap xpixmap;
2127     Window xroot;
2128     int screen_num = gdk_screen_get_number(screen);
2129 
2130     char *wallpaper;
2131 
2132     if (!desktop->conf.wallpaper_common)
2133     {
2134         guint32 cur_desktop = desktop->cur_desktop;
2135 
2136         if(is_it >= 0) /* signal "changed::wallpaper" */
2137         {
2138             int i;
2139 
2140             wallpaper = desktop->conf.wallpaper;
2141             if((gint)cur_desktop >= desktop->conf.wallpapers_configured)
2142             {
2143                 desktop->conf.wallpapers = g_renew(char *, desktop->conf.wallpapers, cur_desktop + 1);
2144                 /* fill the gap with current wallpaper */
2145                 for(i = MAX(desktop->conf.wallpapers_configured,0); i < (int)cur_desktop; i++)
2146                     desktop->conf.wallpapers[i] = g_strdup(wallpaper);
2147                 desktop->conf.wallpapers[cur_desktop] = NULL;
2148                 desktop->conf.wallpapers_configured = cur_desktop + 1;
2149             }
2150             /* free old image if it's not used anymore */
2151             else if (g_strcmp0(desktop->conf.wallpapers[cur_desktop], wallpaper))
2152             {
2153                 wallpaper = desktop->conf.wallpapers[cur_desktop];
2154                 if (wallpaper)
2155                 {
2156                     for (i = 0; i < desktop->conf.wallpapers_configured; i++)
2157                         if (i != (int)cur_desktop && /* test if it's used */
2158                             g_strcmp0(desktop->conf.wallpapers[i], wallpaper) == 0)
2159                             break;
2160                     if (i == desktop->conf.wallpapers_configured) /* no matches */
2161                     {
2162                         for (cache = desktop->cache; cache; cache = cache->next)
2163                             if (strcmp(wallpaper, cache->filename) == 0)
2164                                 break;
2165                         if (cache)
2166                             _free_cache_image(cache);
2167                     }
2168                     g_free(wallpaper);
2169                 }
2170                 wallpaper = desktop->conf.wallpaper;
2171                 desktop->conf.wallpapers[cur_desktop] = g_strdup(wallpaper);
2172             }
2173         }
2174         else /* desktop refresh */
2175         {
2176             if((gint)cur_desktop < desktop->conf.wallpapers_configured)
2177                 wallpaper = desktop->conf.wallpapers[cur_desktop];
2178             else
2179                 wallpaper = NULL;
2180             if (wallpaper == NULL && desktop->conf.wallpaper != NULL)
2181             {
2182                 /* if we have wallpaper set for previous desktop but have not
2183                    for current one, it may mean one of two cases:
2184                    - we expanded number of desktops;
2185                    - we recently switched wallpaper_common off.
2186                    If we selected to use wallpaper image but current desktop
2187                    has no image set (i.e. one of cases above is happening),
2188                    it is reasonable and correct to use last selected image for
2189                    newly selected desktop instead of show plain color on it */
2190                 wallpaper = desktop->conf.wallpaper;
2191                 if ((gint)cur_desktop < desktop->conf.wallpapers_configured)
2192                     /* this means desktop->conf.wallpapers[cur_desktop] is NULL,
2193                        see above, we have to update it too in this case */
2194                     desktop->conf.wallpapers[cur_desktop] = g_strdup(wallpaper);
2195             }
2196             else
2197             {
2198                 g_free(desktop->conf.wallpaper); /* update to current desktop */
2199                 desktop->conf.wallpaper = g_strdup(wallpaper);
2200             }
2201         }
2202     }
2203     else
2204     {
2205         _clear_bg_cache(desktop);
2206         wallpaper = desktop->conf.wallpaper;
2207     }
2208 
2209     if(desktop->conf.wallpaper_mode != FM_WP_COLOR && wallpaper && *wallpaper)
2210     {
2211         struct stat st; /* for mtime */
2212 
2213         /* bug #3613571 - replacing the file will not affect the desktop
2214            we will call stat on each desktop change but it's inevitable */
2215         if (stat(wallpaper, &st) < 0)
2216             st.st_mtime = 0;
2217         for(cache = desktop->cache; cache; cache = cache->next)
2218             if(strcmp(wallpaper, cache->filename) == 0)
2219                 break;
2220         if(cache && cache->wallpaper_mode == desktop->conf.wallpaper_mode
2221            && st.st_mtime == cache->mtime)
2222             pix = NULL; /* no new pix for it */
2223         else if((pix = gdk_pixbuf_new_from_file(wallpaper, NULL)))
2224         {
2225             if(cache)
2226             {
2227                 /* the same file but mode was changed */
2228                 _free_cache_image(cache);
2229             }
2230             else if(desktop->cache)
2231             {
2232                 for(cache = desktop->cache; cache->next; )
2233                     cache = cache->next;
2234                 cache->next = g_new0(FmBackgroundCache, 1);
2235                 cache = cache->next;
2236             }
2237             else
2238                 desktop->cache = cache = g_new0(FmBackgroundCache, 1);
2239             if(!cache->filename)
2240                 cache->filename = g_strdup(wallpaper);
2241             cache->mtime = st.st_mtime;
2242             g_debug("adding new FmBackgroundCache for %s", wallpaper);
2243         }
2244         else
2245             /* if there is a cached image but with another mode and we cannot
2246                get it from file for new mode then just leave it in cache as is */
2247             cache = NULL;
2248     }
2249     else
2250         cache = NULL;
2251 
2252     if(!cache) /* solid color only */
2253     {
2254 #if GTK_CHECK_VERSION(3, 0, 0)
2255         pattern = cairo_pattern_create_rgb(desktop->conf.desktop_bg.red / 65535.0,
2256                                            desktop->conf.desktop_bg.green / 65535.0,
2257                                            desktop->conf.desktop_bg.blue / 65535.0);
2258         gdk_window_set_background_pattern(window, pattern);
2259         cairo_pattern_destroy(pattern);
2260 #else
2261         GdkColor bg = desktop->conf.desktop_bg;
2262 
2263         gdk_colormap_alloc_color(gdk_drawable_get_colormap(window), &bg, FALSE, TRUE);
2264         gdk_window_set_back_pixmap(window, NULL, FALSE);
2265         gdk_window_set_background(window, &bg);
2266 #endif
2267         gdk_window_invalidate_rect(window, NULL, TRUE);
2268         return;
2269     }
2270 
2271     if(!cache->bg) /* no cached image found */
2272     {
2273         int src_w, src_h;
2274         int dest_w, dest_h;
2275         int x = 0, y = 0;
2276         src_w = gdk_pixbuf_get_width(pix);
2277         src_h = gdk_pixbuf_get_height(pix);
2278         if(desktop->conf.wallpaper_mode == FM_WP_TILE)
2279         {
2280             dest_w = src_w;
2281             dest_h = src_h;
2282         }
2283         else
2284         {
2285             GdkRectangle geom;
2286             gdk_screen_get_monitor_geometry(screen, desktop->monitor, &geom);
2287             if (desktop->conf.wallpaper_mode == FM_WP_SCREEN)
2288             {
2289                 dest_w = gdk_screen_get_width(screen);
2290                 dest_h = gdk_screen_get_height(screen);
2291                 x = -geom.x;
2292                 y = -geom.y;
2293             }
2294             else
2295             {
2296                 dest_w = geom.width;
2297                 dest_h = geom.height;
2298             }
2299         }
2300 #if GTK_CHECK_VERSION(3, 0, 0)
2301         xdisplay = GDK_WINDOW_XDISPLAY(root);
2302         /* this code is taken from libgnome-desktop */
2303         xpixmap = XCreatePixmap(xdisplay, RootWindow(xdisplay, screen_num),
2304                                 dest_w, dest_h, DefaultDepth(xdisplay, screen_num));
2305         cache->bg = cairo_xlib_surface_create(xdisplay, xpixmap,
2306                                               GDK_VISUAL_XVISUAL(gdk_screen_get_system_visual(screen)),
2307                                               dest_w, dest_h);
2308         cr = cairo_create(cache->bg);
2309 #else
2310         cache->bg = gdk_pixmap_new(window, dest_w, dest_h, -1);
2311         cr = gdk_cairo_create(cache->bg);
2312 #endif
2313         if(gdk_pixbuf_get_has_alpha(pix)
2314             || desktop->conf.wallpaper_mode == FM_WP_CENTER
2315             || desktop->conf.wallpaper_mode == FM_WP_FIT)
2316         {
2317             gdk_cairo_set_source_color(cr, &desktop->conf.desktop_bg);
2318             cairo_rectangle(cr, 0, 0, dest_w, dest_h);
2319             cairo_fill(cr);
2320         }
2321 
2322         switch(desktop->conf.wallpaper_mode)
2323         {
2324         case FM_WP_TILE:
2325             break;
2326         case FM_WP_STRETCH:
2327         case FM_WP_SCREEN:
2328             if(dest_w == src_w && dest_h == src_h)
2329                 scaled = (GdkPixbuf*)g_object_ref(pix);
2330             else
2331                 scaled = gdk_pixbuf_scale_simple(pix, dest_w, dest_h, GDK_INTERP_BILINEAR);
2332             g_object_unref(pix);
2333             pix = scaled;
2334             break;
2335         case FM_WP_FIT:
2336         case FM_WP_CROP:
2337             if(dest_w != src_w || dest_h != src_h)
2338             {
2339                 gdouble w_ratio = (float)dest_w / src_w;
2340                 gdouble h_ratio = (float)dest_h / src_h;
2341                 gdouble ratio = (desktop->conf.wallpaper_mode == FM_WP_FIT)
2342                     ? MIN(w_ratio, h_ratio)
2343                     : MAX(w_ratio, h_ratio);
2344                 if(ratio != 1.0)
2345                 {
2346                     src_w *= ratio;
2347                     src_h *= ratio;
2348                     scaled = gdk_pixbuf_scale_simple(pix, src_w, src_h, GDK_INTERP_BILINEAR);
2349                     g_object_unref(pix);
2350                     pix = scaled;
2351                 }
2352             }
2353             /* continue to execute code in case FM_WP_CENTER */
2354         case FM_WP_CENTER:
2355             x = (dest_w - src_w)/2;
2356             y = (dest_h - src_h)/2;
2357             break;
2358         case FM_WP_COLOR: ; /* handled above */
2359         }
2360         gdk_cairo_set_source_pixbuf(cr, pix, x, y);
2361         cairo_paint(cr);
2362         cairo_destroy(cr);
2363         cache->wallpaper_mode = desktop->conf.wallpaper_mode;
2364     }
2365 #if GTK_CHECK_VERSION(3, 0, 0)
2366     pattern = cairo_pattern_create_for_surface(cache->bg);
2367     gdk_window_set_background_pattern(window, pattern);
2368     cairo_pattern_destroy(pattern);
2369 #else
2370     gdk_window_set_back_pixmap(window, cache->bg, FALSE);
2371 #endif
2372 
2373     /* set root map here */
2374     xdisplay = GDK_WINDOW_XDISPLAY(root);
2375     xroot = RootWindow(xdisplay, screen_num);
2376 
2377 #if GTK_CHECK_VERSION(3, 0, 0)
2378     xpixmap = cairo_xlib_surface_get_drawable(cache->bg);
2379 #else
2380     xpixmap = GDK_WINDOW_XWINDOW(cache->bg);
2381 #endif
2382 
2383     XChangeProperty(xdisplay, GDK_WINDOW_XID(root),
2384                     XA_XROOTMAP_ID, XA_PIXMAP, 32, PropModeReplace, (guchar*)&xpixmap, 1);
2385 
2386     XGrabServer (xdisplay);
2387 
2388 #if 0
2389     result = XGetWindowProperty (display,
2390                                  RootWindow (display, screen_num),
2391                                  gdk_x11_get_xatom_by_name ("ESETROOT_PMAP_ID"),
2392                                  0L, 1L, False, XA_PIXMAP,
2393                                  &type, &format, &nitems,
2394                                  &bytes_after,
2395                                  &data_esetroot);
2396 
2397     if (data_esetroot != NULL) {
2398             if (result == Success && type == XA_PIXMAP &&
2399                 format == 32 &&
2400                 nitems == 1) {
2401                     gdk_error_trap_push ();
2402                     XKillClient (display, *(Pixmap *)data_esetroot);
2403                     gdk_error_trap_pop_ignored ();
2404             }
2405             XFree (data_esetroot);
2406     }
2407 
2408     XChangeProperty (display, RootWindow (display, screen_num),
2409                      gdk_x11_get_xatom_by_name ("ESETROOT_PMAP_ID"),
2410                      XA_PIXMAP, 32, PropModeReplace,
2411                      (guchar *) &xpixmap, 1);
2412 #endif
2413 
2414     XChangeProperty(xdisplay, xroot, XA_XROOTPMAP_ID, XA_PIXMAP, 32,
2415                     PropModeReplace, (guchar*)&xpixmap, 1);
2416 
2417     XSetWindowBackgroundPixmap(xdisplay, xroot, xpixmap);
2418     XClearWindow(xdisplay, xroot);
2419 
2420     XFlush(xdisplay);
2421     XUngrabServer(xdisplay);
2422 
2423     if(pix)
2424         g_object_unref(pix);
2425 
2426     gdk_window_invalidate_rect(window, NULL, TRUE);
2427 }
2428 
2429 
2430 /* ---------------------------------------------------------------------
2431     FmFolderModel signal handlers */
2432 
on_row_deleting(FmFolderModel * model,GtkTreePath * tp,GtkTreeIter * iter,gpointer data,FmDesktop * desktop)2433 static void on_row_deleting(FmFolderModel* model, GtkTreePath* tp,
2434                             GtkTreeIter* iter, gpointer data, FmDesktop* desktop)
2435 {
2436     GList *l;
2437 
2438     for(l = desktop->fixed_items; l; l = l->next)
2439         if(l->data == data)
2440         {
2441             desktop->fixed_items = g_list_delete_link(desktop->fixed_items, l);
2442             break;
2443         }
2444     if((gpointer)desktop->focus == data)
2445     {
2446         GtkTreeIter it = *iter;
2447         fm_desktop_accessible_focus_unset(desktop, data);
2448         if(gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &it))
2449             desktop->focus = fm_folder_model_get_item_userdata(model, &it);
2450         else
2451         {
2452             if(gtk_tree_path_prev(tp))
2453             {
2454                 gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &it, tp);
2455                 gtk_tree_path_next(tp);
2456                 desktop->focus = fm_folder_model_get_item_userdata(model, &it);
2457             }
2458             else
2459                 desktop->focus = NULL;
2460         }
2461         if (desktop->focus)
2462             fm_desktop_accessible_focus_set(desktop, desktop->focus);
2463     }
2464     if((gpointer)desktop->drop_hilight == data)
2465         desktop->drop_hilight = NULL;
2466     if((gpointer)desktop->hover_item == data)
2467     {
2468         desktop->hover_item = NULL;
2469         /* bug #3615015: after deleting the item tooltip stuck on the desktop */
2470         g_object_set(G_OBJECT(desktop), "tooltip-text", NULL, NULL);
2471     }
2472     fm_desktop_accessible_item_deleted(desktop, data);
2473     desktop_item_free(data);
2474 }
2475 
on_row_inserted(FmFolderModel * mod,GtkTreePath * tp,GtkTreeIter * it,FmDesktop * desktop)2476 static void on_row_inserted(FmFolderModel* mod, GtkTreePath* tp, GtkTreeIter* it, FmDesktop* desktop)
2477 {
2478     FmDesktopItem* item = desktop_item_new(mod, it);
2479     gint *indices = gtk_tree_path_get_indices(tp);
2480     fm_desktop_accessible_item_added(desktop, item, indices[0]);
2481     fm_folder_model_set_item_userdata(mod, it, item);
2482     queue_layout_items(desktop);
2483 }
2484 
on_row_deleted(FmFolderModel * mod,GtkTreePath * tp,FmDesktop * desktop)2485 static void on_row_deleted(FmFolderModel* mod, GtkTreePath* tp, FmDesktop* desktop)
2486 {
2487     queue_layout_items(desktop);
2488 }
2489 
on_row_changed(FmFolderModel * model,GtkTreePath * tp,GtkTreeIter * it,FmDesktop * desktop)2490 static void on_row_changed(FmFolderModel* model, GtkTreePath* tp, GtkTreeIter* it, FmDesktop* desktop)
2491 {
2492     FmDesktopItem* item = fm_folder_model_get_item_userdata(model, it);
2493     GdkPixbuf *icon;
2494 
2495     fm_file_info_unref(item->fi);
2496     gtk_tree_model_get(GTK_TREE_MODEL(model), it,
2497                        FM_FOLDER_MODEL_COL_INFO, &item->fi,
2498                        FM_FOLDER_MODEL_COL_ICON, &icon, -1);
2499     fm_file_info_ref(item->fi);
2500 
2501     /* we need to redraw old area as we changing data */
2502     redraw_item(desktop, item);
2503     calc_item_size(desktop, item, icon);
2504     if (icon)
2505         g_object_unref(icon);
2506     redraw_item(desktop, item);
2507     /* queue_layout_items(desktop); */
2508 }
2509 
on_rows_reordered(FmFolderModel * model,GtkTreePath * parent_tp,GtkTreeIter * parent_it,gpointer new_order,FmDesktop * desktop)2510 static void on_rows_reordered(FmFolderModel* model, GtkTreePath* parent_tp, GtkTreeIter* parent_it, gpointer new_order, FmDesktop* desktop)
2511 {
2512     fm_desktop_accessible_items_reordered(desktop, GTK_TREE_MODEL(model), new_order);
2513     queue_layout_items(desktop);
2514 }
2515 
2516 
2517 /* ---------------------------------------------------------------------
2518     Events handlers */
2519 
update_working_area(FmDesktop * desktop)2520 static void update_working_area(FmDesktop* desktop)
2521 {
2522     GdkScreen* screen = gtk_widget_get_screen((GtkWidget*)desktop);
2523     GdkRectangle geom;
2524 #if GTK_CHECK_VERSION(3, 4, 0)
2525     gdk_screen_get_monitor_workarea(screen, desktop->monitor, &desktop->working_area);
2526     /* we need working area coordinates within the monitor not the screen */
2527     gdk_screen_get_monitor_geometry(screen, desktop->monitor, &geom);
2528     desktop->working_area.x -= geom.x;
2529     desktop->working_area.y -= geom.y;
2530 #else
2531     GdkWindow* root = gdk_screen_get_root_window(screen);
2532     Atom ret_type;
2533     gulong len, after;
2534     int format;
2535     guchar* prop;
2536     guint32 n_desktops, cur_desktop;
2537     gulong* working_area;
2538 
2539     /* default to screen size */
2540     gdk_screen_get_monitor_geometry(screen, desktop->monitor, &geom);
2541     desktop->working_area.width = geom.width;
2542     desktop->working_area.height = geom.height;
2543 
2544     if(XGetWindowProperty(GDK_WINDOW_XDISPLAY(root), GDK_WINDOW_XID(root),
2545                        XA_NET_NUMBER_OF_DESKTOPS, 0, 1, False, XA_CARDINAL, &ret_type,
2546                        &format, &len, &after, &prop) != Success)
2547         goto _out;
2548     if(!prop)
2549         goto _out;
2550     n_desktops = *(guint32*)prop;
2551     XFree(prop);
2552 
2553     if(XGetWindowProperty(GDK_WINDOW_XDISPLAY(root), GDK_WINDOW_XID(root),
2554                        XA_NET_CURRENT_DESKTOP, 0, 1, False, XA_CARDINAL, &ret_type,
2555                        &format, &len, &after, &prop) != Success)
2556         goto _out;
2557     if(!prop)
2558         goto _out;
2559     cur_desktop = *(guint32*)prop;
2560     XFree(prop);
2561 
2562     if(XGetWindowProperty(GDK_WINDOW_XDISPLAY(root), GDK_WINDOW_XID(root),
2563                        XA_NET_WORKAREA, 0, 4 * 32, False, AnyPropertyType, &ret_type,
2564                        &format, &len, &after, &prop) != Success)
2565         goto _out;
2566     if(ret_type == None || format == 0 || len != n_desktops*4)
2567     {
2568         if(prop)
2569             XFree(prop);
2570         goto _out;
2571     }
2572     working_area = ((gulong*)prop) + cur_desktop * 4;
2573 
2574     desktop->working_area.x = (gint)working_area[0] - geom.x;
2575     desktop->working_area.y = (gint)working_area[1] - geom.y;
2576     if(desktop->working_area.x > 0 &&
2577        desktop->working_area.x < desktop->working_area.width)
2578         desktop->working_area.width -= desktop->working_area.x;
2579     if(desktop->working_area.y > 0 &&
2580        desktop->working_area.y < desktop->working_area.height)
2581         desktop->working_area.height -= desktop->working_area.y;
2582     if(desktop->working_area.x + (gint)working_area[2] < desktop->working_area.width)
2583         desktop->working_area.width = working_area[2] + desktop->working_area.x;
2584     if(desktop->working_area.y + (gint)working_area[3] < desktop->working_area.height)
2585         desktop->working_area.height = working_area[3] + desktop->working_area.y;
2586     g_debug("got working area: %d.%d.%d.%d", desktop->working_area.x, desktop->working_area.y,
2587             desktop->working_area.width, desktop->working_area.height);
2588     /* we need working area coordinates within the monitor not the screen */
2589     desktop->working_area.x = MAX(0, desktop->working_area.x);
2590     desktop->working_area.y = MAX(0, desktop->working_area.y);
2591 
2592     XFree(prop);
2593 _out:
2594 #endif
2595     queue_layout_items(desktop);
2596     return;
2597 }
2598 
on_root_event(GdkXEvent * xevent,GdkEvent * event,gpointer data)2599 static GdkFilterReturn on_root_event(GdkXEvent *xevent, GdkEvent *event, gpointer data)
2600 {
2601     XPropertyEvent * evt = (XPropertyEvent*) xevent;
2602     FmDesktop* self = (FmDesktop*)data;
2603     if (evt->type == PropertyNotify)
2604     {
2605         if(evt->atom == XA_NET_WORKAREA)
2606             update_working_area(self);
2607         else if(evt->atom == XA_NET_CURRENT_DESKTOP)
2608         {
2609             gint desktop = get_desktop_for_root_window(gdk_screen_get_root_window(
2610                                     gtk_widget_get_screen(GTK_WIDGET(data))));
2611             if(desktop >= 0)
2612             {
2613                 self->cur_desktop = (guint)desktop;
2614                 if(!self->conf.wallpaper_common)
2615                     update_background(self, -1);
2616             }
2617         }
2618     }
2619     return GDK_FILTER_CONTINUE;
2620 }
2621 
on_screen_size_changed(GdkScreen * screen,FmDesktop * desktop)2622 static void on_screen_size_changed(GdkScreen* screen, FmDesktop* desktop)
2623 {
2624     GdkRectangle geom;
2625     if (desktop->monitor >= gdk_screen_get_n_monitors(screen))
2626     {
2627         gint i;
2628         /* our monitor was disconnected... remove FmDesktop now! */
2629         for (i = 0; i < n_screens; i++)
2630             if (desktops[i] == desktop)
2631                 break;
2632         if (i < n_screens)
2633             desktops[i] = fm_desktop_new(screen, desktop->monitor ? -2 : -1);
2634         gtk_widget_destroy(GTK_WIDGET(desktop));
2635         return;
2636     }
2637     gdk_screen_get_monitor_geometry(screen, desktop->monitor, &geom);
2638     gtk_window_resize((GtkWindow*)desktop, geom.width, geom.height);
2639     /* bug #3614780: if monitor was moved desktop should be moved too */
2640     gtk_window_move((GtkWindow*)desktop, geom.x, geom.y);
2641     /* FIXME: check if new monitor was added! */
2642 }
2643 
reload_icons()2644 static void reload_icons()
2645 {
2646     int i;
2647     for(i=0; i < n_screens; ++i)
2648         if(desktops[i]->monitor >= 0)
2649             gtk_widget_queue_resize(GTK_WIDGET(desktops[i]));
2650 }
2651 
on_big_icon_size_changed(FmConfig * cfg,FmFolderModel * model)2652 static void on_big_icon_size_changed(FmConfig* cfg, FmFolderModel* model)
2653 {
2654     fm_folder_model_set_icon_size(model, fm_config->big_icon_size);
2655     reload_icons();
2656 }
2657 
on_icon_theme_changed(GtkIconTheme * theme,gpointer user_data)2658 static void on_icon_theme_changed(GtkIconTheme* theme, gpointer user_data)
2659 {
2660     reload_icons();
2661 }
2662 
2663 
2664 /* ---------------------------------------------------------------------
2665     Popup handlers */
2666 
fm_desktop_update_popup(FmFolderView * fv,GtkWindow * window,GtkUIManager * ui,GtkActionGroup * act_grp,FmFileInfoList * files)2667 static void fm_desktop_update_popup(FmFolderView* fv, GtkWindow* window,
2668                                     GtkUIManager* ui, GtkActionGroup* act_grp,
2669                                     FmFileInfoList* files)
2670 {
2671     GtkAction* act;
2672 
2673     /* remove 'Rename' item and accelerator */
2674     act = gtk_action_group_get_action(act_grp, "Rename");
2675     gtk_action_set_visible(act, FALSE);
2676     gtk_action_set_sensitive(act, FALSE);
2677     /* hide 'Show Hidden' item */
2678     act = gtk_action_group_get_action(act_grp, "ShowHidden");
2679     gtk_action_set_visible(act, FALSE);
2680     /* add 'Configure desktop' item replacing 'Properties' */
2681     act = gtk_action_group_get_action(act_grp, "Prop");
2682     gtk_action_set_visible(act, FALSE);
2683     //gtk_action_group_remove_action(act_grp, act);
2684 #if !FM_CHECK_VERSION(1, 2, 0)
2685     if(fm_folder_view_get_model(fv) == NULL)
2686     {
2687         /* hide folder-oriented actions if there is no folder */
2688         act = gtk_action_group_get_action(act_grp, "SelAll");
2689         gtk_action_set_visible(act, FALSE);
2690         act = gtk_action_group_get_action(act_grp, "InvSel");
2691         gtk_action_set_visible(act, FALSE);
2692         act = gtk_action_group_get_action(act_grp, "Sort");
2693         gtk_action_set_visible(act, FALSE);
2694     }
2695 #endif
2696     gtk_action_group_set_translation_domain(act_grp, NULL);
2697     gtk_action_group_add_actions(act_grp, desktop_actions,
2698                                  G_N_ELEMENTS(desktop_actions), window);
2699     gtk_ui_manager_add_ui_from_string(ui, desktop_menu_xml, -1, NULL);
2700 }
2701 
fm_desktop_update_item_popup(FmFolderView * fv,GtkWindow * window,GtkUIManager * ui,GtkActionGroup * act_grp,FmFileInfoList * files)2702 static void fm_desktop_update_item_popup(FmFolderView* fv, GtkWindow* window,
2703                                          GtkUIManager* ui, GtkActionGroup* act_grp,
2704                                          FmFileInfoList* files)
2705 {
2706     FmFileInfo* fi;
2707     GList* sel_items, *l;
2708     GtkAction* act;
2709     gboolean all_fixed = TRUE, has_fixed = FALSE;
2710     gboolean all_native = TRUE;
2711 #if FM_CHECK_VERSION(1, 2, 0)
2712     gboolean has_extra = FALSE, has_mount = FALSE;
2713 #endif
2714 
2715     sel_items = get_selected_items(FM_DESKTOP(fv), NULL);
2716     for(l = sel_items; l; l=l->next)
2717     {
2718         FmDesktopItem* item = (FmDesktopItem*)l->data;
2719         if(item->fixed_pos)
2720             has_fixed = TRUE;
2721         else
2722             all_fixed = FALSE;
2723         if (!pcmanfm_can_open_path_in_terminal(fm_file_info_get_path(item->fi)))
2724             all_native = FALSE;
2725 #if FM_CHECK_VERSION(1, 2, 0)
2726         if (item->is_special)
2727             has_extra = TRUE;
2728         if (item->is_mount)
2729             has_mount = TRUE;
2730 #endif
2731     }
2732     g_list_free(sel_items);
2733 
2734     fi = (FmFileInfo*)fm_file_info_list_peek_head(files);
2735 
2736     /* merge some specific menu items for folders */
2737     gtk_action_group_set_translation_domain(act_grp, NULL);
2738     if(fm_file_info_list_get_length(files) == 1 && fm_file_info_is_dir(fi))
2739     {
2740         gtk_action_group_add_actions(act_grp, folder_menu_actions,
2741                                      G_N_ELEMENTS(folder_menu_actions), fv);
2742         gtk_ui_manager_add_ui_from_string(ui, folder_menu_xml, -1, NULL);
2743         /* disable terminal for non-native folders */
2744         act = gtk_action_group_get_action(act_grp, "Term");
2745         gtk_action_set_visible(act, all_native);
2746     }
2747 #if FM_CHECK_VERSION(1, 2, 0)
2748     if (has_extra)
2749     {
2750         if (fm_file_info_list_get_length(files) == 1)
2751         {
2752             gtk_action_group_add_actions(act_grp, extra_item_menu_actions,
2753                                          G_N_ELEMENTS(extra_item_menu_actions), fv);
2754             gtk_ui_manager_add_ui_from_string(ui, extra_item_menu_xml, -1, NULL);
2755             if (has_mount)
2756             {
2757                 act = gtk_action_group_get_action(act_grp, "Disable");
2758                 gtk_action_set_visible(act, FALSE);
2759             }
2760         }
2761         /* some menu items should be never available for extra items */
2762         act = gtk_action_group_get_action(act_grp, "Cut");
2763         gtk_action_set_visible(act, FALSE);
2764         act = gtk_action_group_get_action(act_grp, "Del");
2765         gtk_action_set_visible(act, FALSE);
2766         act = gtk_action_group_get_action(act_grp, "Rename");
2767         gtk_action_set_visible(act, FALSE);
2768     }
2769 #endif
2770 
2771     /* merge desktop icon specific items */
2772     gtk_action_group_add_actions(act_grp, desktop_icon_actions,
2773                                  G_N_ELEMENTS(desktop_icon_actions), fv);
2774     act = gtk_action_group_get_action(act_grp, "Snap");
2775     gtk_action_set_sensitive(act, has_fixed);
2776 
2777     gtk_action_group_add_toggle_actions(act_grp, desktop_icon_toggle_actions,
2778                                         G_N_ELEMENTS(desktop_icon_toggle_actions),
2779                                         fv);
2780     act = gtk_action_group_get_action(act_grp, "Fix");
2781     gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(act), all_fixed);
2782 
2783     gtk_ui_manager_add_ui_from_string(ui, desktop_icon_menu_xml, -1, NULL);
2784 }
2785 
2786 /* folder options work only with single folder - see above */
on_open_in_new_tab(GtkAction * act,gpointer user_data)2787 static void on_open_in_new_tab(GtkAction* act, gpointer user_data)
2788 {
2789     FmDesktop* desktop = FM_DESKTOP(user_data);
2790 
2791     if(desktop->focus)
2792         fm_main_win_open_in_last_active(fm_file_info_get_path(desktop->focus->fi));
2793 }
2794 
on_open_in_new_win(GtkAction * act,gpointer user_data)2795 static void on_open_in_new_win(GtkAction* act, gpointer user_data)
2796 {
2797     FmDesktop* desktop = FM_DESKTOP(user_data);
2798 
2799     if(desktop->focus)
2800         fm_main_win_add_win(NULL, fm_file_info_get_path(desktop->focus->fi));
2801 }
2802 
on_open_folder_in_terminal(GtkAction * act,gpointer user_data)2803 static void on_open_folder_in_terminal(GtkAction* act, gpointer user_data)
2804 {
2805     FmDesktop* desktop = FM_DESKTOP(user_data);
2806 
2807     if(desktop->focus /*&& !fm_file_info_is_virtual(fi)*/)
2808         pcmanfm_open_folder_in_terminal(NULL, fm_file_info_get_path(desktop->focus->fi));
2809 }
2810 
on_fix_pos(GtkToggleAction * act,gpointer user_data)2811 static void on_fix_pos(GtkToggleAction* act, gpointer user_data)
2812 {
2813     FmDesktop* desktop = FM_DESKTOP(user_data);
2814     GList* items = get_selected_items(desktop, NULL);
2815     GList* l;
2816     if(gtk_toggle_action_get_active(act))
2817     {
2818         for(l = items; l; l=l->next)
2819         {
2820             FmDesktopItem* item = (FmDesktopItem*)l->data;
2821             if(!item->fixed_pos)
2822             {
2823                 item->fixed_pos = TRUE;
2824                 desktop->fixed_items = g_list_prepend(desktop->fixed_items, item);
2825             }
2826         }
2827     }
2828     else
2829     {
2830         for(l = items; l; l=l->next)
2831         {
2832             FmDesktopItem* item = (FmDesktopItem*)l->data;
2833             item->fixed_pos = FALSE;
2834             desktop->fixed_items = g_list_remove(desktop->fixed_items, item);
2835         }
2836         queue_layout_items(desktop);
2837     }
2838     g_list_free(items);
2839     queue_config_save(desktop);
2840 }
2841 
2842 #if FM_CHECK_VERSION(1, 2, 0)
on_disable(GtkAction * act,gpointer user_data)2843 static void on_disable(GtkAction* act, gpointer user_data)
2844 {
2845     FmDesktop *desktop = FM_DESKTOP(user_data);
2846     GList *items = get_selected_items(desktop, NULL);
2847     FmDesktopItem *item = (FmDesktopItem*)items->data;
2848 
2849     g_list_free(items);
2850     if (trash_can && trash_can->fi == item->fi)
2851     {
2852         desktop->conf.show_trash = FALSE;
2853     }
2854     else if (documents && documents->fi == item->fi)
2855     {
2856         desktop->conf.show_documents = FALSE;
2857     }
2858     else /* else is error */
2859     {
2860         g_warning("invalid item to remove from desktop");
2861         return;
2862     }
2863     queue_config_save(desktop);
2864     fm_folder_model_extra_file_remove(desktop->model, item->fi);
2865 }
2866 #endif
2867 
2868 /* round() is only available in C99. Don't use it now for portability. */
_round(double x)2869 static inline double _round(double x)
2870 {
2871     return (x > 0.0) ? floor(x + 0.5) : ceil(x - 0.5);
2872 }
2873 
on_snap_to_grid(GtkAction * act,gpointer user_data)2874 static void on_snap_to_grid(GtkAction* act, gpointer user_data)
2875 {
2876     FmDesktop* desktop = FM_DESKTOP(user_data);
2877     FmDesktopItem* item;
2878     GList* items = get_selected_items(desktop, NULL);
2879     GList* l;
2880     int x, y;
2881     GtkTextDirection direction = gtk_widget_get_direction(GTK_WIDGET(desktop));
2882 
2883     y = desktop->working_area.y + desktop->ymargin;
2884     //bottom = desktop->working_area.y + desktop->working_area.height - desktop->ymargin - desktop->cell_h;
2885 
2886     if(direction != GTK_TEXT_DIR_RTL) /* LTR or NONE */
2887         x = desktop->working_area.x + desktop->xmargin;
2888     else /* RTL */
2889         x = desktop->working_area.x + desktop->working_area.width - desktop->xmargin - desktop->cell_w;
2890 
2891     for(l = items; l; l = l->next)
2892     {
2893         int new_x, new_y;
2894         item = (FmDesktopItem*)l->data;
2895         if(!item->fixed_pos)
2896             continue;
2897         new_x = x + _round((double)(item->area.x - x) / desktop->cell_w) * desktop->cell_w;
2898         new_y = y + _round((double)(item->area.y - y) / desktop->cell_h) * desktop->cell_h;
2899         move_item(desktop, item, new_x, new_y, FALSE);
2900     }
2901     g_list_free(items);
2902 
2903     queue_layout_items(desktop);
2904 }
2905 
2906 
2907 /* ---------------------------------------------------------------------
2908     GtkWidget class default signal handlers */
2909 
is_point_in_rect(GdkRectangle * rect,int x,int y)2910 static gboolean is_point_in_rect(GdkRectangle* rect, int x, int y)
2911 {
2912     return x >= rect->x && x < (rect->x + rect->width) && y >= rect->y && y < (rect->y + rect->height);
2913 }
2914 
hit_test(FmDesktop * self,GtkTreeIter * it,int x,int y)2915 static FmDesktopItem* hit_test(FmDesktop* self, GtkTreeIter *it, int x, int y)
2916 {
2917     FmDesktopItem* item;
2918     GtkTreeModel* model;
2919 
2920     if (!self->model)
2921         return NULL;
2922     model = GTK_TREE_MODEL(self->model);
2923     if(model && gtk_tree_model_get_iter_first(model, it)) do
2924     {
2925         GdkRectangle icon_rect;
2926         item = fm_folder_model_get_item_userdata(self->model, it);
2927         /* we cannot drop dragged items onto themselves */
2928         if (item->is_selected && self->dragging)
2929             continue;
2930         /* SF bug #963: icon_rect and text_rect may be not contiguous,
2931            so let expand icon test area up to text_rect */
2932         icon_rect = item->icon_rect;
2933         icon_rect.height = item->text_rect.y - icon_rect.y;
2934         if(is_point_in_rect(&icon_rect, x, y)
2935          || is_point_in_rect(&item->text_rect, x, y))
2936             return item;
2937     }
2938     while(gtk_tree_model_iter_next(model, it));
2939     return NULL;
2940 }
2941 
get_nearest_item(FmDesktop * desktop,FmDesktopItem * item,GtkDirectionType dir)2942 static FmDesktopItem* get_nearest_item(FmDesktop* desktop, FmDesktopItem* item,  GtkDirectionType dir)
2943 {
2944     GtkTreeModel* model;
2945     FmDesktopItem* item2, *ret = NULL;
2946     guint min_x_dist, min_y_dist, dist;
2947     GtkTreeIter it;
2948 
2949     if (!desktop->model)
2950         return NULL;
2951     model = GTK_TREE_MODEL(desktop->model);
2952     if(!gtk_tree_model_get_iter_first(model, &it))
2953         return NULL;
2954     if(!item) /* there is no focused item yet, select first one then */
2955         return fm_folder_model_get_item_userdata(desktop->model, &it);
2956 
2957     min_x_dist = min_y_dist = (guint)-1;
2958     item2 = NULL;
2959 
2960     switch(dir)
2961     {
2962     case GTK_DIR_LEFT:
2963         do
2964         {
2965             item2 = fm_folder_model_get_item_userdata(desktop->model, &it);
2966             if(item2->area.x >= item->area.x)
2967                 continue;
2968             dist = item->area.x - item2->area.x;
2969             if(dist < min_x_dist)
2970             {
2971                 ret = item2;
2972                 min_x_dist = dist;
2973                 min_y_dist = ABS(item->area.y - item2->area.y);
2974             }
2975             else if(dist == min_x_dist && item2 != ret) /* if there is another item of the same x distance */
2976             {
2977                 /* get the one with smaller y distance */
2978                 dist = ABS(item2->area.y - item->area.y);
2979                 if(dist < min_y_dist)
2980                 {
2981                     ret = item2;
2982                     min_y_dist = dist;
2983                 }
2984             }
2985         }
2986         while(gtk_tree_model_iter_next(model, &it));
2987         break;
2988     case GTK_DIR_RIGHT:
2989         do
2990         {
2991             item2 = fm_folder_model_get_item_userdata(desktop->model, &it);
2992             if(item2->area.x <= item->area.x)
2993                 continue;
2994             dist = item2->area.x - item->area.x;
2995             if(dist < min_x_dist)
2996             {
2997                 ret = item2;
2998                 min_x_dist = dist;
2999                 min_y_dist = ABS(item->area.y - item2->area.y);
3000             }
3001             else if(dist == min_x_dist && item2 != ret) /* if there is another item of the same x distance */
3002             {
3003                 /* get the one with smaller y distance */
3004                 dist = ABS(item2->area.y - item->area.y);
3005                 if(dist < min_y_dist)
3006                 {
3007                     ret = item2;
3008                     min_y_dist = dist;
3009                 }
3010             }
3011         }
3012         while(gtk_tree_model_iter_next(model, &it));
3013         break;
3014     case GTK_DIR_UP:
3015         do
3016         {
3017             item2 = fm_folder_model_get_item_userdata(desktop->model, &it);
3018             if(item2->area.y >= item->area.y)
3019                 continue;
3020             dist = item->area.y - item2->area.y;
3021             if(dist < min_y_dist)
3022             {
3023                 ret = item2;
3024                 min_y_dist = dist;
3025                 min_x_dist = ABS(item->area.x - item2->area.x);
3026             }
3027             else if(dist == min_y_dist && item2 != ret) /* if there is another item of the same y distance */
3028             {
3029                 /* get the one with smaller x distance */
3030                 dist = ABS(item2->area.x - item->area.x);
3031                 if(dist < min_x_dist)
3032                 {
3033                     ret = item2;
3034                     min_x_dist = dist;
3035                 }
3036             }
3037         }
3038         while(gtk_tree_model_iter_next(model, &it));
3039         break;
3040     case GTK_DIR_DOWN:
3041         do
3042         {
3043             item2 = fm_folder_model_get_item_userdata(desktop->model, &it);
3044             if(item2->area.y <= item->area.y)
3045                 continue;
3046             dist = item2->area.y - item->area.y;
3047             if(dist < min_y_dist)
3048             {
3049                 ret = item2;
3050                 min_y_dist = dist;
3051                 min_x_dist = ABS(item->area.x - item2->area.x);
3052             }
3053             else if(dist == min_y_dist && item2 != ret) /* if there is another item of the same y distance */
3054             {
3055                 /* get the one with smaller x distance */
3056                 dist = ABS(item2->area.x - item->area.x);
3057                 if(dist < min_x_dist)
3058                 {
3059                     ret = item2;
3060                     min_x_dist = dist;
3061                 }
3062             }
3063         }
3064         while(gtk_tree_model_iter_next(model, &it));
3065         break;
3066     case GTK_DIR_TAB_FORWARD: /* FIXME */
3067         break;
3068     case GTK_DIR_TAB_BACKWARD: /* FIXME */
3069         ;
3070     }
3071     return ret;
3072 }
3073 
set_focused_item(FmDesktop * desktop,FmDesktopItem * item)3074 static void set_focused_item(FmDesktop* desktop, FmDesktopItem* item)
3075 {
3076     if(item != desktop->focus)
3077     {
3078         FmDesktopItem* old_focus = desktop->focus;
3079         desktop->focus = item;
3080         if(old_focus)
3081         {
3082             redraw_item(desktop, old_focus);
3083             fm_desktop_accessible_focus_unset(desktop, old_focus);
3084         }
3085         if(item)
3086         {
3087             redraw_item(desktop, item);
3088             fm_desktop_accessible_focus_set(desktop, item);
3089         }
3090     }
3091 }
3092 
_focus_and_select_focused_item(FmDesktop * desktop,FmDesktopItem * item)3093 static void _focus_and_select_focused_item(FmDesktop *desktop, FmDesktopItem *item)
3094 {
3095     item->is_selected = TRUE;
3096     fm_desktop_item_selected_changed(desktop, item);
3097     set_focused_item(desktop, item);
3098 }
3099 
3100 /* This function is taken from xfdesktop */
forward_event_to_rootwin(GdkScreen * gscreen,GdkEvent * event)3101 static void forward_event_to_rootwin(GdkScreen *gscreen, GdkEvent *event)
3102 {
3103     XButtonEvent xev, xev2;
3104     Display *dpy = GDK_DISPLAY_XDISPLAY(gdk_screen_get_display(gscreen));
3105 
3106     if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE)
3107     {
3108         if (event->type == GDK_BUTTON_PRESS)
3109         {
3110             xev.type = ButtonPress;
3111             /*
3112              * rox has an option to disable the next
3113              * instruction. it is called "blackbox_hack". Does
3114              * anyone know why exactly it is needed?
3115              */
3116             XUngrabPointer(dpy, event->button.time);
3117         }
3118         else
3119             xev.type = ButtonRelease;
3120 
3121         xev.button = event->button.button;
3122         xev.x = event->button.x;    /* Needed for icewm */
3123         xev.y = event->button.y;
3124         xev.x_root = event->button.x_root;
3125         xev.y_root = event->button.y_root;
3126         xev.state = event->button.state;
3127 
3128         xev2.type = 0;
3129     }
3130     else if (event->type == GDK_SCROLL)
3131     {
3132         xev.type = ButtonPress;
3133         xev.button = event->scroll.direction + 4;
3134         xev.x = event->scroll.x;    /* Needed for icewm */
3135         xev.y = event->scroll.y;
3136         xev.x_root = event->scroll.x_root;
3137         xev.y_root = event->scroll.y_root;
3138         xev.state = event->scroll.state;
3139 
3140         xev2.type = ButtonRelease;
3141         xev2.button = xev.button;
3142     }
3143     else
3144         return ;
3145     xev.window = GDK_WINDOW_XID(gdk_screen_get_root_window(gscreen));
3146     xev.root = xev.window;
3147     xev.subwindow = None;
3148     xev.time = event->button.time;
3149     xev.same_screen = True;
3150 
3151     XSendEvent(dpy, xev.window, False, ButtonPressMask | ButtonReleaseMask,
3152                 (XEvent *) & xev);
3153     if (xev2.type == 0)
3154         return ;
3155 
3156     /* send button release for scroll event */
3157     xev2.window = xev.window;
3158     xev2.root = xev.root;
3159     xev2.subwindow = xev.subwindow;
3160     xev2.time = xev.time;
3161     xev2.x = xev.x;
3162     xev2.y = xev.y;
3163     xev2.x_root = xev.x_root;
3164     xev2.y_root = xev.y_root;
3165     xev2.state = xev.state;
3166     xev2.same_screen = xev.same_screen;
3167 
3168     XSendEvent(dpy, xev2.window, False, ButtonPressMask | ButtonReleaseMask,
3169                 (XEvent *) & xev2);
3170 }
3171 
3172 
3173 #if GTK_CHECK_VERSION(3, 0, 0)
on_draw(GtkWidget * w,cairo_t * cr)3174 static gboolean on_draw(GtkWidget* w, cairo_t* cr)
3175 #else
3176 static gboolean on_expose(GtkWidget* w, GdkEventExpose* evt)
3177 #endif
3178 {
3179     FmDesktop* self = (FmDesktop*)w;
3180 #if !GTK_CHECK_VERSION(3, 0, 0)
3181     cairo_t* cr;
3182 #endif
3183     GtkTreeModel* model = self->model ? GTK_TREE_MODEL(self->model) : NULL;
3184     GtkTreeIter it;
3185     GdkRectangle area;
3186 
3187 #if GTK_CHECK_VERSION(3, 0, 0)
3188     if(G_UNLIKELY(!gtk_cairo_should_draw_window(cr, gtk_widget_get_window(w))))
3189         return FALSE;
3190 
3191     cairo_save(cr);
3192     gtk_cairo_transform_to_window(cr, w, gtk_widget_get_window(w));
3193     gdk_cairo_get_clip_rectangle(cr, &area);
3194 #else
3195     if(G_UNLIKELY(! gtk_widget_get_visible (w) || ! gtk_widget_get_mapped (w)))
3196         return TRUE;
3197 
3198     cr = gdk_cairo_create(gtk_widget_get_window(w));
3199     area = evt->area;
3200 #endif
3201     if(self->rubber_bending)
3202         paint_rubber_banding_rect(self, cr, &area);
3203 
3204     if(model && gtk_tree_model_get_iter_first(model, &it)) do
3205     {
3206         FmDesktopItem* item = fm_folder_model_get_item_userdata(self->model, &it);
3207         GdkRectangle* intersect, tmp, tmp2;
3208         GdkPixbuf* icon = NULL;
3209         if(gdk_rectangle_intersect(&area, &item->icon_rect, &tmp))
3210             intersect = &tmp;
3211         else
3212             intersect = NULL;
3213 
3214         if(gdk_rectangle_intersect(&area, &item->text_rect, &tmp2))
3215         {
3216             if(intersect)
3217                 gdk_rectangle_union(intersect, &tmp2, intersect);
3218             else
3219                 intersect = &tmp2;
3220         }
3221 
3222         if(intersect)
3223         {
3224             gtk_tree_model_get(model, &it, FM_FOLDER_MODEL_COL_ICON, &icon, -1);
3225             paint_item(self, item, cr, intersect, icon);
3226             if(icon)
3227                 g_object_unref(icon);
3228         }
3229     }
3230     while(gtk_tree_model_iter_next(model, &it));
3231 #if GTK_CHECK_VERSION(3, 0, 0)
3232     cairo_restore(cr);
3233 #else
3234     cairo_destroy(cr);
3235 #endif
3236 
3237     return TRUE;
3238 }
3239 
on_size_allocate(GtkWidget * w,GtkAllocation * alloc)3240 static void on_size_allocate(GtkWidget* w, GtkAllocation* alloc)
3241 {
3242     FmDesktop* self = (FmDesktop*)w;
3243 
3244     /* calculate item size */
3245     PangoContext* pc;
3246     PangoFontMetrics *metrics;
3247     int font_h;
3248 
3249     pc = gtk_widget_get_pango_context((GtkWidget*)self);
3250 
3251     metrics = pango_context_get_metrics(pc, NULL, NULL);
3252 
3253     font_h = pango_font_metrics_get_ascent(metrics) + pango_font_metrics_get_descent (metrics);
3254     pango_font_metrics_unref(metrics);
3255 
3256     font_h /= PANGO_SCALE;
3257 
3258     self->spacing = SPACING;
3259     self->xpad = self->ypad = PADDING;
3260     self->xmargin = self->ymargin = MARGIN;
3261     self->text_h = font_h * 2;
3262     self->text_w = 100;
3263 #if FM_CHECK_VERSION(1, 2, 0)
3264     if (fm_config->show_full_names)
3265         self->pango_text_h = -1;
3266     else
3267 #endif
3268         self->pango_text_h = self->text_h * PANGO_SCALE;
3269     self->pango_text_w = self->text_w * PANGO_SCALE;
3270     self->text_h += 4;
3271     self->text_w += 4; /* 4 is for drawing border */
3272     self->cell_h = fm_config->big_icon_size + self->spacing + self->text_h + self->ypad * 2;
3273     self->cell_w = MAX((gint)self->text_w, fm_config->big_icon_size) + self->xpad * 2;
3274 
3275     update_working_area(self);
3276     /* queue_layout_items(self); this is called in update_working_area */
3277 
3278     /* scale the wallpaper */
3279     if(gtk_widget_get_realized(w))
3280     {
3281 #if GTK_CHECK_VERSION(3, 0, 0)
3282         /* bug SF#958: with GTK 3.8+ font is reset to default after realizing
3283            so let enforce font description on it right away */
3284         PangoFontDescription *font_desc = pango_font_description_from_string(self->conf.desktop_font);
3285         pango_context_set_font_description(pc, font_desc);
3286         pango_font_description_free(font_desc);
3287 #endif
3288         /* bug #3614866: after monitor geometry was changed we need to redraw
3289            the background invalidating all the cache */
3290         _clear_bg_cache(self);
3291         if(self->conf.wallpaper_mode != FM_WP_COLOR && self->conf.wallpaper_mode != FM_WP_TILE)
3292             update_background(self, -1);
3293     }
3294 
3295     GTK_WIDGET_CLASS(fm_desktop_parent_class)->size_allocate(w, alloc);
3296 }
3297 
3298 #if GTK_CHECK_VERSION(3, 0, 0)
on_get_preferred_width(GtkWidget * w,gint * minimal_width,gint * natural_width)3299 static void on_get_preferred_width(GtkWidget *w, gint *minimal_width, gint *natural_width)
3300 {
3301     GdkScreen* scr = gtk_widget_get_screen(w);
3302     gint monitor = FM_DESKTOP(w)->monitor;
3303     GdkRectangle geom;
3304     gdk_screen_get_monitor_geometry(scr, monitor, &geom);
3305     *minimal_width = *natural_width = geom.width;
3306 }
3307 
on_get_preferred_height(GtkWidget * w,gint * minimal_height,gint * natural_height)3308 static void on_get_preferred_height(GtkWidget *w, gint *minimal_height, gint *natural_height)
3309 {
3310     GdkScreen* scr = gtk_widget_get_screen(w);
3311     gint monitor = FM_DESKTOP(w)->monitor;
3312     GdkRectangle geom;
3313     gdk_screen_get_monitor_geometry(scr, monitor, &geom);
3314     *minimal_height = *natural_height = geom.height;
3315 }
3316 #else
on_size_request(GtkWidget * w,GtkRequisition * req)3317 static void on_size_request(GtkWidget* w, GtkRequisition* req)
3318 {
3319     GdkScreen* scr = gtk_widget_get_screen(w);
3320     gint monitor = FM_DESKTOP(w)->monitor;
3321     GdkRectangle geom;
3322     gdk_screen_get_monitor_geometry(scr, monitor, &geom);
3323     req->width = geom.width;
3324     req->height = geom.height;
3325 }
3326 #endif
3327 
_stop_rubberbanding(FmDesktop * self,gint x,gint y)3328 static void _stop_rubberbanding(FmDesktop *self, gint x, gint y)
3329 {
3330     /* re-enable Gtk+ DnD callbacks again */
3331     gpointer drag_data = g_object_get_data(G_OBJECT(self),
3332                             g_intern_static_string("gtk-site-data"));
3333     if(G_LIKELY(drag_data != NULL))
3334     {
3335         g_signal_handlers_unblock_matched(G_OBJECT(self), G_SIGNAL_MATCH_DATA,
3336                                           0, 0, NULL, NULL, drag_data);
3337     }
3338     self->rubber_bending = FALSE;
3339     update_rubberbanding(self, x, y);
3340     gtk_grab_remove(GTK_WIDGET(self));
3341 }
3342 
on_button_press(GtkWidget * w,GdkEventButton * evt)3343 static gboolean on_button_press(GtkWidget* w, GdkEventButton* evt)
3344 {
3345     FmDesktop* self = (FmDesktop*)w;
3346     FmDesktopItem *item = NULL, *clicked_item = NULL;
3347     GtkTreeIter it;
3348     FmFolderViewClickType clicked = FM_FV_CLICK_NONE;
3349 
3350     clicked_item = hit_test(FM_DESKTOP(w), &it, (int)evt->x, (int)evt->y);
3351 
3352     /* reset auto-selection now */
3353     if (self->single_click_timeout_handler != 0)
3354     {
3355         g_source_remove(self->single_click_timeout_handler);
3356         self->single_click_timeout_handler = 0;
3357     }
3358 
3359     if(evt->type == GDK_BUTTON_PRESS)
3360     {
3361         /* ignore another buttons while some is in progress */
3362         if (self->button_pressed == 0)
3363             self->button_pressed = evt->button;
3364         if(evt->button == 1)  /* left button */
3365         {
3366             /* store button state for drag & drop */
3367             self->drag_start_x = evt->x;
3368             self->drag_start_y = evt->y;
3369         }
3370         else if (self->rubber_bending)
3371         {
3372             /* LP #1071121: right click stops rubberbanding but
3373                leaves the selection area on the desktop.
3374                To avoid that weird thing we reset and stop rubberbanding now */
3375             _stop_rubberbanding(self, self->drag_start_x, self->drag_start_y);
3376         }
3377 
3378         /* if ctrl / shift is not pressed, deselect all. */
3379         /* FIXME: do [un]selection on button release */
3380         if(! (evt->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)))
3381         {
3382             if (clicked_item == NULL)
3383             {
3384                 if (evt->button == 1)
3385                     /* SF bug #999: unselect all only on left button */
3386                     _unselect_all(FM_FOLDER_VIEW(self));
3387             }
3388             /* don't cancel selection if clicking on selected items */
3389             else if (!((evt->button == 1 || evt->button == 3) && clicked_item->is_selected))
3390                 _unselect_all(FM_FOLDER_VIEW(self));
3391         }
3392 
3393         if(clicked_item)
3394         {
3395             if(evt->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
3396                 clicked_item->is_selected = ! clicked_item->is_selected;
3397             else
3398                 clicked_item->is_selected = TRUE;
3399             fm_desktop_item_selected_changed(self, clicked_item);
3400 
3401             if(self->focus && self->focus != item)
3402             {
3403                 FmDesktopItem* old_focus = self->focus;
3404                 self->focus = NULL;
3405                 if(old_focus)
3406                 {
3407                     redraw_item(self, old_focus);
3408                     fm_desktop_accessible_focus_unset(self, old_focus);
3409                 }
3410             }
3411             self->focus = clicked_item;
3412             fm_desktop_accessible_focus_set(self, clicked_item);
3413             redraw_item(self, clicked_item);
3414 
3415             if(evt->button == 3)  /* right click, context menu */
3416                 clicked = FM_FV_CONTEXT_MENU;
3417             else if(evt->button == 2)
3418                 clicked = FM_FV_MIDDLE_CLICK;
3419         }
3420         else /* no item is clicked */
3421         {
3422             if(evt->button == 3)  /* right click on the blank area => desktop popup menu */
3423             {
3424                 if(!self->conf.show_wm_menu)
3425                     clicked = FM_FV_CONTEXT_MENU;
3426             }
3427             else if(evt->button == 1)
3428             {
3429                 /* disable Gtk+ DnD callbacks, because else rubberbanding will be interrupted */
3430                 gpointer drag_data = g_object_get_data(G_OBJECT(self),
3431                                         g_intern_static_string("gtk-site-data"));
3432                 if(G_LIKELY(drag_data != NULL))
3433                 {
3434                     g_signal_handlers_block_matched(G_OBJECT(self),
3435                                                     G_SIGNAL_MATCH_DATA, 0, 0,
3436                                                     NULL, NULL, drag_data);
3437                 }
3438                 self->rubber_bending = TRUE;
3439 
3440                 /* FIXME: if you foward the event here, this will break rubber bending... */
3441                 /* forward the event to root window */
3442                 /* forward_event_to_rootwin(gtk_widget_get_screen(w), evt); */
3443 
3444                 gtk_grab_add(w);
3445                 self->rubber_bending_x = evt->x;
3446                 self->rubber_bending_y = evt->y;
3447             }
3448         }
3449     }
3450     else if(evt->type == GDK_2BUTTON_PRESS) /* activate items */
3451     {
3452         if(clicked_item && evt->button == 1)   /* left double click */
3453             clicked = FM_FV_ACTIVATED;
3454     }
3455 
3456     if(clicked != FM_FV_CLICK_NONE)
3457     {
3458         GtkTreePath* tp = NULL;
3459 
3460         if(self->model && clicked_item)
3461             tp = gtk_tree_model_get_path(GTK_TREE_MODEL(self->model), &it);
3462         fm_folder_view_item_clicked(FM_FOLDER_VIEW(self), tp, clicked);
3463         if(tp)
3464             gtk_tree_path_free(tp);
3465         /* SF bug #929: after click the tooltip is still set to the item name */
3466         self->hover_item = NULL;
3467     }
3468     /* forward the event to root window */
3469     else if(evt->button != 1 && evt->button == self->button_pressed)
3470     {
3471         self->forward_pending = TRUE;
3472         forward_event_to_rootwin(gtk_widget_get_screen(w), (GdkEvent*)evt);
3473     }
3474 
3475     if(! gtk_widget_has_focus(w))
3476     {
3477         /* g_debug("we don't have the focus, grab it!"); */
3478         gtk_widget_grab_focus(w);
3479     }
3480     return TRUE;
3481 }
3482 
on_button_release(GtkWidget * w,GdkEventButton * evt)3483 static gboolean on_button_release(GtkWidget* w, GdkEventButton* evt)
3484 {
3485     FmDesktop* self = (FmDesktop*)w;
3486 
3487     if(self->rubber_bending)
3488     {
3489         _stop_rubberbanding(self, evt->x, evt->y);
3490     }
3491     else if(self->dragging)
3492     {
3493         self->dragging = FALSE;
3494         /* restore after drag */
3495         queue_layout_items(self);
3496     }
3497     else if(fm_config->single_click && evt->button == 1)
3498     {
3499         GtkTreeIter it;
3500         FmDesktopItem* clicked_item = hit_test(self, &it, evt->x, evt->y);
3501         if(clicked_item)
3502             /* left single click */
3503             fm_launch_file_simple(GTK_WINDOW(w), NULL, clicked_item->fi, pcmanfm_open_folder, w);
3504     }
3505 
3506     /* forward the event to root window */
3507     if (self->button_pressed == evt->button)
3508     {
3509         if (self->forward_pending)
3510             forward_event_to_rootwin(gtk_widget_get_screen(w), (GdkEvent*)evt);
3511         self->button_pressed = 0;
3512         self->forward_pending = FALSE;
3513     }
3514 
3515     return TRUE;
3516 }
3517 
on_single_click_timeout(gpointer user_data)3518 static gboolean on_single_click_timeout(gpointer user_data)
3519 {
3520     FmDesktop* self = (FmDesktop*)user_data;
3521     GtkWidget* w = (GtkWidget*)self;
3522     GdkWindow* window;
3523     int x, y;
3524     FmDesktopItem *item;
3525     GdkModifierType state;
3526     GtkTreeIter it;
3527 
3528     if(g_source_is_destroyed(g_main_current_source()))
3529         return FALSE;
3530 
3531     self->single_click_timeout_handler = 0;
3532     /* ensure we are still in single-click mode and have hovered item */
3533     if (!fm_config->single_click || !self->hover_item)
3534         return FALSE;
3535     /* ensure we are still on the same item */
3536     window = gtk_widget_get_window(w);
3537     gdk_window_get_pointer(window, &x, &y, &state);
3538     item = hit_test(self, &it, x, y);
3539     if (item != self->hover_item)
3540         return FALSE;
3541     /* ok, let select the item then */
3542     state &= gtk_accelerator_get_default_mod_mask();
3543     if (state == 0) /* no modifiers - drop selection and select this item */
3544     {
3545         _unselect_all(FM_FOLDER_VIEW(self));
3546         item->is_selected = TRUE;
3547     }
3548     else if (state == GDK_CONTROL_MASK) /* invert selection on the item */
3549     {
3550         item->is_selected = ! item->is_selected;
3551     }
3552     else /* ignore other modifiers */
3553         return FALSE;
3554     fm_desktop_item_selected_changed(self, item);
3555     set_focused_item(self, item);
3556 
3557     return FALSE;
3558 }
3559 
on_motion_notify(GtkWidget * w,GdkEventMotion * evt)3560 static gboolean on_motion_notify(GtkWidget* w, GdkEventMotion* evt)
3561 {
3562     FmDesktop* self = (FmDesktop*)w;
3563     if(! self->button_pressed)
3564     {
3565         if(fm_config->single_click)
3566         {
3567             GtkTreeIter it;
3568             FmDesktopItem* item = hit_test(self, &it, evt->x, evt->y);
3569             FmDesktopItem *hover_item = self->hover_item;
3570             GdkWindow* window;
3571 
3572             if(item != hover_item)
3573             {
3574                 if(0 != self->single_click_timeout_handler)
3575                 {
3576                     g_source_remove(self->single_click_timeout_handler);
3577                     self->single_click_timeout_handler = 0;
3578                 }
3579                 window = gtk_widget_get_window(w);
3580                 self->hover_item = item;
3581                 if (hover_item)
3582                     redraw_item(self, hover_item);
3583                 if(item)
3584                 {
3585                     redraw_item(self, item);
3586                     gdk_window_set_cursor(window, hand_cursor);
3587 #if FM_CHECK_VERSION(1, 2, 0)
3588                     if(fm_config->auto_selection_delay > 0)
3589                         self->single_click_timeout_handler = gdk_threads_add_timeout(fm_config->auto_selection_delay,
3590                                                                                      on_single_click_timeout, self);
3591 #else
3592                     self->single_click_timeout_handler = gdk_threads_add_timeout(400, on_single_click_timeout, self); //400 ms
3593 #endif
3594                         /* Making a loop to aviod the selection of the item */
3595                         /* on_single_click_timeout(self); */
3596                 }
3597                 else
3598                 {
3599                     gdk_window_set_cursor(window, NULL);
3600                 }
3601             }
3602         }
3603         else
3604         {
3605             GtkTreeIter it;
3606             FmDesktopItem* item = hit_test(self, &it, evt->x, evt->y);
3607             FmDesktopItem *hover_item = self->hover_item;
3608 
3609             if(item != hover_item)
3610             {
3611                 self->hover_item = item;
3612                 if (hover_item)
3613                     redraw_item(self, hover_item);
3614                 if(item)
3615                     redraw_item(self, item);
3616             }
3617         }
3618         return TRUE;
3619     }
3620 
3621     if(self->dragging)
3622     {
3623         /* FIXME: never reached */
3624     }
3625     else if(self->rubber_bending)
3626     {
3627         update_rubberbanding(self, evt->x, evt->y);
3628     }
3629     /* we use auto-DnD so no DnD check is possible here */
3630 
3631     return TRUE;
3632 }
3633 
on_leave_notify(GtkWidget * w,GdkEventCrossing * evt)3634 static gboolean on_leave_notify(GtkWidget* w, GdkEventCrossing *evt)
3635 {
3636     FmDesktop* self = (FmDesktop*)w;
3637     if(self->single_click_timeout_handler)
3638     {
3639         g_source_remove(self->single_click_timeout_handler);
3640         self->single_click_timeout_handler = 0;
3641     }
3642     return TRUE;
3643 }
3644 
get_focused_item(FmDesktopItem * focus,GtkTreeModel * model,GtkTreeIter * it)3645 static gboolean get_focused_item(FmDesktopItem* focus, GtkTreeModel* model, GtkTreeIter* it)
3646 {
3647     FmDesktopItem* item;
3648     if(gtk_tree_model_get_iter_first(model, it)) do
3649     {
3650         item = fm_folder_model_get_item_userdata(FM_FOLDER_MODEL(model), it);
3651         if(item == focus)
3652             return item->is_selected;
3653     }
3654     while(gtk_tree_model_iter_next(model, it));
3655     return FALSE;
3656 }
3657 
3658 /* ---- Interactive search funcs: mostly picked from ExoIconView ---- */
3659 
3660 /* Cut and paste from gtkwindow.c & gtkwidget.c */
send_focus_change(GtkWidget * widget,gboolean in)3661 static void send_focus_change(GtkWidget *widget, gboolean in)
3662 {
3663   GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE);
3664 
3665   fevent->focus_change.type = GDK_FOCUS_CHANGE;
3666   fevent->focus_change.window = g_object_ref (gtk_widget_get_window (widget));
3667   fevent->focus_change.in = in;
3668 
3669 #if GTK_CHECK_VERSION(2, 22, 0)
3670   gtk_widget_send_focus_change (widget, fevent);
3671 #else
3672   g_object_ref (widget);
3673   if (in)
3674     GTK_OBJECT_FLAGS (widget) |= GTK_HAS_FOCUS;
3675   else
3676     GTK_OBJECT_FLAGS (widget) &= ~(GTK_HAS_FOCUS);
3677   gtk_widget_event (widget, fevent);
3678   g_object_notify (G_OBJECT (widget), "has-focus");
3679   g_object_unref (widget);
3680 #endif
3681 
3682   gdk_event_free (fevent);
3683 }
3684 
desktop_search_dialog_hide(GtkWidget * search_dialog,FmDesktop * desktop)3685 static void desktop_search_dialog_hide(GtkWidget *search_dialog, FmDesktop *desktop)
3686 {
3687     /* disconnect the "changed" signal handler */
3688     if (desktop->search_entry_changed_id != 0)
3689     {
3690         g_signal_handler_disconnect(desktop->search_entry, desktop->search_entry_changed_id);
3691         desktop->search_entry_changed_id = 0;
3692     }
3693 
3694     /* disable the flush timeout */
3695     if (desktop->search_timeout_id != 0)
3696     {
3697         g_source_remove(desktop->search_timeout_id);
3698         desktop->search_timeout_id = 0;
3699     }
3700 
3701     /* send focus-out event */
3702     send_focus_change(desktop->search_entry, FALSE);
3703     gtk_widget_hide(search_dialog);
3704     gtk_entry_set_text(GTK_ENTRY(desktop->search_entry), "");
3705 }
3706 
desktop_search_delete_event(GtkWidget * widget,GdkEventAny * evt,FmDesktop * desktop)3707 static gboolean desktop_search_delete_event(GtkWidget *widget, GdkEventAny *evt,
3708                                             FmDesktop *desktop)
3709 {
3710     /* hide the search dialog */
3711     desktop_search_dialog_hide(widget, desktop);
3712     return TRUE;
3713 }
3714 
desktop_search_timeout(gpointer user_data)3715 static gboolean desktop_search_timeout(gpointer user_data)
3716 {
3717     FmDesktop *desktop = FM_DESKTOP(user_data);
3718 
3719     if (!g_source_is_destroyed(g_main_current_source()))
3720         desktop_search_dialog_hide(desktop->search_window, desktop);
3721     return FALSE;
3722 }
3723 
desktop_search_timeout_destroy(gpointer user_data)3724 static void desktop_search_timeout_destroy(gpointer user_data)
3725 {
3726     FM_DESKTOP(user_data)->search_timeout_id = 0;
3727 }
3728 
desktop_search_move(GtkWidget * widget,FmDesktop * desktop,gboolean move_up)3729 static void desktop_search_move(GtkWidget *widget, FmDesktop *desktop,
3730                                 gboolean move_up)
3731 {
3732     GtkTreeModel *model;
3733     const gchar *text;
3734     char *casefold, *key, *name;
3735     FmDesktopItem *item;
3736     GtkTreeIter it;
3737     gboolean found = FALSE;
3738 
3739     /* check if we have a model */
3740     if (desktop->model == NULL)
3741         return;
3742     model = GTK_TREE_MODEL(desktop->model);
3743 
3744     /* determine the current text for the search entry */
3745     text = gtk_entry_get_text(GTK_ENTRY(desktop->search_entry));
3746     if (G_UNLIKELY(text == NULL || text[0] == '\0'))
3747         return;
3748 
3749     /* determine the iterator of the focused item */
3750     if (!get_focused_item(desktop->focus, model, &it))
3751         return;
3752 
3753     /* normalize the pattern */
3754     casefold = g_utf8_casefold(text, -1);
3755     key = g_utf8_normalize(casefold, -1, G_NORMALIZE_ALL);
3756     g_free(casefold);
3757     /* let find matched item now */
3758     if (move_up)
3759     {
3760 #if GTK_CHECK_VERSION(3, 0, 0)
3761         while (!found && gtk_tree_model_iter_previous(model, &it))
3762 #else
3763         GtkTreePath *tp = gtk_tree_model_get_path(model, &it);
3764         while (!found && gtk_tree_path_prev(tp) && gtk_tree_model_get_iter(model, &it, tp))
3765 #endif
3766         {
3767             item = fm_folder_model_get_item_userdata(desktop->model, &it);
3768             casefold = g_utf8_casefold(fm_file_info_get_disp_name(item->fi), -1);
3769             name = g_utf8_normalize(casefold, -1, G_NORMALIZE_ALL);
3770             g_free(casefold);
3771             found = (strncmp(name, key, strlen(key)) == 0);
3772             g_free(name);
3773         }
3774 #if !GTK_CHECK_VERSION(3, 0, 0)
3775         gtk_tree_path_free(tp);
3776 #endif
3777     }
3778     else
3779     {
3780         while (!found && gtk_tree_model_iter_next(model, &it))
3781         {
3782             item = fm_folder_model_get_item_userdata(desktop->model, &it);
3783             casefold = g_utf8_casefold(fm_file_info_get_disp_name(item->fi), -1);
3784             name = g_utf8_normalize(casefold, -1, G_NORMALIZE_ALL);
3785             g_free(casefold);
3786             found = (strncmp(name, key, strlen(key)) == 0);
3787             g_free(name);
3788         }
3789     }
3790     g_free(key);
3791 
3792     if (!found)
3793         return;
3794 
3795     /* unselect all items */
3796     _unselect_all(FM_FOLDER_VIEW(desktop));
3797     /* focus found item */
3798     _focus_and_select_focused_item(desktop, item);
3799 }
3800 
desktop_search_scroll_event(GtkWidget * widget,GdkEventScroll * evt,FmDesktop * desktop)3801 static gboolean desktop_search_scroll_event(GtkWidget *widget,
3802                                             GdkEventScroll *evt,
3803                                             FmDesktop *desktop)
3804 {
3805     g_return_val_if_fail(GTK_IS_WIDGET(widget), FALSE);
3806     g_return_val_if_fail(FM_IS_DESKTOP(desktop), FALSE);
3807 
3808     if (evt->direction == GDK_SCROLL_UP)
3809         desktop_search_move(widget, desktop, TRUE);
3810     else if (evt->direction == GDK_SCROLL_DOWN)
3811         desktop_search_move(widget, desktop, FALSE);
3812     else
3813         return FALSE;
3814 
3815     return TRUE;
3816 }
3817 
desktop_search_update_timeout(FmDesktop * desktop)3818 static void desktop_search_update_timeout(FmDesktop *desktop)
3819 {
3820     if (desktop->search_timeout_id == 0)
3821         return;
3822     /* drop the previous timeout */
3823     g_source_remove(desktop->search_timeout_id);
3824     /* schedule a new timeout */
3825     desktop->search_timeout_id = gdk_threads_add_timeout_full(G_PRIORITY_LOW,
3826                                                               DESKTOP_SEARCH_DIALOG_TIMEOUT,
3827                                                               desktop_search_timeout,
3828                                                               desktop,
3829                                                               desktop_search_timeout_destroy);
3830 }
3831 
desktop_search_key_press_event(GtkWidget * widget,GdkEventKey * evt,FmDesktop * desktop)3832 static gboolean desktop_search_key_press_event(GtkWidget *widget,
3833                                                GdkEventKey *evt,
3834                                                FmDesktop *desktop)
3835 {
3836     g_return_val_if_fail(GTK_IS_WIDGET(widget), FALSE);
3837     g_return_val_if_fail(FM_IS_DESKTOP(desktop), FALSE);
3838 
3839     /* close window and cancel the search */
3840     if (evt->keyval == GDK_KEY_Escape || evt->keyval == GDK_KEY_Tab)
3841         desktop_search_dialog_hide(widget, desktop);
3842 
3843     /* select previous matching iter */
3844     else if (evt->keyval == GDK_KEY_Up || evt->keyval == GDK_KEY_KP_Up)
3845         desktop_search_move(widget, desktop, TRUE);
3846 
3847     else if (((evt->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
3848              && (evt->keyval == GDK_KEY_g || evt->keyval == GDK_KEY_G))
3849         desktop_search_move(widget, desktop, TRUE);
3850 
3851     /* select next matching iter */
3852     else if (evt->keyval == GDK_KEY_Down || evt->keyval == GDK_KEY_KP_Down)
3853         desktop_search_move(widget, desktop, FALSE);
3854 
3855     else if (((evt->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == GDK_CONTROL_MASK)
3856              && (evt->keyval == GDK_KEY_g || evt->keyval == GDK_KEY_G))
3857         desktop_search_move(widget, desktop, FALSE);
3858 
3859     else
3860         return FALSE;
3861 
3862     /* renew the flush timeout */
3863     desktop_search_update_timeout(desktop);
3864 
3865     return TRUE;
3866 }
3867 
desktop_search_activate(GtkEntry * entry,FmDesktop * desktop)3868 static void desktop_search_activate(GtkEntry *entry, FmDesktop *desktop)
3869 {
3870     GtkTreeModel *model;
3871     GtkTreePath *tp;
3872     GtkTreeIter it;
3873 
3874     /* hide the interactive search dialog */
3875     desktop_search_dialog_hide(desktop->search_window, desktop);
3876 
3877     /* check if we have a cursor item, and if so, activate it */
3878     if (desktop->focus)
3879     {
3880         /* only activate the cursor item if it's selected */
3881         if (desktop->focus->is_selected)
3882         {
3883             model = GTK_TREE_MODEL(desktop->model);
3884             if(get_focused_item(desktop->focus, model, &it))
3885             {
3886                 tp = gtk_tree_model_get_path(model, &it);
3887                 fm_folder_view_item_clicked(FM_FOLDER_VIEW(desktop), tp, FM_FV_ACTIVATED);
3888                 if(tp)
3889                     gtk_tree_path_free(tp);
3890             }
3891         }
3892     }
3893 }
3894 
3895 #if GTK_CHECK_VERSION(2, 20, 0)
desktop_search_preedit_changed(GtkEntry * entry,gchar * preedit,FmDesktop * desktop)3896 static void desktop_search_preedit_changed(GtkEntry *entry, gchar *preedit,
3897                                            FmDesktop *desktop)
3898 #else
3899 static void desktop_search_preedit_changed(GtkIMContext *im_context,
3900                                            FmDesktop *desktop)
3901 #endif
3902 {
3903     desktop->search_imcontext_changed = TRUE;
3904 
3905     /* re-register the search timeout */
3906     desktop_search_update_timeout(desktop);
3907 }
3908 
desktop_search_position(FmDesktop * desktop)3909 static void desktop_search_position(FmDesktop *desktop)
3910 {
3911     GtkRequisition requisition;
3912     GdkRectangle geom;
3913     gint x, y;
3914 
3915     /* make sure the search dialog is realized */
3916     gtk_widget_realize(desktop->search_window);
3917 
3918 #if GTK_CHECK_VERSION(3, 0, 0)
3919     gtk_widget_get_preferred_size(desktop->search_window, NULL, &requisition);
3920 #else
3921     gtk_widget_size_request(desktop->search_window, &requisition);
3922 #endif
3923 
3924     /* put it into right upper corner */
3925     gdk_screen_get_monitor_geometry(gtk_widget_get_screen((GtkWidget*)desktop),
3926                                     desktop->monitor, &geom);
3927     x = geom.x + desktop->working_area.x + desktop->working_area.width - requisition.width;
3928     y = geom.y + desktop->working_area.y;
3929 
3930     gtk_window_move(GTK_WINDOW(desktop->search_window), x, y);
3931 }
3932 
desktop_search_init(GtkWidget * search_entry,FmDesktop * desktop)3933 static void desktop_search_init(GtkWidget *search_entry, FmDesktop *desktop)
3934 {
3935     GtkTreeModel *model;
3936     const gchar *text;
3937     char *casefold, *key, *name;
3938     FmDesktopItem *item;
3939     GtkTreeIter it;
3940     gboolean found = FALSE;
3941 
3942     /* check if we have a model */
3943     if (desktop->model == NULL)
3944         return;
3945     model = GTK_TREE_MODEL(desktop->model);
3946 
3947     /* renew the flush timeout */
3948     desktop_search_update_timeout(desktop);
3949 
3950     /* determine the current text for the search entry */
3951     text = gtk_entry_get_text(GTK_ENTRY(desktop->search_entry));
3952     if (G_UNLIKELY(text == NULL || text[0] == '\0'))
3953         return;
3954 
3955     /* unselect all items */
3956     _unselect_all(FM_FOLDER_VIEW(desktop));
3957 
3958     /* normalize the pattern */
3959     casefold = g_utf8_casefold(text, -1);
3960     key = g_utf8_normalize(casefold, -1, G_NORMALIZE_ALL);
3961     g_free(casefold);
3962     /* find first matched item now */
3963     if (gtk_tree_model_get_iter_first(model, &it)) do
3964     {
3965         item = fm_folder_model_get_item_userdata(desktop->model, &it);
3966         casefold = g_utf8_casefold(fm_file_info_get_disp_name(item->fi), -1);
3967         name = g_utf8_normalize(casefold, -1, G_NORMALIZE_ALL);
3968         g_free(casefold);
3969         found = (strncmp(name, key, strlen(key)) == 0);
3970         g_free(name);
3971     }
3972     while (!found && gtk_tree_model_iter_next(model, &it));
3973     g_free(key);
3974 
3975     /* focus found item */
3976     if (!found)
3977         return;
3978     _focus_and_select_focused_item(desktop, item);
3979 }
3980 
desktop_search_ensure_window(FmDesktop * desktop)3981 static void desktop_search_ensure_window(FmDesktop *desktop)
3982 {
3983     GtkWindow *window;
3984     GtkWidget *frame;
3985     GtkWidget *vbox;
3986 
3987     /* check if we already have a search window */
3988     if (G_LIKELY(desktop->search_window != NULL))
3989         return;
3990 
3991     /* allocate a new search window */
3992     desktop->search_window = gtk_window_new(GTK_WINDOW_POPUP);
3993     window = GTK_WINDOW(desktop->search_window);
3994     gtk_window_group_add_window(win_group, window);
3995     gtk_window_set_modal(window, TRUE);
3996     gtk_window_set_screen(window, gtk_widget_get_screen(GTK_WIDGET(desktop)));
3997     /* connect signal handlers */
3998     g_signal_connect(window, "delete-event", G_CALLBACK(desktop_search_delete_event), desktop);
3999     g_signal_connect(window, "scroll-event", G_CALLBACK(desktop_search_scroll_event), desktop);
4000     g_signal_connect(window, "key-press-event", G_CALLBACK(desktop_search_key_press_event), desktop);
4001 
4002     /* allocate the frame widget */
4003     frame = g_object_new(GTK_TYPE_FRAME, "shadow-type", GTK_SHADOW_ETCHED_IN, NULL);
4004     gtk_container_add(GTK_CONTAINER(window), frame);
4005     gtk_widget_show(frame);
4006 
4007     /* allocate the vertical box */
4008     vbox = g_object_new(GTK_TYPE_VBOX, "border-width", 3, NULL);
4009     gtk_container_add(GTK_CONTAINER(frame), vbox);
4010     gtk_widget_show(vbox);
4011 
4012     /* allocate the search entry widget */
4013     desktop->search_entry = gtk_entry_new();
4014     g_signal_connect(desktop->search_entry, "activate", G_CALLBACK(desktop_search_activate), desktop);
4015 #if GTK_CHECK_VERSION(2, 20, 0)
4016     g_signal_connect(desktop->search_entry, "preedit-changed",
4017                      G_CALLBACK(desktop_search_preedit_changed), desktop);
4018 #else
4019     g_signal_connect(GTK_ENTRY(desktop->search_entry)->im_context, "preedit-changed",
4020                      G_CALLBACK(desktop_search_preedit_changed), desktop);
4021 #endif
4022     gtk_box_pack_start(GTK_BOX(vbox), desktop->search_entry, TRUE, TRUE, 0);
4023     gtk_widget_realize(desktop->search_entry);
4024     gtk_widget_show(desktop->search_entry);
4025 }
4026 
desktop_search_start(FmDesktop * desktop,gboolean keybinding)4027 static gboolean desktop_search_start(FmDesktop *desktop, gboolean keybinding)
4028 {
4029     GTypeClass *klass;
4030 
4031     /* check if we already display the search window */
4032     if (desktop->search_window != NULL && gtk_widget_get_visible(desktop->search_window))
4033         return TRUE;
4034 
4035     desktop_search_ensure_window(desktop);
4036 
4037     /* clear search entry if we were started by a keybinding */
4038     if (G_UNLIKELY(keybinding))
4039         gtk_entry_set_text(GTK_ENTRY(desktop->search_entry), "");
4040 
4041     /* determine the position for the search dialog */
4042     desktop_search_position(desktop);
4043 
4044     /* display the search dialog */
4045     gtk_widget_show(desktop->search_window);
4046 
4047     /* connect "changed" signal for the entry */
4048     if (G_UNLIKELY(desktop->search_entry_changed_id == 0))
4049         desktop->search_entry_changed_id = g_signal_connect(G_OBJECT(desktop->search_entry),
4050                                                             "changed",
4051                                                             G_CALLBACK(desktop_search_init),
4052                                                             desktop);
4053 
4054     /* start the search timeout */
4055     desktop->search_timeout_id = gdk_threads_add_timeout_full(G_PRIORITY_LOW,
4056                                                               DESKTOP_SEARCH_DIALOG_TIMEOUT,
4057                                                               desktop_search_timeout,
4058                                                               desktop,
4059                                                               desktop_search_timeout_destroy);
4060 
4061     /* grab focus will select all the text, we don't want that to happen, so we
4062      * call the parent instance and bypass the selection change. This is probably
4063      * really hackish, but GtkTreeView does it as well *hrhr*
4064      */
4065     klass = g_type_class_peek_parent(GTK_ENTRY_GET_CLASS(desktop->search_entry));
4066     (*GTK_WIDGET_CLASS(klass)->grab_focus)(desktop->search_entry);
4067 
4068     /* send focus-in event */
4069     send_focus_change(desktop->search_entry, TRUE);
4070 
4071     /* search first matching iter */
4072     desktop_search_init(desktop->search_entry, desktop);
4073 
4074     return TRUE;
4075 }
4076 
on_key_press(GtkWidget * w,GdkEventKey * evt)4077 static gboolean on_key_press(GtkWidget* w, GdkEventKey* evt)
4078 {
4079     FmDesktop* desktop = (FmDesktop*)w;
4080     FmDesktopItem* item;
4081     int modifier = (evt->state & gtk_accelerator_get_default_mod_mask());
4082     FmPathList* sels;
4083     GtkTreeModel* model;
4084     GtkTreePath* tp = NULL;
4085     GtkTreeIter it;
4086     char *old_text, *new_text;
4087     GdkScreen *screen;
4088     GdkEvent *new_event;
4089     guint popup_menu_id;
4090     gboolean retval;
4091 
4092     switch (evt->keyval)
4093     {
4094     case GDK_KEY_Escape:
4095         if (desktop->rubber_bending)
4096         {
4097             /* cancel rubberbanding now */
4098             _stop_rubberbanding(desktop, desktop->drag_start_x, desktop->drag_start_y);
4099             return TRUE;
4100         }
4101         break;
4102     case GDK_KEY_Left:
4103         item = get_nearest_item(desktop, desktop->focus, GTK_DIR_LEFT);
4104         if(item)
4105         {
4106             if(0 == modifier)
4107             {
4108                 _unselect_all(FM_FOLDER_VIEW(desktop));
4109                 item->is_selected = TRUE;
4110                 fm_desktop_item_selected_changed(desktop, item);
4111             }
4112             set_focused_item(desktop, item);
4113         }
4114         return TRUE;
4115     case GDK_KEY_Right:
4116         item = get_nearest_item(desktop, desktop->focus, GTK_DIR_RIGHT);
4117         if(item)
4118         {
4119             if(0 == modifier)
4120             {
4121                 _unselect_all(FM_FOLDER_VIEW(desktop));
4122                 item->is_selected = TRUE;
4123                 fm_desktop_item_selected_changed(desktop, item);
4124             }
4125             set_focused_item(desktop, item);
4126         }
4127         return TRUE;
4128     case GDK_KEY_Up:
4129         item = get_nearest_item(desktop, desktop->focus, GTK_DIR_UP);
4130         if(item)
4131         {
4132             if(0 == modifier)
4133             {
4134                 _unselect_all(FM_FOLDER_VIEW(desktop));
4135                 item->is_selected = TRUE;
4136                 fm_desktop_item_selected_changed(desktop, item);
4137             }
4138             set_focused_item(desktop, item);
4139         }
4140         return TRUE;
4141     case GDK_KEY_Down:
4142         item = get_nearest_item(desktop, desktop->focus, GTK_DIR_DOWN);
4143         if(item)
4144         {
4145             if(0 == modifier)
4146             {
4147                 _unselect_all(FM_FOLDER_VIEW(desktop));
4148                 item->is_selected = TRUE;
4149                 fm_desktop_item_selected_changed(desktop, item);
4150             }
4151             set_focused_item(desktop, item);
4152         }
4153         return TRUE;
4154     case GDK_KEY_space:
4155         if(modifier & GDK_CONTROL_MASK)
4156         {
4157             if(desktop->focus)
4158             {
4159                 desktop->focus->is_selected = !desktop->focus->is_selected;
4160                 redraw_item(desktop, desktop->focus);
4161                 fm_desktop_item_selected_changed(desktop, desktop->focus);
4162             }
4163             return TRUE;
4164         }
4165         break;
4166     case GDK_KEY_F2:
4167         sels = _dup_selected_file_paths(FM_FOLDER_VIEW(desktop));
4168         if(sels)
4169         {
4170             fm_rename_file(GTK_WINDOW(desktop), fm_path_list_peek_head(sels));
4171             fm_path_list_unref(sels);
4172             return TRUE;
4173         }
4174         break;
4175     case GDK_KEY_Return:
4176     case GDK_KEY_ISO_Enter:
4177     case GDK_KEY_KP_Enter:
4178         if(modifier == 0 && desktop->focus)
4179         {
4180             model = GTK_TREE_MODEL(desktop->model);
4181             if(get_focused_item(desktop->focus, model, &it))
4182             {
4183                 tp = gtk_tree_model_get_path(model, &it);
4184                 fm_folder_view_item_clicked(FM_FOLDER_VIEW(desktop), tp, FM_FV_ACTIVATED);
4185                 if(tp)
4186                     gtk_tree_path_free(tp);
4187             }
4188             return TRUE;
4189         }
4190         break;
4191     case GDK_KEY_F:
4192     case GDK_KEY_f:
4193         if (modifier == GDK_CONTROL_MASK)
4194             return desktop_search_start(desktop, TRUE);
4195         break;
4196     }
4197     if (GTK_WIDGET_CLASS(fm_desktop_parent_class)->key_press_event(w, evt))
4198         return TRUE;
4199 
4200     /* well, let try interactive search now */
4201     desktop_search_ensure_window(desktop);
4202 
4203     /* make sure the search window is realized */
4204     gtk_widget_realize(desktop->search_window);
4205 
4206     /* make a copy of the current text */
4207     old_text = gtk_editable_get_chars(GTK_EDITABLE(desktop->search_entry), 0, -1);
4208 
4209     /* make sure we don't accidently popup the context menu */
4210     popup_menu_id = g_signal_connect(desktop->search_entry, "popup-menu", G_CALLBACK(gtk_true), NULL);
4211 
4212     /* move the search window offscreen */
4213     screen = gtk_widget_get_screen(w);
4214     gtk_window_move(GTK_WINDOW(desktop->search_window),
4215                     gdk_screen_get_width(screen) + 1,
4216                     gdk_screen_get_height(screen) + 1);
4217     gtk_widget_show(desktop->search_window);
4218 
4219     /* allocate a new event to forward */
4220     new_event = gdk_event_copy((GdkEvent *)evt);
4221     g_object_unref(new_event->key.window);
4222     new_event->key.window = g_object_ref(gtk_widget_get_window(desktop->search_entry));
4223 
4224     /* send the event to the search entry. If the "preedit-changed" signal is
4225      * emitted during this event, priv->search_imcontext_changed will be set. */
4226     desktop->search_imcontext_changed = FALSE;
4227     retval = gtk_widget_event(desktop->search_entry, new_event);
4228     gtk_widget_hide(desktop->search_window);
4229 
4230     /* release the temporary event */
4231     gdk_event_free(new_event);
4232 
4233     /* disconnect the popup menu prevention */
4234     g_signal_handler_disconnect(desktop->search_entry, popup_menu_id);
4235 
4236     /* we check to make sure that the entry tried to handle the,
4237      * and that the text has actually changed. */
4238     new_text = gtk_editable_get_chars(GTK_EDITABLE(desktop->search_entry), 0, -1);
4239     retval = retval && (strcmp(new_text, old_text) != 0);
4240     g_free(old_text);
4241     g_free(new_text);
4242 
4243     /* if we're in a preedit or the text was modified */
4244     if (desktop->search_imcontext_changed || retval)
4245     {
4246         if (desktop_search_start(desktop, FALSE))
4247         {
4248             gtk_widget_grab_focus(w);
4249             return TRUE;
4250         }
4251         else
4252         {
4253             gtk_entry_set_text(GTK_ENTRY(desktop->search_entry), "");
4254             return FALSE;
4255         }
4256     }
4257     return FALSE;
4258 }
4259 
4260 #if 0
4261 static void on_style_set(GtkWidget* w, GtkStyle* prev)
4262 {
4263     FmDesktop* self = (FmDesktop*)w;
4264     PangoContext* pc = gtk_widget_get_pango_context(w);
4265     if(font_desc)
4266         pango_context_set_font_description(pc, font_desc);
4267     pango_layout_context_changed(self->pl);
4268 }
4269 #endif
4270 
on_direction_changed(GtkWidget * w,GtkTextDirection prev)4271 static void on_direction_changed(GtkWidget* w, GtkTextDirection prev)
4272 {
4273     FmDesktop* self = (FmDesktop*)w;
4274     pango_layout_context_changed(self->pl);
4275     queue_layout_items(self);
4276 }
4277 
on_realize(GtkWidget * w)4278 static void on_realize(GtkWidget* w)
4279 {
4280     FmDesktop* self = (FmDesktop*)w;
4281     PangoFontDescription *font_desc;
4282     PangoContext* pc;
4283 #if GTK_CHECK_VERSION(3, 0, 0)
4284     char *css_data;
4285 #endif
4286 
4287     GTK_WIDGET_CLASS(fm_desktop_parent_class)->realize(w);
4288     gtk_window_set_skip_pager_hint(GTK_WINDOW(w), TRUE);
4289     gtk_window_set_skip_taskbar_hint(GTK_WINDOW(w), TRUE);
4290     gtk_window_set_resizable((GtkWindow*)w, FALSE);
4291 
4292     load_config(self);
4293     /* setup self->conf now if it wasn't loaded above */
4294     if (!self->conf.configured)
4295     {
4296         copy_desktop_config(&self->conf, &app_config->desktop_section);
4297         queue_config_save(self);
4298     }
4299     /* copy found configuration to use by next monitor */
4300     else if (!app_config->desktop_section.configured)
4301         copy_desktop_config(&app_config->desktop_section, &self->conf);
4302     update_background(self, -1);
4303     /* set a proper desktop font if needed */
4304     if (self->conf.desktop_font == NULL)
4305         self->conf.desktop_font = g_strdup("Sans 12");
4306     font_desc = pango_font_description_from_string(self->conf.desktop_font);
4307     pc = gtk_widget_get_pango_context(w);
4308     pango_context_set_font_description(pc, font_desc);
4309     pango_font_description_free(font_desc);
4310 #if GTK_CHECK_VERSION(3, 0, 0)
4311     css_data = g_strdup_printf("FmDesktop {\n"
4312                                    "background-color: #%02x%02x%02x\n"
4313                                "}",
4314                                self->conf.desktop_bg.red/256,
4315                                self->conf.desktop_bg.green/256,
4316                                self->conf.desktop_bg.blue/256);
4317     gtk_css_provider_load_from_data(self->css, css_data, -1, NULL);
4318     g_free(css_data);
4319 #endif
4320     if (self->layout_pending)
4321         queue_layout_items(self);
4322 }
4323 
on_focus_in(GtkWidget * w,GdkEventFocus * evt)4324 static gboolean on_focus_in(GtkWidget* w, GdkEventFocus* evt)
4325 {
4326     FmDesktop* self = (FmDesktop*) w;
4327     GtkTreeIter it;
4328 #if !GTK_CHECK_VERSION(2, 22, 0)
4329     GTK_WIDGET_SET_FLAGS(w, GTK_HAS_FOCUS);
4330 #endif
4331     if(!self->focus && self->model
4332        && gtk_tree_model_get_iter_first(GTK_TREE_MODEL(self->model), &it))
4333     {
4334         self->focus = fm_folder_model_get_item_userdata(self->model, &it);
4335         fm_desktop_accessible_focus_set(self, self->focus);
4336     }
4337     if(self->focus)
4338         redraw_item(self, self->focus);
4339     return FALSE;
4340 }
4341 
on_focus_out(GtkWidget * w,GdkEventFocus * evt)4342 static gboolean on_focus_out(GtkWidget* w, GdkEventFocus* evt)
4343 {
4344     FmDesktop* self = (FmDesktop*) w;
4345     if(self->focus)
4346     {
4347 #if !GTK_CHECK_VERSION(2, 22, 0)
4348         GTK_WIDGET_UNSET_FLAGS(w, GTK_HAS_FOCUS);
4349 #endif
4350         redraw_item(self, self->focus);
4351     }
4352     return FALSE;
4353 }
4354 
4355 /* ---- Drag & Drop support ---- */
on_drag_motion(GtkWidget * dest_widget,GdkDragContext * drag_context,gint x,gint y,guint time)4356 static gboolean on_drag_motion (GtkWidget *dest_widget,
4357                                 GdkDragContext *drag_context,
4358                                 gint x, gint y, guint time)
4359 {
4360     GdkAtom target;
4361     GdkDragAction action = 0;
4362     FmDesktop* desktop = FM_DESKTOP(dest_widget);
4363     FmDesktopItem* item;
4364     GtkTreeIter it;
4365 
4366     /* we don't support drag & drop if no model is set */
4367     if (desktop->model == NULL)
4368     {
4369         fm_dnd_dest_set_dest_file(desktop->dnd_dest, NULL);
4370         gdk_drag_status(drag_context, action, time);
4371         return FALSE;
4372     }
4373 
4374     /* check if we're dragging over an item */
4375     item = hit_test(desktop, &it, x, y);
4376 
4377     /* handle moving desktop items */
4378     if(!item)
4379     {
4380         if(fm_drag_context_has_target(drag_context, desktop_atom)
4381            && (gdk_drag_context_get_actions(drag_context) & GDK_ACTION_MOVE))
4382         {
4383             /* desktop item is being dragged */
4384             action = GDK_ACTION_MOVE; /* move desktop items */
4385             fm_dnd_dest_set_dest_file(desktop->dnd_dest, NULL);
4386         }
4387     }
4388 
4389     /* FmDndDest will do the rest */
4390     if(!action)
4391     {
4392         fm_dnd_dest_set_dest_file(desktop->dnd_dest,
4393                                   item ? item->fi : fm_folder_get_info(fm_folder_model_get_folder(desktop->model)));
4394         target = fm_dnd_dest_find_target(desktop->dnd_dest, drag_context);
4395         if(target != GDK_NONE &&
4396            fm_dnd_dest_is_target_supported(desktop->dnd_dest, target))
4397             action = fm_dnd_dest_get_default_action(desktop->dnd_dest, drag_context, target);
4398     }
4399     gdk_drag_status(drag_context, action, time);
4400 
4401     if(desktop->drop_hilight != item)
4402     {
4403         FmDesktopItem* old_drop = desktop->drop_hilight;
4404         if(action) /* don't hilight non-dropable item, see #3591767 */
4405             desktop->drop_hilight = item;
4406         if(old_drop)
4407             redraw_item(desktop, old_drop);
4408         if(item && action)
4409             redraw_item(desktop, item);
4410     }
4411 
4412     return (action != 0);
4413 }
4414 
on_drag_leave(GtkWidget * dest_widget,GdkDragContext * drag_context,guint time)4415 static void on_drag_leave (GtkWidget *dest_widget,
4416                            GdkDragContext *drag_context,
4417                            guint time)
4418 {
4419     FmDesktop* desktop = FM_DESKTOP(dest_widget);
4420 
4421     if(desktop->drop_hilight)
4422     {
4423         FmDesktopItem* old_drop = desktop->drop_hilight;
4424         desktop->drop_hilight = NULL;
4425         redraw_item(desktop, old_drop);
4426     }
4427 }
4428 
on_drag_drop(GtkWidget * dest_widget,GdkDragContext * drag_context,gint x,gint y,guint time)4429 static gboolean on_drag_drop (GtkWidget *dest_widget,
4430                               GdkDragContext *drag_context,
4431                               gint x, gint y, guint time)
4432 {
4433     FmDesktop* desktop = FM_DESKTOP(dest_widget);
4434     FmDesktopItem* item;
4435     GtkTreeIter it;
4436 
4437     /* check if we're dropping on an item */
4438     item = hit_test(desktop, &it, x, y);
4439 
4440     /* handle moving desktop items */
4441     if(!item)
4442     {
4443         if(fm_drag_context_has_target(drag_context, desktop_atom)
4444            && (gdk_drag_context_get_actions(drag_context) & GDK_ACTION_MOVE))
4445         {
4446             /* desktop item is being dragged */
4447             gtk_drag_get_data(dest_widget, drag_context, desktop_atom, time);
4448             return TRUE;
4449         }
4450     }
4451     return FALSE;
4452 }
4453 
on_drag_data_received(GtkWidget * dest_widget,GdkDragContext * drag_context,gint x,gint y,GtkSelectionData * sel_data,guint info,guint time)4454 static void on_drag_data_received (GtkWidget *dest_widget,
4455                                    GdkDragContext *drag_context,
4456                                    gint x, gint y, GtkSelectionData *sel_data,
4457                                    guint info, guint time)
4458 {
4459     FmDesktop* desktop = FM_DESKTOP(dest_widget);
4460     GList *items, *l;
4461     int offset_x, offset_y;
4462 
4463     if(info != FM_DND_DEST_DESKTOP_ITEM)
4464         return;
4465 
4466     /* desktop items are being dragged */
4467     items = get_selected_items(desktop, NULL);
4468     offset_x = x - desktop->drag_start_x;
4469     offset_y = y - desktop->drag_start_y;
4470     for(l = items; l; l=l->next)
4471     {
4472         FmDesktopItem* item = (FmDesktopItem*)l->data;
4473         move_item(desktop, item, item->area.x + offset_x, item->area.y + offset_y, FALSE);
4474     }
4475     g_list_free(items);
4476 
4477     /* save position of desktop icons on next idle */
4478     queue_config_save(desktop);
4479 
4480     queue_layout_items(desktop);
4481 
4482     gtk_drag_finish(drag_context, TRUE, FALSE, time);
4483 }
4484 
_create_drag_icon(FmDesktop * desktop,gint * x,gint * y)4485 static GdkPixbuf *_create_drag_icon(FmDesktop *desktop, gint *x, gint *y)
4486 {
4487     GtkTreeModel *model;
4488     FmDesktopItem *item;
4489     cairo_surface_t *s;
4490     GdkPixbuf *pixbuf;
4491     cairo_t *cr;
4492     GdkPixbuf *icon;
4493     GtkTreeIter it;
4494     GdkRectangle area, icon_rect;
4495 #if !GTK_CHECK_VERSION(3, 0, 0)
4496     guchar *dest_data, *src_data;
4497     int dest_stride, src_stride, _x, _y;
4498 #endif
4499 
4500     if (!desktop->model)
4501         return NULL;
4502     model = GTK_TREE_MODEL(desktop->model);
4503     if (!gtk_tree_model_get_iter_first(model, &it))
4504         return NULL;
4505 
4506     /* determine the size of complete pixbuf */
4507     area.width = 0; /* mark it */
4508     do
4509     {
4510         item = fm_folder_model_get_item_userdata(desktop->model, &it);
4511         if (!item->is_selected)
4512             continue;
4513         if (area.width == 0)
4514             area = item->icon_rect;
4515         else
4516         {
4517             if (item->icon_rect.x < area.x)
4518             {
4519                 area.width += (area.x - item->icon_rect.x);
4520                 area.x = item->icon_rect.x;
4521             }
4522             if (item->icon_rect.x + item->icon_rect.width > area.x + area.width)
4523                 area.width = item->icon_rect.x + item->icon_rect.width - area.x;
4524             if (item->icon_rect.y < area.y)
4525             {
4526                 area.height += (area.y - item->icon_rect.y);
4527                 area.y = item->icon_rect.y;
4528             }
4529             if (item->icon_rect.y + item->icon_rect.height > area.y + area.height)
4530                 area.height = item->icon_rect.y + item->icon_rect.height - area.y;
4531         }
4532     }
4533     while(gtk_tree_model_iter_next(model, &it));
4534 
4535     /* now create the pixbuf */
4536     if (area.width == 0) /* no selection??? */
4537         return NULL;
4538     area.width += 2;
4539     area.height += 2;
4540     s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, area.width, area.height);
4541     cr = cairo_create(s);
4542 
4543     gtk_tree_model_get_iter_first(model, &it);
4544     do
4545     {
4546         item = fm_folder_model_get_item_userdata(desktop->model, &it);
4547         if (!item->is_selected)
4548             continue;
4549         /* FIXME: should we render name too, or is it too heavy? */
4550         icon = NULL;
4551         gtk_tree_model_get(model, &it, FM_FOLDER_MODEL_COL_ICON, &icon, -1);
4552         /* draw the icon */
4553         if (icon)
4554         {
4555             icon_rect.x = item->icon_rect.x - area.x + 1;
4556             icon_rect.width = item->icon_rect.width;
4557             icon_rect.y = item->icon_rect.y - area.y + 1;
4558             icon_rect.height = item->icon_rect.height;
4559 //g_debug("icon at %d %d %d %d",icon_rect.x,icon_rect.y,icon_rect.width,icon_rect.height);
4560             gdk_cairo_set_source_pixbuf(cr, icon, icon_rect.x, icon_rect.y);
4561             gdk_cairo_rectangle(cr, &icon_rect);
4562             cairo_fill(cr);
4563             g_object_unref(icon);
4564         }
4565     }
4566     while(gtk_tree_model_iter_next(model, &it));
4567 
4568     cairo_destroy (cr);
4569 #if GTK_CHECK_VERSION(3, 0, 0)
4570     pixbuf = gdk_pixbuf_get_from_surface(s, 0, 0, area.width, area.height);
4571 #else
4572     /* GTK2 has no API gdk_pixbuf_get_from_surface() but we cannot
4573        preserve transparency using gdk_pixbuf_get_from_drawable() so
4574        therefore have to implement that API behavior here instead */
4575     pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, area.width, area.height);
4576     cairo_surface_flush(s);
4577     dest_data = gdk_pixbuf_get_pixels(pixbuf);
4578     dest_stride = gdk_pixbuf_get_rowstride(pixbuf);
4579     src_data = cairo_image_surface_get_data(s);
4580     src_stride = cairo_image_surface_get_stride(s);
4581 
4582     /* convert alpha from cairo_surface_t into GdkPixbuf format */
4583     for (_y = 0; _y < area.height; _y++)
4584     {
4585         guint32 *src = (guint32 *) src_data;
4586 
4587         for (_x = 0; _x < area.width; _x++)
4588         {
4589             guint alpha = src[_x] >> 24;
4590 
4591             if (alpha == 0)
4592             {
4593                 dest_data[_x * 4 + 0] = 0;
4594                 dest_data[_x * 4 + 1] = 0;
4595                 dest_data[_x * 4 + 2] = 0;
4596             }
4597             else
4598             {
4599                 dest_data[_x * 4 + 0] = (((src[_x] & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
4600                 dest_data[_x * 4 + 1] = (((src[_x] & 0x00ff00) >>  8) * 255 + alpha / 2) / alpha;
4601                 dest_data[_x * 4 + 2] = (((src[_x] & 0x0000ff) >>  0) * 255 + alpha / 2) / alpha;
4602             }
4603             dest_data[_x * 4 + 3] = alpha;
4604         }
4605         src_data += src_stride;
4606         dest_data += dest_stride;
4607     }
4608 #endif
4609     cairo_surface_destroy(s);
4610     *x = area.x;
4611     *y = area.y;
4612     return pixbuf;
4613 }
4614 
on_drag_begin(GtkWidget * widget,GdkDragContext * drag_context)4615 static void on_drag_begin (GtkWidget *widget, GdkDragContext *drag_context)
4616 {
4617     /* GTK auto-dragging started a drag, update state */
4618     FmDesktop *desktop = FM_DESKTOP(widget);
4619     gint x, y, icon_x, icon_y;
4620     GdkPixbuf *pix = _create_drag_icon(desktop, &x, &y);
4621 
4622     desktop->dragging = TRUE;
4623 
4624     /* set drag cursor to selected items */
4625     if (pix)
4626     {
4627         icon_x = desktop->drag_start_x - x;
4628         icon_y = desktop->drag_start_y - y;
4629         gtk_drag_set_icon_pixbuf(drag_context, pix, icon_x, icon_y);
4630         g_object_unref(pix);
4631     }
4632 
4633     queue_layout_items(desktop);
4634 }
4635 
on_dnd_src_data_get(FmDndSrc * ds,FmDesktop * desktop)4636 static void on_dnd_src_data_get(FmDndSrc* ds, FmDesktop* desktop)
4637 {
4638     FmFileInfoList* files = _dup_selected_files(FM_FOLDER_VIEW(desktop));
4639     if(files)
4640     {
4641         fm_dnd_src_set_files(ds, files);
4642         fm_file_info_list_unref(files);
4643     }
4644 }
4645 
4646 /* ---------------------------------------------------------------------
4647     FmFolder signal handlers */
4648 
on_folder_start_loading(FmFolder * folder,gpointer user_data)4649 static void on_folder_start_loading(FmFolder* folder, gpointer user_data)
4650 {
4651     /* FIXME: should we delete the model here? */
4652 }
4653 
on_folder_finish_loading(FmFolder * folder,gpointer user_data)4654 static void on_folder_finish_loading(FmFolder* folder, gpointer user_data)
4655 {
4656     FmDesktop* desktop = user_data;
4657 
4658     /* the desktop folder is just loaded, apply desktop items and positions */
4659     if(desktop->monitor < 0)
4660         return;
4661     fm_folder_view_add_popup(FM_FOLDER_VIEW(desktop), GTK_WINDOW(desktop),
4662                              fm_desktop_update_popup);
4663     reload_items(desktop);
4664 }
4665 
on_folder_error(FmFolder * folder,GError * err,FmJobErrorSeverity severity,gpointer user_data)4666 static FmJobErrorAction on_folder_error(FmFolder* folder, GError* err, FmJobErrorSeverity severity, gpointer user_data)
4667 {
4668     if(err->domain == G_IO_ERROR)
4669     {
4670         if(err->code == G_IO_ERROR_NOT_MOUNTED && severity < FM_JOB_ERROR_CRITICAL)
4671         {
4672             FmPath* path = fm_folder_get_path(folder);
4673             if(fm_mount_path(NULL, path, TRUE))
4674                 return FM_JOB_RETRY;
4675         }
4676     }
4677     fm_show_error(NULL, NULL, err->message);
4678     return FM_JOB_CONTINUE;
4679 }
4680 
4681 
4682 #if !GTK_CHECK_VERSION(3, 0, 0)
4683 /* ---------------------------------------------------------------------
4684    We should not follow background changes on the style so we create
4685    a style class for the desktop with dummy set background operator.
4686    For GTK+ 3.0 we add a style provider with high priority instead. */
4687 
_dummy_set_background(GtkStyle * style,GdkWindow * window,GtkStateType state_type)4688 static void _dummy_set_background(GtkStyle *style, GdkWindow *window, GtkStateType state_type)
4689 {
4690 }
4691 
4692 #define FM_DESKTOP_TYPE_STYLE    (fm_desktop_style_get_type())
4693 
4694 typedef struct _FmDesktopStyle FmDesktopStyle;
4695 struct _FmDesktopStyle
4696 {
4697     GtkStyle parent;
4698 };
4699 
4700 typedef struct _FmDesktopStyleClass FmDesktopStyleClass;
4701 struct _FmDesktopStyleClass
4702 {
4703     GtkStyleClass parent_class;
4704 };
4705 
G_DEFINE_TYPE(FmDesktopStyle,fm_desktop_style,GTK_TYPE_STYLE)4706 G_DEFINE_TYPE(FmDesktopStyle, fm_desktop_style, GTK_TYPE_STYLE)
4707 
4708 static void fm_desktop_style_class_init(FmDesktopStyleClass *klass)
4709 {
4710     GtkStyleClass *style_class = GTK_STYLE_CLASS(klass);
4711 
4712     style_class->set_background = _dummy_set_background;
4713 }
4714 
fm_desktop_style_init(FmDesktopStyle * self)4715 static void fm_desktop_style_init(FmDesktopStyle *self)
4716 {
4717 }
4718 #endif
4719 
4720 /* ---------------------------------------------------------------------
4721     FmDesktop class main handlers */
4722 
4723 #if 0
4724 static void on_desktop_model_destroy(gpointer data, GObject* model)
4725 {
4726     g_signal_handlers_disconnect_by_func(app_config, on_big_icon_size_changed, model);
4727     *(gpointer*)data = NULL;
4728 }
4729 #endif
4730 
4731 #if FM_CHECK_VERSION(1, 0, 2)
on_sort_changed(GtkTreeSortable * model,FmDesktop * desktop)4732 static void on_sort_changed(GtkTreeSortable *model, FmDesktop *desktop)
4733 {
4734     FmFolderModelCol by;
4735     FmSortMode type;
4736 
4737     if (!fm_folder_model_get_sort(FM_FOLDER_MODEL(model), &by, &type))
4738         /* FIXME: print error if failed */
4739         return;
4740     if (type == desktop->conf.desktop_sort_type &&
4741         by == desktop->conf.desktop_sort_by) /* not changed */
4742         return;
4743     desktop->conf.desktop_sort_type = type;
4744     desktop->conf.desktop_sort_by = by;
4745     queue_config_save(desktop);
4746 }
4747 #endif
4748 
connect_model(FmDesktop * desktop,FmFolder * folder)4749 static inline void connect_model(FmDesktop *desktop, FmFolder *folder)
4750 {
4751     desktop->model = fm_folder_model_new(folder, FALSE);
4752     g_signal_connect(folder, "start-loading", G_CALLBACK(on_folder_start_loading), desktop);
4753     g_signal_connect(folder, "finish-loading", G_CALLBACK(on_folder_finish_loading), desktop);
4754     g_signal_connect(folder, "error", G_CALLBACK(on_folder_error), desktop);
4755     fm_folder_model_set_icon_size(desktop->model, fm_config->big_icon_size);
4756     g_signal_connect(app_config, "changed::big_icon_size",
4757                      G_CALLBACK(on_big_icon_size_changed), desktop->model);
4758     g_signal_connect(desktop->model, "row-deleting", G_CALLBACK(on_row_deleting), desktop);
4759     g_signal_connect(desktop->model, "row-inserted", G_CALLBACK(on_row_inserted), desktop);
4760     g_signal_connect(desktop->model, "row-deleted", G_CALLBACK(on_row_deleted), desktop);
4761     g_signal_connect(desktop->model, "row-changed", G_CALLBACK(on_row_changed), desktop);
4762     g_signal_connect(desktop->model, "rows-reordered", G_CALLBACK(on_rows_reordered), desktop);
4763 #if FM_CHECK_VERSION(1, 0, 2)
4764     fm_folder_model_set_sort(desktop->model, desktop->conf.desktop_sort_by,
4765                              desktop->conf.desktop_sort_type);
4766     g_signal_connect(desktop->model, "sort-column-changed", G_CALLBACK(on_sort_changed), desktop);
4767 #else
4768     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(desktop->model),
4769                                          desktop->conf.desktop_sort_by,
4770                                          desktop->conf.desktop_sort_type);
4771 #endif
4772     on_folder_start_loading(folder, desktop);
4773     if(fm_folder_is_loaded(folder))
4774         on_folder_finish_loading(folder, desktop);
4775 }
4776 
disconnect_model(FmDesktop * desktop)4777 static inline void disconnect_model(FmDesktop* desktop)
4778 {
4779     FmFolder *folder;
4780 
4781     if (desktop->model == NULL)
4782         return;
4783     folder = fm_folder_model_get_folder(desktop->model);
4784     g_signal_handlers_disconnect_by_func(folder, on_folder_start_loading, desktop);
4785     g_signal_handlers_disconnect_by_func(folder, on_folder_finish_loading, desktop);
4786     g_signal_handlers_disconnect_by_func(folder, on_folder_error, desktop);
4787     g_signal_handlers_disconnect_by_func(app_config, on_big_icon_size_changed, desktop->model);
4788     g_signal_handlers_disconnect_by_func(desktop->model, on_row_deleting, desktop);
4789     g_signal_handlers_disconnect_by_func(desktop->model, on_row_inserted, desktop);
4790     g_signal_handlers_disconnect_by_func(desktop->model, on_row_deleted, desktop);
4791     g_signal_handlers_disconnect_by_func(desktop->model, on_row_changed, desktop);
4792     g_signal_handlers_disconnect_by_func(desktop->model, on_rows_reordered, desktop);
4793 #if FM_CHECK_VERSION(1, 0, 2)
4794     g_signal_handlers_disconnect_by_func(desktop->model, on_sort_changed, desktop);
4795 #endif
4796     g_object_unref(desktop->model);
4797     desktop->model = NULL;
4798     fm_desktop_accessible_model_removed(desktop);
4799     /* update popup now */
4800     fm_folder_view_add_popup(FM_FOLDER_VIEW(desktop), GTK_WINDOW(desktop),
4801                              fm_desktop_update_popup);
4802 }
4803 
4804 #if FM_CHECK_VERSION(1, 2, 0)
on_show_full_names_changed(FmConfig * cfg,FmDesktop * self)4805 static void on_show_full_names_changed(FmConfig *cfg, FmDesktop *self)
4806 {
4807     if (fm_config->show_full_names)
4808     {
4809         self->pango_text_h = -1;
4810         pango_layout_set_ellipsize(self->pl, PANGO_ELLIPSIZE_NONE);
4811     }
4812     else
4813     {
4814         self->pango_text_h = self->text_h * PANGO_SCALE;
4815         pango_layout_set_ellipsize(self->pl, PANGO_ELLIPSIZE_END);
4816     }
4817     queue_layout_items(self);
4818 }
4819 #endif
4820 
4821 #if GTK_CHECK_VERSION(3, 0, 0)
fm_desktop_destroy(GtkWidget * object)4822 static void fm_desktop_destroy(GtkWidget *object)
4823 #else
4824 static void fm_desktop_destroy(GtkObject *object)
4825 #endif
4826 {
4827     FmDesktop *self;
4828     GdkScreen* screen;
4829 
4830     self = FM_DESKTOP(object);
4831     if(self->icon_render) /* see bug #3533958 by korzhpavel@SF */
4832     {
4833         screen = gtk_widget_get_screen((GtkWidget*)self);
4834         gdk_window_remove_filter(gdk_screen_get_root_window(screen), on_root_event, self);
4835 
4836         g_signal_handlers_disconnect_by_func(screen, on_screen_size_changed, self);
4837 #if FM_CHECK_VERSION(1, 2, 0)
4838         g_signal_handlers_disconnect_by_func(app_config, on_show_full_names_changed, self);
4839 #endif
4840 
4841         gtk_window_group_remove_window(win_group, (GtkWindow*)self);
4842 
4843         if (self->model)
4844             disconnect_model(self);
4845 
4846         unload_items(self);
4847 
4848         g_object_unref(self->icon_render);
4849         self->icon_render = NULL;
4850         g_object_unref(self->pl);
4851 
4852         if(self->single_click_timeout_handler)
4853             g_source_remove(self->single_click_timeout_handler);
4854 
4855         if(self->idle_layout)
4856             g_source_remove(self->idle_layout);
4857 
4858         g_signal_handlers_disconnect_by_func(self->dnd_src, on_dnd_src_data_get, self);
4859         g_object_unref(self->dnd_src);
4860         g_object_unref(self->dnd_dest);
4861     }
4862 
4863     if (self->conf.configured)
4864     {
4865         self->conf.configured = FALSE;
4866         if (self->conf.changed) /* if config was changed then save it now */
4867             save_item_pos(self);
4868         g_free(self->conf.wallpaper);
4869         if (self->conf.wallpapers_configured > 0)
4870         {
4871             int i;
4872             for (i = 0; i < self->conf.wallpapers_configured; i++)
4873                 g_free(self->conf.wallpapers[i]);
4874             g_free(self->conf.wallpapers);
4875         }
4876         g_free(self->conf.desktop_font);
4877         g_free(self->conf.folder);
4878     }
4879 
4880     _clear_bg_cache(self);
4881 
4882     /* cancel any pending search timeout */
4883     if (G_UNLIKELY(self->search_timeout_id))
4884     {
4885         g_source_remove(self->search_timeout_id);
4886         self->search_timeout_id = 0;
4887     }
4888 
4889     /* destroy the interactive search dialog */
4890     if (G_UNLIKELY(self->search_window))
4891     {
4892         g_signal_handlers_disconnect_by_func(self->search_window, desktop_search_delete_event, self);
4893         g_signal_handlers_disconnect_by_func(self->search_window, desktop_search_scroll_event, self);
4894         g_signal_handlers_disconnect_by_func(self->search_window, desktop_search_key_press_event, self);
4895         gtk_widget_destroy(self->search_window);
4896         self->search_entry = NULL;
4897         self->search_window = NULL;
4898     }
4899 
4900 #if GTK_CHECK_VERSION(3, 0, 0)
4901     GTK_WIDGET_CLASS(fm_desktop_parent_class)->destroy(object);
4902 #else
4903     GTK_OBJECT_CLASS(fm_desktop_parent_class)->destroy(object);
4904 #endif
4905 }
4906 
fm_desktop_init(FmDesktop * self)4907 static void fm_desktop_init(FmDesktop *self)
4908 {
4909 #if GTK_CHECK_VERSION(3, 0, 0)
4910     self->css = gtk_css_provider_new();
4911     gtk_style_context_add_provider(gtk_widget_get_style_context((GtkWidget*)self),
4912                                    GTK_STYLE_PROVIDER(self->css),
4913                                    GTK_STYLE_PROVIDER_PRIORITY_USER);
4914 #else
4915     GtkStyle *style = g_object_new(FM_DESKTOP_TYPE_STYLE, NULL);
4916     gtk_widget_set_style((GtkWidget*)self, style);
4917     g_object_unref(style);
4918 #endif
4919 }
4920 
4921 /* we should have a constructor to handle parameters */
fm_desktop_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)4922 static GObject* fm_desktop_constructor(GType type, guint n_construct_properties,
4923                                        GObjectConstructParam *construct_properties)
4924 {
4925     GObject* object = G_OBJECT_CLASS(fm_desktop_parent_class)->constructor(type, n_construct_properties, construct_properties);
4926     FmDesktop* self = (FmDesktop*)object;
4927     GdkScreen* screen = gtk_widget_get_screen((GtkWidget*)self);
4928     GdkWindow* root;
4929     guint i;
4930     gint n;
4931     GdkRectangle geom;
4932 
4933     for(i = 0; i < n_construct_properties; i++)
4934         if(!strcmp(construct_properties[i].pspec->name, "monitor")
4935            && G_VALUE_HOLDS_INT(construct_properties[i].value))
4936             self->monitor = g_value_get_int(construct_properties[i].value);
4937     if(self->monitor < 0)
4938         return object; /* this monitor is disabled */
4939     g_debug("fm_desktop_constructor for monitor %d", self->monitor);
4940     gdk_screen_get_monitor_geometry(screen, self->monitor, &geom);
4941     gtk_window_set_default_size((GtkWindow*)self, geom.width, geom.height);
4942     gtk_window_move(GTK_WINDOW(self), geom.x, geom.y);
4943     gtk_widget_set_app_paintable((GtkWidget*)self, TRUE);
4944     gtk_window_set_type_hint(GTK_WINDOW(self), GDK_WINDOW_TYPE_HINT_DESKTOP);
4945     gtk_widget_add_events((GtkWidget*)self,
4946                         GDK_POINTER_MOTION_MASK |
4947                         GDK_BUTTON_PRESS_MASK |
4948                         GDK_BUTTON_RELEASE_MASK |
4949                         GDK_KEY_PRESS_MASK|
4950                         GDK_PROPERTY_CHANGE_MASK);
4951 
4952     self->icon_render = fm_cell_renderer_pixbuf_new();
4953     g_object_set(self->icon_render, "follow-state", TRUE, NULL);
4954     g_object_ref_sink(self->icon_render);
4955     fm_cell_renderer_pixbuf_set_fixed_size(FM_CELL_RENDERER_PIXBUF(self->icon_render), fm_config->big_icon_size, fm_config->big_icon_size);
4956 
4957     /* FIXME: call pango_layout_context_changed() on the layout in response to the
4958      * "style-set" and "direction-changed" signals for the widget. */
4959     //pc = gtk_widget_get_pango_context((GtkWidget*)self);
4960     self->pl = gtk_widget_create_pango_layout((GtkWidget*)self, NULL);
4961     pango_layout_set_alignment(self->pl, PANGO_ALIGN_CENTER);
4962 #if FM_CHECK_VERSION(1, 2, 0)
4963     if (fm_config->show_full_names)
4964         pango_layout_set_ellipsize(self->pl, PANGO_ELLIPSIZE_NONE);
4965     else
4966 #endif
4967         pango_layout_set_ellipsize(self->pl, PANGO_ELLIPSIZE_END);
4968     pango_layout_set_wrap(self->pl, PANGO_WRAP_WORD_CHAR);
4969 #if FM_CHECK_VERSION(1, 2, 0)
4970     g_signal_connect(app_config, "changed::show_full_names",
4971                      G_CALLBACK(on_show_full_names_changed), self);
4972 #endif
4973 
4974     root = gdk_screen_get_root_window(screen);
4975     gdk_window_set_events(root, gdk_window_get_events(root)|GDK_PROPERTY_CHANGE_MASK);
4976     gdk_window_add_filter(root, on_root_event, self);
4977     g_signal_connect(screen, "monitors-changed", G_CALLBACK(on_screen_size_changed), self);
4978 
4979     n = get_desktop_for_root_window(root);
4980     if(n < 0)
4981         n = 0;
4982     self->cur_desktop = (guint)n;
4983 
4984     /* init dnd support */
4985     self->dnd_src = fm_dnd_src_new((GtkWidget*)self);
4986 #if !FM_CHECK_VERSION(1, 2, 1)
4987     /* override pre-1.2.1 handler from FmDndSrc to allow icon change */
4988     g_signal_connect_after(self, "drag-begin", G_CALLBACK(on_drag_begin), NULL);
4989 #endif
4990     /* add our own targets */
4991     fm_dnd_src_add_targets((GtkWidget*)self, dnd_targets, G_N_ELEMENTS(dnd_targets));
4992     g_signal_connect(self->dnd_src, "data-get", G_CALLBACK(on_dnd_src_data_get), self);
4993 
4994     self->dnd_dest = fm_dnd_dest_new_with_handlers((GtkWidget*)self);
4995     fm_dnd_dest_add_targets((GtkWidget*)self, dnd_targets, G_N_ELEMENTS(dnd_targets));
4996 
4997     gtk_window_group_add_window(win_group, GTK_WINDOW(self));
4998 
4999     return object;
5000 }
5001 
fm_desktop_new(GdkScreen * screen,gint monitor)5002 FmDesktop *fm_desktop_new(GdkScreen* screen, gint monitor)
5003 {
5004     return g_object_new(FM_TYPE_DESKTOP, "screen", screen, "monitor", monitor, NULL);
5005 }
5006 
fm_desktop_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)5007 static void fm_desktop_set_property(GObject *object, guint property_id,
5008                                     const GValue *value, GParamSpec *pspec)
5009 {
5010     switch(property_id)
5011     {
5012         case PROP_MONITOR:
5013             FM_DESKTOP(object)->monitor = g_value_get_int(value);
5014             break;
5015         default:
5016             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
5017     }
5018 }
5019 
fm_desktop_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)5020 static void fm_desktop_get_property(GObject *object, guint property_id,
5021                                     GValue *value, GParamSpec *pspec)
5022 {
5023     switch(property_id)
5024     {
5025         case PROP_MONITOR:
5026             g_value_set_int(value, FM_DESKTOP(object)->monitor);
5027             break;
5028         default:
5029             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
5030     }
5031 }
5032 
5033 /* init for FmDesktop class */
fm_desktop_class_init(FmDesktopClass * klass)5034 static void fm_desktop_class_init(FmDesktopClass *klass)
5035 {
5036     GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
5037     typedef gboolean (*DeleteEvtHandler) (GtkWidget*, GdkEventAny*);
5038     char* atom_names[] = {"_NET_WORKAREA", "_NET_NUMBER_OF_DESKTOPS",
5039                           "_NET_CURRENT_DESKTOP", "_XROOTMAP_ID", "_XROOTPMAP_ID"};
5040     Atom atoms[G_N_ELEMENTS(atom_names)] = {0};
5041     GObjectClass* object_class = G_OBJECT_CLASS(klass);
5042 
5043 #if GTK_CHECK_VERSION(3, 0, 0)
5044     widget_class->destroy = fm_desktop_destroy;
5045     widget_class->draw = on_draw;
5046     widget_class->get_preferred_width = on_get_preferred_width;
5047     widget_class->get_preferred_height = on_get_preferred_height;
5048 #else
5049     GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS(klass);
5050     gtk_object_class->destroy = fm_desktop_destroy;
5051 
5052     widget_class->expose_event = on_expose;
5053     widget_class->size_request = on_size_request;
5054 #endif
5055     widget_class->size_allocate = on_size_allocate;
5056     widget_class->button_press_event = on_button_press;
5057     widget_class->button_release_event = on_button_release;
5058     widget_class->motion_notify_event = on_motion_notify;
5059     widget_class->leave_notify_event = on_leave_notify;
5060     widget_class->key_press_event = on_key_press;
5061     /* widget_class->style_set = on_style_set; */
5062     widget_class->direction_changed = on_direction_changed;
5063     widget_class->realize = on_realize;
5064     widget_class->focus_in_event = on_focus_in;
5065     widget_class->focus_out_event = on_focus_out;
5066     /* widget_class->scroll_event = on_scroll; */
5067     widget_class->delete_event = (DeleteEvtHandler)gtk_true;
5068     widget_class->get_accessible = fm_desktop_get_accessible;
5069 
5070     widget_class->drag_motion = on_drag_motion;
5071     widget_class->drag_drop = on_drag_drop;
5072     widget_class->drag_data_received = on_drag_data_received;
5073     widget_class->drag_leave = on_drag_leave;
5074 #if FM_CHECK_VERSION(1, 2, 1)
5075     widget_class->drag_begin = on_drag_begin;
5076 #endif
5077     /* widget_class->drag_data_get = on_drag_data_get; */
5078 
5079     if(XInternAtoms(gdk_x11_get_default_xdisplay(), atom_names,
5080                     G_N_ELEMENTS(atom_names), False, atoms))
5081     {
5082         XA_NET_WORKAREA = atoms[0];
5083         XA_NET_NUMBER_OF_DESKTOPS = atoms[1];
5084         XA_NET_CURRENT_DESKTOP = atoms[2];
5085         XA_XROOTMAP_ID = atoms[3];
5086         XA_XROOTPMAP_ID = atoms[4];
5087     }
5088 
5089     object_class->constructor = fm_desktop_constructor;
5090     object_class->set_property = fm_desktop_set_property;
5091     object_class->get_property = fm_desktop_get_property;
5092 
5093     g_object_class_install_property(object_class, PROP_MONITOR,
5094         g_param_spec_int("monitor", "Monitor",
5095                          "Monitor number where desktop is",
5096                          -2, 127, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
5097 
5098     desktop_atom = gdk_atom_intern_static_string(dnd_targets[0].target);
5099 }
5100 
5101 
5102 //static void on_clicked(FmFolderView* fv, FmFolderViewClickType type, FmFileInfo* file)
5103 
5104 //static void on_sel_changed(FmFolderView* fv, FmFileInfoList* sels)
5105 
5106 //static void on_sort_changed(FmFolderView* fv)
5107 
5108 /* ---------------------------------------------------------------------
5109     FmFolderView interface implementation */
5110 
_set_sel_mode(FmFolderView * fv,GtkSelectionMode mode)5111 static void _set_sel_mode(FmFolderView* fv, GtkSelectionMode mode)
5112 {
5113     /* not implemented */
5114 }
5115 
_get_sel_mode(FmFolderView * fv)5116 static GtkSelectionMode _get_sel_mode(FmFolderView* fv)
5117 {
5118     return GTK_SELECTION_MULTIPLE;
5119 }
5120 
5121 #if !FM_CHECK_VERSION(1, 0, 2)
_set_sort(FmFolderView * fv,GtkSortType type,FmFolderModelViewCol by)5122 static void _set_sort(FmFolderView* fv, GtkSortType type, FmFolderModelViewCol by)
5123 {
5124     FmDesktop* desktop = FM_DESKTOP(fv);
5125 
5126     if(type == (GtkSortType)desktop->conf.desktop_sort_type &&
5127        by == (FmFolderModelViewCol)desktop->conf.desktop_sort_by)
5128         return;
5129     desktop->conf.desktop_sort_type = type;
5130     desktop->conf.desktop_sort_by = by;
5131     pcmanfm_save_config(FALSE);
5132     if (desktop->model)
5133         gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(desktop->model),
5134                                              by, type);
5135 }
5136 
_get_sort(FmFolderView * fv,GtkSortType * type,FmFolderModelViewCol * by)5137 static void _get_sort(FmFolderView* fv, GtkSortType* type, FmFolderModelViewCol* by)
5138 {
5139     FmDesktop* desktop = FM_DESKTOP(fv);
5140 
5141     if(type)
5142         *type = desktop->conf.desktop_sort_type;
5143     if(by)
5144         *by = desktop->conf.desktop_sort_by;
5145 }
5146 #endif
5147 
_set_show_hidden(FmFolderView * fv,gboolean show)5148 static void _set_show_hidden(FmFolderView* fv, gboolean show)
5149 {
5150     /* not implemented */
5151 }
5152 
_get_show_hidden(FmFolderView * fv)5153 static gboolean _get_show_hidden(FmFolderView* fv)
5154 {
5155     return FALSE;
5156 }
5157 
_get_folder(FmFolderView * fv)5158 static FmFolder* _get_folder(FmFolderView* fv)
5159 {
5160     FmDesktop* desktop = FM_DESKTOP(fv);
5161 
5162     return desktop->model ? fm_folder_model_get_folder(desktop->model) : NULL;
5163 }
5164 
_set_model(FmFolderView * fv,FmFolderModel * model)5165 static void _set_model(FmFolderView* fv, FmFolderModel* model)
5166 {
5167     /* not implemented */
5168 }
5169 
_get_model(FmFolderView * fv)5170 static FmFolderModel* _get_model(FmFolderView* fv)
5171 {
5172     return FM_DESKTOP(fv)->model;
5173 }
5174 
_count_selected_files(FmFolderView * fv)5175 static gint _count_selected_files(FmFolderView* fv)
5176 {
5177     FmDesktop* desktop = FM_DESKTOP(fv);
5178     GtkTreeModel* model;
5179     GtkTreeIter it;
5180     gint n = 0;
5181 
5182     if (!desktop->model)
5183         return 0;
5184     model = GTK_TREE_MODEL(desktop->model);
5185     if(!gtk_tree_model_get_iter_first(model, &it))
5186         return 0;
5187     do
5188     {
5189         FmDesktopItem* item = fm_folder_model_get_item_userdata(desktop->model, &it);
5190         if(item->is_selected)
5191             n++;
5192     }
5193     while(gtk_tree_model_iter_next(model, &it));
5194     return n;
5195 }
5196 
_dup_selected_files(FmFolderView * fv)5197 static FmFileInfoList* _dup_selected_files(FmFolderView* fv)
5198 {
5199     FmDesktop* desktop = FM_DESKTOP(fv);
5200     FmFileInfoList* files = NULL;
5201     GtkTreeModel* model;
5202     GtkTreeIter it;
5203 
5204     if (!desktop->model)
5205         return NULL;
5206     model = GTK_TREE_MODEL(desktop->model);
5207     if(!gtk_tree_model_get_iter_first(model, &it))
5208         return NULL;
5209     do
5210     {
5211         FmDesktopItem* item = fm_folder_model_get_item_userdata(desktop->model, &it);
5212         if(item->is_selected)
5213         {
5214             if(!files)
5215                 files = fm_file_info_list_new();
5216             fm_file_info_list_push_tail(files, item->fi);
5217         }
5218     }
5219     while(gtk_tree_model_iter_next(model, &it));
5220     return files;
5221 }
5222 
_dup_selected_file_paths(FmFolderView * fv)5223 static FmPathList* _dup_selected_file_paths(FmFolderView* fv)
5224 {
5225     FmDesktop* desktop = FM_DESKTOP(fv);
5226     FmPathList* files = NULL;
5227     GtkTreeModel* model;
5228     GtkTreeIter it;
5229 
5230     if (!desktop->model)
5231         return NULL;
5232     model = GTK_TREE_MODEL(desktop->model);
5233     if(!gtk_tree_model_get_iter_first(model, &it))
5234         return NULL;
5235     do
5236     {
5237         FmDesktopItem* item = fm_folder_model_get_item_userdata(desktop->model, &it);
5238         if(item->is_selected)
5239         {
5240             if(!files)
5241                 files = fm_path_list_new();
5242             fm_path_list_push_tail(files, fm_file_info_get_path(item->fi));
5243         }
5244     }
5245     while(gtk_tree_model_iter_next(model, &it));
5246     return files;
5247 }
5248 
_select_all(FmFolderView * fv)5249 static void _select_all(FmFolderView* fv)
5250 {
5251     FmDesktop* desktop = FM_DESKTOP(fv);
5252     GtkTreeIter it;
5253     GtkTreeModel* model;
5254 
5255     if (!desktop->model)
5256         return;
5257     model = GTK_TREE_MODEL(desktop->model);
5258     if(!gtk_tree_model_get_iter_first(model, &it))
5259         return;
5260     do
5261     {
5262         FmDesktopItem* item = fm_folder_model_get_item_userdata(desktop->model, &it);
5263         if(!item->is_selected)
5264         {
5265             item->is_selected = TRUE;
5266             redraw_item(desktop, item);
5267             fm_desktop_item_selected_changed(desktop, item);
5268         }
5269     }
5270     while(gtk_tree_model_iter_next(model, &it));
5271 }
5272 
_unselect_all(FmFolderView * fv)5273 static void _unselect_all(FmFolderView* fv)
5274 {
5275     FmDesktop* desktop = FM_DESKTOP(fv);
5276     GtkTreeIter it;
5277     GtkTreeModel* model;
5278 
5279     if (!desktop->model)
5280         return;
5281     model = GTK_TREE_MODEL(desktop->model);
5282     if(!gtk_tree_model_get_iter_first(model, &it))
5283         return;
5284     do
5285     {
5286         FmDesktopItem* item = fm_folder_model_get_item_userdata(desktop->model, &it);
5287         if(item->is_selected)
5288         {
5289             item->is_selected = FALSE;
5290             redraw_item(desktop, item);
5291             fm_desktop_item_selected_changed(desktop, item);
5292         }
5293     }
5294     while(gtk_tree_model_iter_next(model, &it));
5295 }
5296 
_select_invert(FmFolderView * fv)5297 static void _select_invert(FmFolderView* fv)
5298 {
5299     /* not implemented */
5300 }
5301 
_select_file_path(FmFolderView * fv,FmPath * path)5302 static void _select_file_path(FmFolderView* fv, FmPath* path)
5303 {
5304     /* not implemented */
5305 }
5306 
_get_custom_menu_callbacks(FmFolderView * fv,FmFolderViewUpdatePopup * popup,FmLaunchFolderFunc * launch)5307 static void _get_custom_menu_callbacks(FmFolderView* fv,
5308                                        FmFolderViewUpdatePopup* popup,
5309                                        FmLaunchFolderFunc* launch)
5310 {
5311     if(popup)
5312         *popup = fm_desktop_update_item_popup;
5313     if(launch)
5314         *launch = pcmanfm_open_folder;
5315 }
5316 
5317 /* init for FmFolderView interface implementation */
fm_desktop_view_init(FmFolderViewInterface * iface)5318 static void fm_desktop_view_init(FmFolderViewInterface* iface)
5319 {
5320     iface->set_sel_mode = _set_sel_mode;
5321     iface->get_sel_mode = _get_sel_mode;
5322 #if !FM_CHECK_VERSION(1, 0, 2)
5323     iface->set_sort = _set_sort;
5324     iface->get_sort = _get_sort;
5325 #endif
5326     iface->set_show_hidden = _set_show_hidden;
5327     iface->get_show_hidden = _get_show_hidden;
5328     iface->get_folder = _get_folder;
5329     iface->set_model = _set_model;
5330     iface->get_model = _get_model;
5331     iface->count_selected_files = _count_selected_files;
5332     iface->dup_selected_files = _dup_selected_files;
5333     iface->dup_selected_file_paths = _dup_selected_file_paths;
5334     iface->select_all = _select_all;
5335     //iface->unselect_all = _unselect_all;
5336     iface->select_invert = _select_invert;
5337     iface->select_file_path = _select_file_path;
5338     iface->get_custom_menu_callbacks = _get_custom_menu_callbacks;
5339 }
5340 
5341 
5342 /* ---------------------------------------------------------------------
5343     Desktop preferences */
5344 
5345 static GtkWindow* desktop_pref_dlg = NULL;
5346 
on_response(GtkDialog * dlg,int res,GtkWindow ** pdlg)5347 static void on_response(GtkDialog* dlg, int res, GtkWindow** pdlg)
5348 {
5349     *pdlg = NULL;
5350     gtk_widget_destroy(GTK_WIDGET(dlg));
5351 }
5352 
5353 /* preferences setup is tightly linked with desktop so should be here */
on_wallpaper_set(GtkFileChooserButton * btn,FmDesktop * desktop)5354 static void on_wallpaper_set(GtkFileChooserButton* btn, FmDesktop *desktop)
5355 {
5356     char* file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(btn));
5357     g_free(desktop->conf.wallpaper);
5358     desktop->conf.wallpaper = file;
5359     queue_config_save(desktop);
5360     update_background(desktop, 0);
5361 }
5362 
on_update_img_preview(GtkFileChooser * chooser,GtkImage * img)5363 static void on_update_img_preview( GtkFileChooser *chooser, GtkImage* img )
5364 {
5365     char* file = gtk_file_chooser_get_preview_filename( chooser );
5366     GdkPixbuf* pix = NULL;
5367     if( file )
5368     {
5369         pix = gdk_pixbuf_new_from_file_at_scale( file, 128, 128, TRUE, NULL );
5370         g_free( file );
5371     }
5372     if( pix )
5373     {
5374         gtk_file_chooser_set_preview_widget_active(chooser, TRUE);
5375         gtk_image_set_from_pixbuf( img, pix );
5376         g_object_unref( pix );
5377     }
5378     else
5379     {
5380         gtk_image_clear( img );
5381         gtk_file_chooser_set_preview_widget_active(chooser, FALSE);
5382     }
5383 }
5384 
on_wallpaper_mode_changed(GtkComboBox * combo,FmDesktop * desktop)5385 static void on_wallpaper_mode_changed(GtkComboBox* combo, FmDesktop *desktop)
5386 {
5387     int sel = gtk_combo_box_get_active(combo);
5388 
5389     if(sel >= 0 && sel != (int)desktop->conf.wallpaper_mode)
5390     {
5391         desktop->conf.wallpaper_mode = sel;
5392         queue_config_save(desktop);
5393         update_background(desktop, 0);
5394     }
5395 }
5396 
on_wallpaper_mode_changed2(GtkComboBox * combo,GtkWidget * wallpaper_box)5397 static void on_wallpaper_mode_changed2(GtkComboBox *combo, GtkWidget *wallpaper_box)
5398 {
5399     /* update the box */
5400     if (wallpaper_box)
5401         gtk_widget_set_sensitive(wallpaper_box,
5402                                  gtk_combo_box_get_active(combo) != FM_WP_COLOR);
5403 }
5404 
on_bg_color_set(GtkColorButton * btn,FmDesktop * desktop)5405 static void on_bg_color_set(GtkColorButton *btn, FmDesktop *desktop)
5406 {
5407     GdkColor new_val;
5408 
5409     gtk_color_button_get_color(btn, &new_val);
5410     if (!gdk_color_equal(&desktop->conf.desktop_bg, &new_val))
5411     {
5412 #if GTK_CHECK_VERSION(3, 0, 0)
5413         char *css_data = g_strdup_printf("FmDesktop {\n"
5414                                              "background-color: #%02x%02x%02x\n"
5415                                          "}", new_val.red/256, new_val.green/256,
5416                                          new_val.blue/256);
5417         gtk_css_provider_load_from_data(desktop->css, css_data, -1, NULL);
5418         g_free(css_data);
5419 #endif
5420         desktop->conf.desktop_bg = new_val;
5421         queue_config_save(desktop);
5422         update_background(desktop, 0);
5423     }
5424 }
5425 
on_wallpaper_common_toggled(GtkToggleButton * btn,FmDesktop * desktop)5426 static void on_wallpaper_common_toggled(GtkToggleButton* btn, FmDesktop *desktop)
5427 {
5428     gboolean new_val = gtk_toggle_button_get_active(btn);
5429 
5430     if(desktop->conf.wallpaper_common != new_val)
5431     {
5432         desktop->conf.wallpaper_common = new_val;
5433         queue_config_save(desktop);
5434         update_background(desktop, 0);
5435     }
5436 }
5437 
on_fg_color_set(GtkColorButton * btn,FmDesktop * desktop)5438 static void on_fg_color_set(GtkColorButton *btn, FmDesktop *desktop)
5439 {
5440     GdkColor new_val;
5441 
5442     gtk_color_button_get_color(btn, &new_val);
5443     if (!gdk_color_equal(&desktop->conf.desktop_fg, &new_val))
5444     {
5445         desktop->conf.desktop_fg = new_val;
5446         queue_config_save(desktop);
5447         gtk_widget_queue_draw(GTK_WIDGET(desktop));
5448     }
5449 }
5450 
on_shadow_color_set(GtkColorButton * btn,FmDesktop * desktop)5451 static void on_shadow_color_set(GtkColorButton *btn, FmDesktop *desktop)
5452 {
5453     GdkColor new_val;
5454 
5455     gtk_color_button_get_color(btn, &new_val);
5456     if (!gdk_color_equal(&desktop->conf.desktop_shadow, &new_val))
5457     {
5458         desktop->conf.desktop_shadow = new_val;
5459         queue_config_save(desktop);
5460         gtk_widget_queue_draw(GTK_WIDGET(desktop));
5461     }
5462 }
5463 
on_wm_menu_toggled(GtkToggleButton * btn,FmDesktop * desktop)5464 static void on_wm_menu_toggled(GtkToggleButton* btn, FmDesktop *desktop)
5465 {
5466     gboolean new_val = gtk_toggle_button_get_active(btn);
5467 
5468     if(desktop->conf.show_wm_menu != new_val)
5469     {
5470         desktop->conf.show_wm_menu = new_val;
5471         queue_config_save(desktop);
5472     }
5473 }
5474 
on_desktop_font_set(GtkFontButton * btn,FmDesktop * desktop)5475 static void on_desktop_font_set(GtkFontButton* btn, FmDesktop *desktop)
5476 {
5477     const char* font = gtk_font_button_get_font_name(btn);
5478 
5479     if(font)
5480     {
5481         PangoFontDescription *font_desc;
5482 
5483         g_free(desktop->conf.desktop_font);
5484         desktop->conf.desktop_font = g_strdup(font);
5485         queue_config_save(desktop);
5486         if(desktop->monitor < 0)
5487             return;
5488         font_desc = pango_font_description_from_string(desktop->conf.desktop_font);
5489         if(font_desc)
5490         {
5491             PangoContext* pc = gtk_widget_get_pango_context((GtkWidget*)desktop);
5492 
5493             pango_context_set_font_description(pc, font_desc);
5494             pango_layout_context_changed(desktop->pl);
5495             gtk_widget_queue_resize(GTK_WIDGET(desktop));
5496             pango_font_description_free(font_desc);
5497         }
5498     }
5499 }
5500 
on_desktop_folder_new_win_toggled(GtkToggleButton * btn,FmDesktop * desktop)5501 static void on_desktop_folder_new_win_toggled(GtkToggleButton* btn, FmDesktop *desktop)
5502 {
5503     app_config->desktop_folder_new_win = gtk_toggle_button_get_active(btn);
5504     pcmanfm_save_config(FALSE);
5505 }
5506 
5507 #if FM_CHECK_VERSION(1, 2, 0)
on_show_documents_toggled(GtkToggleButton * btn,FmDesktop * desktop)5508 static void on_show_documents_toggled(GtkToggleButton* btn, FmDesktop *desktop)
5509 {
5510     gboolean new_val = gtk_toggle_button_get_active(btn);
5511 
5512     if(desktop->conf.show_documents != new_val)
5513     {
5514         desktop->conf.show_documents = new_val;
5515         queue_config_save(desktop);
5516         if (documents && documents->fi && desktop->model)
5517         {
5518             if (new_val)
5519                 fm_folder_model_extra_file_add(desktop->model, documents->fi,
5520                                                FM_FOLDER_MODEL_ITEMPOS_PRE);
5521             else
5522                 fm_folder_model_extra_file_remove(desktop->model, documents->fi);
5523         }
5524     }
5525 }
5526 
on_show_trash_toggled(GtkToggleButton * btn,FmDesktop * desktop)5527 static void on_show_trash_toggled(GtkToggleButton* btn, FmDesktop *desktop)
5528 {
5529     gboolean new_val = gtk_toggle_button_get_active(btn);
5530 
5531     if(desktop->conf.show_trash != new_val)
5532     {
5533         desktop->conf.show_trash = new_val;
5534         queue_config_save(desktop);
5535         if (trash_can && trash_can->fi && desktop->model)
5536         {
5537             if (new_val)
5538                 fm_folder_model_extra_file_add(desktop->model, trash_can->fi,
5539                                                FM_FOLDER_MODEL_ITEMPOS_PRE);
5540             else
5541                 fm_folder_model_extra_file_remove(desktop->model, trash_can->fi);
5542         }
5543     }
5544 }
5545 
on_show_mounts_toggled(GtkToggleButton * btn,FmDesktop * desktop)5546 static void on_show_mounts_toggled(GtkToggleButton* btn, FmDesktop *desktop)
5547 {
5548     GSList *msl;
5549     gboolean new_val = gtk_toggle_button_get_active(btn);
5550 
5551     if(desktop->conf.show_mounts != new_val)
5552     {
5553         desktop->conf.show_mounts = new_val;
5554         queue_config_save(desktop);
5555         if (desktop->model) for (msl = mounts; msl; msl = msl->next)
5556         {
5557             FmDesktopExtraItem *mount = msl->data;
5558             if (new_val)
5559                 fm_folder_model_extra_file_add(desktop->model, mount->fi,
5560                                                FM_FOLDER_MODEL_ITEMPOS_POST);
5561             else
5562                 fm_folder_model_extra_file_remove(desktop->model, mount->fi);
5563         }
5564     }
5565 }
5566 #endif
5567 
on_desktop_folder_set_toggled(GtkToggleButton * btn,GtkFileChooserButton * chooser)5568 static void on_desktop_folder_set_toggled(GtkToggleButton *btn, GtkFileChooserButton *chooser)
5569 {
5570     gboolean active = gtk_toggle_button_get_active(btn);
5571 
5572     gtk_widget_set_sensitive(GTK_WIDGET(chooser), active);
5573     if (active)
5574         g_signal_emit_by_name(chooser, "file-set");
5575     else
5576     {
5577         char *path = fm_path_to_str(fm_path_get_desktop());
5578         gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(chooser), path);
5579         g_free(path);
5580     }
5581 }
5582 
5583 typedef struct
5584 {
5585     FmDesktop *desktop;
5586     GtkWidget *desktop_folder_box;
5587 #if FM_CHECK_VERSION(1, 2, 0)
5588     GtkWidget *icons_page;
5589 #endif
5590     gpointer chooser;
5591     guint timeout_handler;
5592 } FolderChooserData;
5593 
_chooser_timeout(gpointer user_data)5594 static gboolean _chooser_timeout(gpointer user_data)
5595 {
5596     FolderChooserData *data;
5597     GFile *gf;
5598     FmFolder *folder;
5599 
5600     if(g_source_is_destroyed(g_main_current_source()))
5601         return FALSE;
5602     data = user_data;
5603     data->timeout_handler = 0;
5604     if (!gtk_widget_get_sensitive(data->desktop_folder_box))
5605         return FALSE;
5606     gf = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(data->chooser));
5607     if (gf == NULL) /* no file chosen */
5608         return FALSE;
5609     folder = fm_folder_from_gfile(gf);
5610     if (data->desktop->model == NULL ||
5611         fm_folder_model_get_folder(data->desktop->model) != folder)
5612     {
5613         disconnect_model(data->desktop);
5614         if (folder)
5615             connect_model(data->desktop, folder);
5616         else
5617             queue_layout_items(data->desktop);
5618     }
5619     g_free(data->desktop->conf.folder);
5620     if (G_UNLIKELY(data->desktop->model == NULL))
5621         data->desktop->conf.folder = g_strdup("");
5622     else if (!fm_path_equal(fm_folder_get_path(folder), fm_path_get_desktop()))
5623         data->desktop->conf.folder = g_file_get_path(gf);
5624     else
5625         data->desktop->conf.folder = NULL;
5626     queue_config_save(data->desktop);
5627     if (folder)
5628         g_object_unref(folder);
5629     g_object_unref(gf);
5630     return FALSE;
5631 }
5632 
on_desktop_folder_set(GtkFileChooserButton * btn,FolderChooserData * data)5633 static void on_desktop_folder_set(GtkFileChooserButton *btn, FolderChooserData *data)
5634 {
5635     if (data->timeout_handler == 0)
5636         data->timeout_handler = gdk_threads_add_timeout(100, _chooser_timeout, data);
5637 }
5638 
_data_destroy(gpointer data,GObject * dlg)5639 static void _data_destroy(gpointer data, GObject *dlg)
5640 {
5641     FolderChooserData *d = data;
5642 
5643     if (d->timeout_handler)
5644         g_source_remove(d->timeout_handler);
5645     g_free(data);
5646 }
5647 
on_use_desktop_folder_toggled(GtkToggleButton * btn,FolderChooserData * data)5648 static void on_use_desktop_folder_toggled(GtkToggleButton *btn, FolderChooserData *data)
5649 {
5650     gboolean active = gtk_toggle_button_get_active(btn);
5651 
5652     gtk_widget_set_sensitive(data->desktop_folder_box, active);
5653 #if FM_CHECK_VERSION(1, 2, 0)
5654     gtk_widget_set_visible(data->icons_page, active);
5655 #endif
5656     if (active)
5657         g_signal_emit_by_name(data->chooser, "file-set");
5658     else
5659     {
5660         disconnect_model(data->desktop);
5661         queue_layout_items(data->desktop);
5662         g_free(data->desktop->conf.folder);
5663         data->desktop->conf.folder = g_strdup("");
5664         queue_config_save(data->desktop);
5665         /* g_debug("*** desktop folder disabled"); */
5666     }
5667 }
5668 
fm_desktop_preference(GtkAction * act,FmDesktop * desktop)5669 void fm_desktop_preference(GtkAction *act, FmDesktop *desktop)
5670 {
5671     if (desktop == NULL)
5672         return;
5673 
5674     if(!desktop_pref_dlg)
5675     {
5676         GtkBuilder* builder;
5677         GObject *item;
5678         GtkWidget *img_preview;
5679         FolderChooserData *data;
5680         char *path_str;
5681 
5682         builder = gtk_builder_new();
5683         gtk_builder_add_from_file(builder, PACKAGE_UI_DIR "/desktop-pref.ui", NULL);
5684         desktop_pref_dlg = GTK_WINDOW(gtk_builder_get_object(builder, "dlg"));
5685         item = gtk_builder_get_object(builder, "wallpaper");
5686         g_signal_connect(item, "file-set", G_CALLBACK(on_wallpaper_set), desktop);
5687         img_preview = gtk_image_new();
5688         gtk_misc_set_alignment(GTK_MISC(img_preview), 0.5, 0.0);
5689         gtk_widget_set_size_request( img_preview, 128, 128 );
5690         gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(item), img_preview);
5691         g_signal_connect( item, "update-preview", G_CALLBACK(on_update_img_preview), img_preview );
5692         if(desktop->conf.wallpaper)
5693             gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(item), desktop->conf.wallpaper);
5694         item = gtk_builder_get_object(builder, "wallpaper_mode");
5695         gtk_combo_box_set_active(GTK_COMBO_BOX(item), desktop->conf.wallpaper_mode);
5696         g_signal_connect(item, "changed", G_CALLBACK(on_wallpaper_mode_changed), desktop);
5697         item = gtk_builder_get_object(builder, "wallpaper_box");
5698         if (item)
5699         {
5700             g_signal_connect(gtk_builder_get_object(builder, "wallpaper_mode"),
5701                              "changed", G_CALLBACK(on_wallpaper_mode_changed2),
5702                              item);
5703             gtk_widget_set_sensitive(GTK_WIDGET(item),
5704                                      desktop->conf.wallpaper_mode != FM_WP_COLOR);
5705         }
5706         item = gtk_builder_get_object(builder, "desktop_bg");
5707         gtk_color_button_set_color(GTK_COLOR_BUTTON(item), &desktop->conf.desktop_bg);
5708         g_signal_connect(item, "color-set", G_CALLBACK(on_bg_color_set), desktop);
5709         item = gtk_builder_get_object(builder, "wallpaper_common");
5710         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item), desktop->conf.wallpaper_common);
5711         g_signal_connect(item, "toggled", G_CALLBACK(on_wallpaper_common_toggled), desktop);
5712         item = gtk_builder_get_object(builder, "desktop_fg");
5713         gtk_color_button_set_color(GTK_COLOR_BUTTON(item), &desktop->conf.desktop_fg);
5714         g_signal_connect(item, "color-set", G_CALLBACK(on_fg_color_set), desktop);
5715         item = gtk_builder_get_object(builder, "desktop_shadow");
5716         gtk_color_button_set_color(GTK_COLOR_BUTTON(item), &desktop->conf.desktop_shadow);
5717         g_signal_connect(item, "color-set", G_CALLBACK(on_shadow_color_set), desktop);
5718         item = gtk_builder_get_object(builder, "show_wm_menu");
5719         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item), desktop->conf.show_wm_menu);
5720         g_signal_connect(item, "toggled", G_CALLBACK(on_wm_menu_toggled), desktop);
5721         item = gtk_builder_get_object(builder, "desktop_font");
5722         if(desktop->conf.desktop_font)
5723             gtk_font_button_set_font_name(GTK_FONT_BUTTON(item), desktop->conf.desktop_font);
5724         g_signal_connect(item, "font-set", G_CALLBACK(on_desktop_font_set), desktop);
5725 
5726         data = g_new(FolderChooserData, 1);
5727         data->desktop = desktop;
5728         data->desktop_folder_box = GTK_WIDGET(gtk_builder_get_object(builder, "desktop_folder_box"));
5729         data->chooser = gtk_builder_get_object(builder, "desktop_folder");
5730         /* GtkFileChooser is very weird and sends $HOME 4 times then chosen
5731            dir before user even see the button actually, therefore the
5732            workaround is required - we add a timeout before we do anything */
5733         data->timeout_handler = gdk_threads_add_timeout(500, _chooser_timeout, data);
5734         g_object_weak_ref(data->chooser, _data_destroy, data);
5735         /* toggle button to enable icons support */
5736         item = gtk_builder_get_object(builder, "use_desktop_folder");
5737         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item), desktop->model != NULL);
5738         gtk_widget_set_sensitive(data->desktop_folder_box, desktop->model != NULL);
5739         g_signal_connect(item, "toggled",
5740                          G_CALLBACK(on_use_desktop_folder_toggled), data);
5741         /* radio buttons for folder choose - default or custom */
5742         item = gtk_builder_get_object(builder, "desktop_folder_set");
5743         if (desktop->model == NULL
5744             || fm_path_equal(fm_folder_model_get_folder_path(desktop->model),
5745                              fm_path_get_desktop()))
5746         {
5747             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "desktop_folder_default")), TRUE);
5748             gtk_widget_set_sensitive(GTK_WIDGET(data->chooser), FALSE);
5749         }
5750         else
5751         {
5752             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item), TRUE);
5753             gtk_widget_set_sensitive(GTK_WIDGET(data->chooser), TRUE);
5754         }
5755         g_signal_connect(item, "toggled",
5756                          G_CALLBACK(on_desktop_folder_set_toggled), data->chooser);
5757         /* the desktop folder chooser dialog */
5758         if (desktop->model)
5759             path_str = fm_path_to_uri(fm_folder_model_get_folder_path(desktop->model));
5760         else
5761             path_str = fm_path_to_uri(fm_path_get_desktop());
5762         gtk_file_chooser_set_current_folder_uri(data->chooser, path_str);
5763         g_free(path_str);
5764         g_signal_connect(data->chooser, "file-set", G_CALLBACK(on_desktop_folder_set), data);
5765         g_signal_connect(data->chooser, "selection-changed", G_CALLBACK(on_desktop_folder_set), data);
5766         gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(builder, "desktop_folder_chooser")));
5767         item = gtk_builder_get_object(builder, "desktop_folder_new_win");
5768         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item), app_config->desktop_folder_new_win);
5769         g_signal_connect(item, "toggled", G_CALLBACK(on_desktop_folder_new_win_toggled), desktop);
5770 #if FM_CHECK_VERSION(1, 2, 0)
5771         item = gtk_builder_get_object(builder, "show_documents");
5772         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item), desktop->conf.show_documents);
5773         gtk_widget_set_sensitive(GTK_WIDGET(item), documents != NULL);
5774         g_signal_connect(item, "toggled", G_CALLBACK(on_show_documents_toggled), desktop);
5775         item = gtk_builder_get_object(builder, "show_trash");
5776         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item), desktop->conf.show_trash);
5777         gtk_widget_set_sensitive(GTK_WIDGET(item), trash_can != NULL);
5778         g_signal_connect(item, "toggled", G_CALLBACK(on_show_trash_toggled), desktop);
5779         item = gtk_builder_get_object(builder, "show_mounts");
5780         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(item), desktop->conf.show_mounts);
5781         g_signal_connect(item, "toggled", G_CALLBACK(on_show_mounts_toggled), desktop);
5782         data->icons_page = GTK_WIDGET(gtk_builder_get_object(builder, "icons_page"));
5783         gtk_widget_set_visible(data->icons_page, desktop->model != NULL);
5784 #endif
5785 
5786         g_signal_connect(desktop_pref_dlg, "response", G_CALLBACK(on_response), &desktop_pref_dlg);
5787         g_object_unref(builder);
5788 
5789         pcmanfm_ref();
5790         g_signal_connect(desktop_pref_dlg, "destroy", G_CALLBACK(pcmanfm_unref), NULL);
5791         /* make dialog be valid only before the desktop is destroyed */
5792         gtk_window_set_transient_for(desktop_pref_dlg, GTK_WINDOW(desktop));
5793         gtk_window_set_destroy_with_parent(desktop_pref_dlg, TRUE);
5794     }
5795     gtk_window_present(desktop_pref_dlg);
5796 }
5797 
5798 
5799 /* ---------------------------------------------------------------------
5800     Interface functions */
5801 
fm_desktop_manager_init(gint on_screen)5802 void fm_desktop_manager_init(gint on_screen)
5803 {
5804     GdkDisplay * gdpy;
5805     int i, n_scr, n_mon, scr, mon;
5806     const char* desktop_path;
5807 #if FM_CHECK_VERSION(1, 2, 0)
5808     GFile *gf;
5809 #endif
5810 
5811     if(! win_group)
5812         win_group = gtk_window_group_new();
5813 
5814     /* create the ~/Desktop folder if it doesn't exist. */
5815     desktop_path = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
5816     /* FIXME: should we use a localized folder name instead? */
5817     g_mkdir_with_parents(desktop_path, 0700); /* ensure the existance of Desktop folder. */
5818     /* FIXME: should we store the desktop folder path in the annoying ~/.config/user-dirs.dirs file? */
5819 
5820     gdpy = gdk_display_get_default();
5821     n_scr = gdk_display_get_n_screens(gdpy);
5822     n_screens = 0;
5823     for(i = 0; i < n_scr; i++)
5824         n_screens += gdk_screen_get_n_monitors(gdk_display_get_screen(gdpy, i));
5825     desktops = g_new(FmDesktop*, n_screens);
5826     for(scr = 0, i = 0; scr < n_scr; scr++)
5827     {
5828         GdkScreen* screen = gdk_display_get_screen(gdpy, scr);
5829         n_mon = gdk_screen_get_n_monitors(screen);
5830         for(mon = 0; mon < n_mon; mon++)
5831         {
5832             gint mon_init = (on_screen < 0 || on_screen == (int)scr) ? (int)mon : (mon ? -2 : -1);
5833             FmDesktop *desktop = fm_desktop_new(screen, mon_init);
5834             GtkWidget *widget = GTK_WIDGET(desktop);
5835             FmFolder *desktop_folder;
5836 
5837             desktops[i++] = desktop;
5838             if(mon_init < 0)
5839                 continue;
5840             /* realize it: without this, setting wallpaper or font won't work */
5841             gtk_widget_realize(widget);
5842             /* realizing also loads config */
5843             if (desktop->conf.folder)
5844             {
5845                 if (desktop->conf.folder[0])
5846                     desktop_folder = fm_folder_from_path_name(desktop->conf.folder);
5847                 else
5848                     desktop_folder = NULL;
5849             }
5850             else
5851                 desktop_folder = fm_folder_from_path(fm_path_get_desktop());
5852             if (desktop_folder)
5853             {
5854                 connect_model(desktop, desktop_folder);
5855                 g_object_unref(desktop_folder);
5856             }
5857             else
5858                 /* we have to add popup here because it will be never
5859                    set by connect_model() as latter wasn't called */
5860                 fm_folder_view_add_popup(FM_FOLDER_VIEW(desktop),
5861                                          GTK_WINDOW(desktop),
5862                                          fm_desktop_update_popup);
5863             if (desktop->model)
5864 #if FM_CHECK_VERSION(1, 0, 2)
5865                 fm_folder_model_set_sort(desktop->model,
5866                                          desktop->conf.desktop_sort_by,
5867                                          desktop->conf.desktop_sort_type);
5868 #else
5869                 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(desktop->model),
5870                                                      desktop->conf.desktop_sort_by,
5871                                                      desktop->conf.desktop_sort_type);
5872 #endif
5873             gtk_widget_show_all(widget);
5874             gdk_window_lower(gtk_widget_get_window(widget));
5875         }
5876     }
5877 
5878     icon_theme_changed = g_signal_connect(gtk_icon_theme_get_default(), "changed", G_CALLBACK(on_icon_theme_changed), NULL);
5879 
5880     hand_cursor = gdk_cursor_new(GDK_HAND2);
5881 
5882 #if FM_CHECK_VERSION(1, 2, 0)
5883     /* create extra items */
5884     gf = fm_file_new_for_uri("trash:///");
5885     if (g_file_query_exists(gf, NULL))
5886         trash_can = _add_extra_item("trash:///");
5887     else
5888         trash_can = NULL;
5889     if (G_LIKELY(trash_can))
5890     {
5891         trash_monitor = fm_monitor_directory(gf, NULL);
5892         g_signal_connect(trash_monitor, "changed", G_CALLBACK(on_trash_changed), trash_can);
5893     }
5894     g_object_unref(gf);
5895     documents = _add_extra_item(g_get_user_special_dir(G_USER_DIRECTORY_DOCUMENTS));
5896     /* FIXME: support some other dirs */
5897     vol_mon = g_volume_monitor_get();
5898     if (G_LIKELY(vol_mon))
5899     {
5900         GList *ml = g_volume_monitor_get_mounts(vol_mon), *l;
5901 
5902         /* if some mounts are already there, add them to own list */
5903         for (l = ml; l; l = l->next)
5904         {
5905             GMount *mount = G_MOUNT(l->data);
5906             on_mount_added(vol_mon, mount, NULL);
5907             g_object_unref(mount);
5908         }
5909         g_list_free(ml);
5910         g_signal_connect(vol_mon, "mount-added", G_CALLBACK(on_mount_added), NULL);
5911         g_signal_connect(vol_mon, "mount-removed", G_CALLBACK(on_mount_removed), NULL);
5912     }
5913 #endif
5914 
5915     pcmanfm_ref();
5916 }
5917 
fm_desktop_manager_finalize()5918 void fm_desktop_manager_finalize()
5919 {
5920     int i;
5921 
5922     if (idle_config_save)
5923     {
5924         g_source_remove(idle_config_save);
5925         idle_config_save = 0;
5926     }
5927     for(i = 0; i < n_screens; i++)
5928     {
5929         gtk_widget_destroy(GTK_WIDGET(desktops[i]));
5930     }
5931     g_free(desktops);
5932     n_screens = 0;
5933     g_object_unref(win_group);
5934     win_group = NULL;
5935 
5936     g_signal_handler_disconnect(gtk_icon_theme_get_default(), icon_theme_changed);
5937 
5938     if(acc_grp)
5939         g_object_unref(acc_grp);
5940     acc_grp = NULL;
5941 
5942     if(hand_cursor)
5943     {
5944         gdk_cursor_unref(hand_cursor);
5945         hand_cursor = NULL;
5946     }
5947 
5948 #if FM_CHECK_VERSION(1, 2, 0)
5949     if (G_LIKELY(documents))
5950     {
5951         _free_extra_item(documents);
5952         documents = NULL;
5953     }
5954     if (G_LIKELY(trash_can))
5955     {
5956         g_signal_handlers_disconnect_by_func(trash_monitor, on_trash_changed, trash_can);
5957         g_object_unref(trash_monitor);
5958         _free_extra_item(trash_can);
5959         trash_can = NULL;
5960     }
5961     if (G_LIKELY(vol_mon))
5962     {
5963         g_signal_handlers_disconnect_by_func(vol_mon, on_mount_added, NULL);
5964         g_signal_handlers_disconnect_by_func(vol_mon, on_mount_removed, NULL);
5965         g_object_unref(vol_mon);
5966         vol_mon = NULL;
5967     }
5968     while (mounts)
5969     {
5970         _free_extra_item(mounts->data);
5971         mounts = g_slist_delete_link(mounts, mounts);
5972     }
5973 #endif
5974 
5975     pcmanfm_unref();
5976 }
5977 
fm_desktop_get(gint screen,gint monitor)5978 FmDesktop* fm_desktop_get(gint screen, gint monitor)
5979 {
5980     int i = 0, n = 0;
5981     while(i < n_screens && n <= screen)
5982     {
5983         if(n == screen && desktops[i]->monitor == monitor)
5984             return desktops[i];
5985         i++;
5986         if(i < n_screens &&
5987            (desktops[i]->monitor == 0 || desktops[i]->monitor == -1))
5988             n++;
5989     }
5990     return NULL;
5991 }
5992 
fm_desktop_wallpaper_changed(FmDesktop * desktop)5993 void fm_desktop_wallpaper_changed(FmDesktop *desktop)
5994 {
5995     queue_config_save(desktop);
5996     update_background(desktop, 0);
5997 }
5998