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