1 /**
2 * This file is a part of the Cairo-Dock project
3 *
4 * Copyright : (C) see the 'copyright' file.
5 * E-mail : see the 'copyright' file.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (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 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <string.h>
21
22 #include "applet-struct.h"
23 #include "applet-menu.h"
24
25 static void _on_activate_item (GtkWidget *pMenuItem, CDQuickBrowserItem *pItem);
26 static gboolean _on_click_item (GtkWidget *pWidget, GdkEventButton* pButton, CDQuickBrowserItem *pItem);
27
_sort_item(CDQuickBrowserItem * pItem1,CDQuickBrowserItem * pItem2)28 static int _sort_item (CDQuickBrowserItem *pItem1, CDQuickBrowserItem *pItem2)
29 {
30 if (pItem1 == NULL)
31 return -1;
32 if (pItem2 == NULL)
33 return 1;
34 GldiModuleInstance *myApplet = pItem2->pApplet;
35 if (myConfig.bFoldersFirst)
36 {
37 if (pItem1->pSubMenu && ! pItem2->pSubMenu)
38 return -1;
39 if (! pItem1->pSubMenu && pItem2->pSubMenu)
40 return 1;
41 }
42 if (myConfig.bCaseUnsensitive)
43 return g_ascii_strcasecmp (pItem1->cTmpFileName, pItem2->cTmpFileName);
44 else
45 return strcmp (pItem1->cTmpFileName, pItem2->cTmpFileName);
46 }
_list_dir(const gchar * cDirPath,GldiModuleInstance * myApplet)47 static GList *_list_dir (const gchar *cDirPath, GldiModuleInstance *myApplet)
48 {
49 //\______________ On ouvre le repertoire en lecture.
50 GError *erreur = NULL;
51 GDir *dir = g_dir_open (cDirPath, 0, &erreur);
52 if (erreur != NULL)
53 {
54 cd_warning (erreur->message);
55 g_error_free (erreur);
56 return NULL;
57 }
58
59 //\______________ On recupere chaque item, qu'on classe dans une liste temporaire.
60 CDQuickBrowserItem *pItem;
61 const gchar *cFileName;
62 GList *pLocalItemList = NULL;
63 do
64 {
65 cFileName = g_dir_read_name (dir);
66 if (cFileName == NULL)
67 break ;
68 if (! myConfig.bShowHiddenFiles && ( *cFileName == '.' || cFileName[strlen(cFileName) - 1] == '~' ) )
69 continue;
70 pItem = g_new0 (CDQuickBrowserItem, 1);
71 pItem->cPath = g_strdup_printf ("%s/%s", cDirPath, cFileName);
72 pItem->cTmpFileName = cFileName; // valable uniquement dans cette boucle, ca tombe bien le classement se fait ici.
73 pItem->pApplet = myApplet;
74 if (g_file_test (pItem->cPath, G_FILE_TEST_IS_DIR))
75 pItem->pSubMenu = gldi_menu_new (NULL);
76
77 pLocalItemList = g_list_insert_sorted (pLocalItemList,
78 pItem,
79 (GCompareFunc)_sort_item);
80 }
81 while (1);
82 g_dir_close (dir);
83
84 return pLocalItemList;
85 }
_init_fill_menu_from_dir(CDQuickBrowserItem * pItem)86 static void _init_fill_menu_from_dir (CDQuickBrowserItem *pItem)
87 {
88 const gchar *cDirPath = pItem->cPath;
89 GtkWidget *pMenu = pItem->pSubMenu;
90 GldiModuleInstance *myApplet = pItem->pApplet;
91
92 //\______________ On recupere les items du repertoire.
93 GList *pLocalItemList = _list_dir (cDirPath, myApplet);
94
95 //\______________ On rajoute en premier une entree pour ouvrir le repertoire.
96 CDQuickBrowserItem *pOpenDirItem = g_new0 (CDQuickBrowserItem, 1);
97 pOpenDirItem->cPath = g_strdup (cDirPath);
98 pOpenDirItem->pApplet = myApplet;
99 pItem->pLocalItemList = g_list_prepend (pLocalItemList, pOpenDirItem);
100 pItem->pCurrentItem = pItem->pLocalItemList->next; // on la rajoute au menu ici, pas en meme temps que les autres.
101
102 //\______________ On ajoute cette entree dans le menu des maintenant.
103 GtkWidget *pMenuItem = gldi_menu_add_item (pMenu, D_("Open this folder"), myConfig.bHasIcons ? GLDI_ICON_NAME_OPEN : NULL, G_CALLBACK(_on_activate_item), pOpenDirItem); // right click (e.g. open Bonobo or another file mgr)
104 g_signal_connect (G_OBJECT (pMenuItem), "button-release-event", G_CALLBACK(_on_click_item), pOpenDirItem);
105 }
106
107
_drag_begin(GtkWidget * pWidget,GdkDragContext * pDragContext,GtkWidget * pMenuItem)108 static void _drag_begin (GtkWidget *pWidget, GdkDragContext *pDragContext, GtkWidget *pMenuItem)
109 {
110 // add an icon: the current pixbuf (add it now, once and for all).
111 if (GLDI_IS_IMAGE_MENU_ITEM (pMenuItem)) // some items don't have any icon.
112 {
113 gtk_drag_source_set_icon_pixbuf (pMenuItem,
114 gtk_image_get_pixbuf (GTK_IMAGE (gldi_menu_item_get_image (pMenuItem)))); // GTK+ retains a reference on the pixbuf; when pMenuItem disappear, it will naturally loose this reference.
115 }
116 }
117
_drag_data_get(GtkWidget * pWidget,GdkDragContext * pDragContext,GtkSelectionData * pSelectionData,guint iInfo,guint iTime,CDQuickBrowserItem * pItem)118 static void _drag_data_get (GtkWidget *pWidget, GdkDragContext *pDragContext,
119 GtkSelectionData *pSelectionData, guint iInfo, guint iTime, CDQuickBrowserItem *pItem)
120 {
121 gchar *cURI = g_filename_to_uri (pItem->cPath, NULL, NULL);
122 if (cURI != NULL)
123 {
124 gtk_selection_data_set (pSelectionData, gtk_selection_data_get_target (pSelectionData), 8, (guchar *) cURI, strlen (cURI));
125 g_free (cURI);
126 }
127 }
128
_fill_submenu_with_items(CDQuickBrowserItem * pRootItem,int iNbSubItemsAtOnce)129 static void _fill_submenu_with_items (CDQuickBrowserItem *pRootItem, int iNbSubItemsAtOnce)
130 {
131 GldiModuleInstance *myApplet = pRootItem->pApplet;
132 GtkWidget *pMenu = pRootItem->pSubMenu;
133 GList *pFirstItem = pRootItem->pCurrentItem;
134
135 // static GtkTargetEntry s_pMenuItemTargets[] = { {(gchar*) "text/uri-list", 0, 0} }; // for drag and drop support
136
137 CDQuickBrowserItem *pItem;
138 gchar *cFileName;
139 GtkWidget *pMenuItem;
140 gchar *cName = NULL, *cURI = NULL, *cIconName = NULL;
141 gboolean bIsDirectory;
142 int iVolumeID;
143 double fOrder;
144 GList *l;
145 int i;
146 for (l = pFirstItem, i = 0; l != NULL && i < iNbSubItemsAtOnce; l = l->next, i ++)
147 {
148 pItem = l->data;
149
150 //\______________ On cree l'entree avec son icone si necessaire.
151 if (myConfig.bHasIcons)
152 {
153 cairo_dock_fm_get_file_info (pItem->cPath, &cName, &cURI, &cIconName, &bIsDirectory, &iVolumeID, &fOrder, 0);
154 g_free (cName);
155 cName = NULL;
156 g_free (cURI);
157 cURI = NULL;
158 }
159
160 cFileName = strrchr (pItem->cPath, '/');
161 if (cFileName)
162 cFileName ++;
163
164 if (cIconName != NULL)
165 {
166 gchar *cPath = cairo_dock_search_icon_s_path (cIconName, cairo_dock_search_icon_size (GTK_ICON_SIZE_MENU));
167 pMenuItem = gldi_menu_item_new (cFileName, cPath);
168 g_free (cPath);
169 g_free (cIconName);
170 cIconName = NULL;
171 }
172 else
173 {
174 pMenuItem = gldi_menu_item_new (cFileName, "");
175 }
176
177 //\______________ On l'insere dans le menu.
178 gtk_menu_shell_append (GTK_MENU_SHELL (pMenu), pMenuItem);
179
180 if (pItem->pSubMenu != NULL)
181 {
182 gtk_menu_item_set_submenu (GTK_MENU_ITEM (pMenuItem), pItem->pSubMenu);
183 }
184 else
185 {
186 //\______________ Add drag and drop support for files only
187 gtk_drag_source_set (pMenuItem, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
188 NULL, 0,
189 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
190
191 gtk_drag_source_add_text_targets (pMenuItem);
192 gtk_drag_source_add_uri_targets (pMenuItem);
193
194 g_signal_connect (G_OBJECT (pMenuItem), "button-release-event", G_CALLBACK(_on_click_item), pItem); // left and right click
195 g_signal_connect (G_OBJECT (pMenuItem), "drag-begin", G_CALLBACK (_drag_begin), pMenuItem); // to create pixbuf
196 g_signal_connect (G_OBJECT (pMenuItem), "drag-data-get", G_CALLBACK (_drag_data_get), pItem); // when the item is dropped
197 }
198 g_signal_connect (G_OBJECT (pMenuItem), "activate", G_CALLBACK(_on_activate_item), pItem); // select or over (submenu)
199 }
200 pRootItem->pCurrentItem = l;
201 }
_fill_submenu_idle(CDQuickBrowserItem * pItem)202 static gboolean _fill_submenu_idle (CDQuickBrowserItem *pItem)
203 {
204 GldiModuleInstance *myApplet = pItem->pApplet;
205 CD_APPLET_ENTER;
206 if (pItem->pLocalItemList == NULL)
207 {
208 _init_fill_menu_from_dir (pItem);
209 if (pItem->pLocalItemList == NULL) // cas particulier d'un repertoire vide, inutile de revenir ici pour rien faire.
210 pItem->bMenuBuilt = TRUE;
211 }
212 else
213 {
214 _fill_submenu_with_items (pItem, myConfig.iNbSubItemsAtOnce);
215 if (pItem->pCurrentItem == NULL)
216 pItem->bMenuBuilt = TRUE;
217 }
218
219 if (pItem->bMenuBuilt)
220 {
221 GldiModuleInstance *myApplet = pItem->pApplet;
222 myData.iSidFillDirIdle = 0;
223 /* force to compute the size of the menu before displaying it
224 * -> avoid big menu that are out of the screen
225 */
226 gtk_widget_set_size_request (pItem->pSubMenu, -1, -1);
227 gtk_widget_show_all (pItem->pSubMenu);
228 CD_APPLET_LEAVE (FALSE);
229 }
230 CD_APPLET_LEAVE (TRUE);
231 }
_on_activate_item(GtkWidget * pMenuItem,CDQuickBrowserItem * pItem)232 static void _on_activate_item (GtkWidget *pMenuItem, CDQuickBrowserItem *pItem)
233 {
234 g_return_if_fail (pItem != NULL);
235 GldiModuleInstance *myApplet = pItem->pApplet;
236 CD_APPLET_ENTER;
237 if (pItem->pSubMenu != NULL)
238 {
239 if (! pItem->bMenuBuilt)
240 {
241 if (myData.iSidFillDirIdle != 0)
242 g_source_remove (myData.iSidFillDirIdle);
243 myData.iSidFillDirIdle = g_idle_add ((GSourceFunc) _fill_submenu_idle, pItem);
244 }
245 }
246 else // left click, no drag
247 {
248 cairo_dock_fm_launch_uri (pItem->cPath);
249 cd_quick_browser_destroy_menu (myApplet);
250 }
251 CD_APPLET_LEAVE ();
252 }
253
_free_app_list_data(gpointer * data)254 static void _free_app_list_data (gpointer *data)
255 {
256 gchar *cExec = data[1];
257 g_free (cExec);
258 g_free (data);
259 }
260
cd_quick_browser_free_apps_list(GldiModuleInstance * myApplet)261 void cd_quick_browser_free_apps_list (GldiModuleInstance *myApplet)
262 {
263 if (myData.pAppList != NULL)
264 {
265 g_list_foreach (myData.pAppList, (GFunc) _free_app_list_data, NULL);
266 g_list_free (myData.pAppList);
267 myData.pAppList = NULL;
268 }
269 }
270
_cd_launch_with(GtkMenuItem * pMenuItem,gpointer * data)271 static void _cd_launch_with (GtkMenuItem *pMenuItem, gpointer *data)
272 {
273 CDQuickBrowserItem *pItem = data[0];
274 const gchar *cExec = data[1];
275
276 cairo_dock_launch_command_printf ("%s \"%s\"", NULL, cExec, pItem->cPath); // in case the program doesn't handle URI (geeqie, etc).
277
278 cd_quick_browser_destroy_menu (pItem->pApplet);
279 }
280
_cd_open_parent(GtkMenuItem * pMenuItem,CDQuickBrowserItem * pItem)281 static void _cd_open_parent (GtkMenuItem *pMenuItem, CDQuickBrowserItem *pItem)
282 {
283 gchar *cUri = g_filename_to_uri (pItem->cPath, NULL, NULL);
284 gchar *cFolder = g_path_get_dirname (cUri);
285 cairo_dock_fm_launch_uri (cFolder);
286 g_free (cFolder);
287 g_free (cUri);
288
289 cd_quick_browser_destroy_menu (pItem->pApplet);
290 }
_cd_copy_location(GtkMenuItem * pMenuItem,CDQuickBrowserItem * pItem)291 static void _cd_copy_location (GtkMenuItem *pMenuItem, CDQuickBrowserItem *pItem)
292 {
293 GtkClipboard *pClipBoard;
294 pClipBoard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); // GDK_SELECTION_PRIMARY
295
296 gtk_clipboard_set_text (pClipBoard, pItem->cPath, -1);
297
298 cd_quick_browser_destroy_menu (pItem->pApplet);
299 }
_on_click_item(GtkWidget * pWidget,GdkEventButton * pButton,CDQuickBrowserItem * pItem)300 static gboolean _on_click_item (GtkWidget *pWidget, GdkEventButton* pButton, CDQuickBrowserItem *pItem)
301 {
302 g_return_val_if_fail (pItem != NULL, FALSE);
303 GldiModuleInstance *myApplet = pItem->pApplet;
304 CD_APPLET_ENTER;
305
306 if (pButton->button == 3) // right click
307 {
308 gchar *cUri = g_filename_to_uri (pItem->cPath, NULL, NULL);
309 g_return_val_if_fail (cUri != NULL, FALSE);
310
311 GtkWidget *pMenu = gldi_menu_new (NULL);
312
313 GList *pApps = cairo_dock_fm_list_apps_for_file (cUri);
314 if (pApps != NULL)
315 {
316 GtkWidget *pSubMenu = CD_APPLET_ADD_SUB_MENU_WITH_IMAGE (D_("Open with"), pMenu, GLDI_ICON_NAME_OPEN);
317
318 cd_quick_browser_free_apps_list (myApplet);
319
320 GList *a;
321 gchar **pAppInfo;
322 gchar *cIconPath;
323 for (a = pApps; a != NULL; a = a->next)
324 {
325 pAppInfo = a->data;
326
327 if (pAppInfo[2] != NULL)
328 cIconPath = cairo_dock_search_icon_s_path (pAppInfo[2], cairo_dock_search_icon_size (GTK_ICON_SIZE_MENU));
329 else
330 cIconPath = NULL;
331
332 gpointer *data = g_new (gpointer, 2);
333 data[0] = pItem;
334 data[1] = pAppInfo[1];
335 myData.pAppList = g_list_prepend (myData.pAppList, data); // to save the exec command
336
337 CD_APPLET_ADD_IN_MENU_WITH_STOCK_AND_DATA (pAppInfo[0], cIconPath, _cd_launch_with, pSubMenu, data);
338
339 g_free (cIconPath);
340 g_free (pAppInfo[0]);
341 g_free (pAppInfo[2]);
342 g_free (pAppInfo);
343 }
344 g_list_free (pApps);
345 }
346 CD_APPLET_ADD_IN_MENU_WITH_STOCK_AND_DATA (D_("Open parent folder"), GLDI_ICON_NAME_DIRECTORY, _cd_open_parent, pMenu, pItem);
347
348 CD_APPLET_ADD_IN_MENU_WITH_STOCK_AND_DATA (D_("Copy the location"), GLDI_ICON_NAME_COPY, _cd_copy_location, pMenu, pItem);
349
350 gtk_widget_show_all (pMenu);
351 gtk_menu_popup (GTK_MENU (pMenu),
352 NULL,
353 NULL,
354 NULL, // popup on mouse.
355 NULL,
356 1,
357 gtk_get_current_event_time ());
358 g_free (cUri);
359 CD_APPLET_LEAVE (TRUE); // do not remove quick_browser menu now
360 }
361
362 CD_APPLET_LEAVE (FALSE);
363 }
364
cd_quick_browser_make_menu_from_dir(const gchar * cDirPath,GldiModuleInstance * myApplet)365 CDQuickBrowserItem *cd_quick_browser_make_menu_from_dir (const gchar *cDirPath, GldiModuleInstance *myApplet)
366 {
367 CDQuickBrowserItem *pRootItem = g_new0 (CDQuickBrowserItem, 1);
368 pRootItem->cPath = g_strdup (cDirPath);
369 pRootItem->pApplet = myApplet;
370 pRootItem->pSubMenu = gldi_menu_new (myIcon);
371
372 _init_fill_menu_from_dir (pRootItem);
373 _fill_submenu_with_items (pRootItem, 1e6);
374 pRootItem->bMenuBuilt = TRUE;
375 gtk_widget_show_all (pRootItem->pSubMenu);
376
377 return pRootItem;
378 }
379
_free_item(CDQuickBrowserItem * pItem)380 static void _free_item (CDQuickBrowserItem *pItem)
381 {
382 g_free (pItem->cPath);
383 if (pItem->pLocalItemList != NULL)
384 {
385 g_list_foreach (pItem->pLocalItemList, (GFunc) _free_item, NULL);
386 g_list_free (pItem->pLocalItemList);
387 }
388 g_free (pItem);
389 }
cd_quick_browser_destroy_menu(GldiModuleInstance * myApplet)390 void cd_quick_browser_destroy_menu (GldiModuleInstance *myApplet)
391 {
392 if (myData.iSidFillDirIdle != 0)
393 g_source_remove (myData.iSidFillDirIdle);
394 myData.iSidFillDirIdle = 0;
395
396 if (myData.pRootItem != NULL)
397 {
398 gtk_widget_destroy (myData.pRootItem->pSubMenu); // detruit tous les pSubMenu en cascade.
399 _free_item (myData.pRootItem);
400 myData.pRootItem = NULL;
401 }
402 }
403
cd_quick_browser_show_menu(GldiModuleInstance * myApplet)404 void cd_quick_browser_show_menu (GldiModuleInstance *myApplet)
405 {
406 cd_quick_browser_destroy_menu (myApplet);
407
408 myData.pRootItem = cd_quick_browser_make_menu_from_dir (myConfig.cDirPath, myApplet);
409 g_return_if_fail (myData.pRootItem != NULL && myData.pRootItem->pSubMenu != NULL);
410
411 CD_APPLET_POPUP_MENU_ON_MY_ICON (myData.pRootItem->pSubMenu);
412 }
413