1 /*
2  * applications-menu-model: A list model containing menu items
3  *                          of applications
4  *
5  * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
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  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include <glib/gi18n-lib.h>
30 
31 #include <libxfdashboard/applications-menu-model.h>
32 #include <libxfdashboard/application-database.h>
33 #include <libxfdashboard/compat.h>
34 #include <libxfdashboard/debug.h>
35 
36 
37 /* Define these classes in GObject system */
38 struct _XfdashboardApplicationsMenuModelPrivate
39 {
40 	/* Instance related */
41 	GarconMenu						*rootMenu;
42 
43 	XfdashboardApplicationDatabase	*appDB;
44 	guint							reloadRequiredSignalID;
45 };
46 
47 G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardApplicationsMenuModel,
48 							xfdashboard_applications_menu_model,
49 							XFDASHBOARD_TYPE_MODEL)
50 
51 /* Signals */
52 enum
53 {
54 	SIGNAL_LOADED,
55 
56 	SIGNAL_LAST
57 };
58 
59 static guint XfdashboardApplicationsMenuModelSignals[SIGNAL_LAST]={ 0, };
60 
61 /* IMPLEMENTATION: Private variables and methods */
62 typedef struct _XfdashboardApplicationsMenuModelFillData		XfdashboardApplicationsMenuModelFillData;
63 struct _XfdashboardApplicationsMenuModelFillData
64 {
65 	gint				sequenceID;
66 	GSList				*populatedMenus;
67 };
68 
69 typedef struct _XfdashboardApplicationsMenuModelItem			XfdashboardApplicationsMenuModelItem;
70 struct _XfdashboardApplicationsMenuModelItem
71 {
72 	guint				sequenceID;
73 	GarconMenuElement	*menuElement;
74 	GarconMenu			*parentMenu;
75 	GarconMenu			*section;
76 	gchar				*title;
77 	gchar				*description;
78 };
79 
80 /* Forward declarations */
81 static void _xfdashboard_applications_menu_model_fill_model(XfdashboardApplicationsMenuModel *self);
82 
83 /* Free an item of application menu model */
_xfdashboard_applications_menu_model_item_free(XfdashboardApplicationsMenuModelItem * inItem)84 static void _xfdashboard_applications_menu_model_item_free(XfdashboardApplicationsMenuModelItem *inItem)
85 {
86 	if(inItem)
87 	{
88 		/* Release allocated resources in item */
89 		if(inItem->menuElement) g_object_unref(inItem->menuElement);
90 		if(inItem->parentMenu) g_object_unref(inItem->parentMenu);
91 		if(inItem->section) g_object_unref(inItem->section);
92 		if(inItem->title) g_free(inItem->title);
93 		if(inItem->description) g_free(inItem->description);
94 
95 		/* Free item */
96 		g_free(inItem);
97 	}
98 }
99 
100 /* Create a new item for application menu model */
_xfdashboard_applications_menu_model_item_new(void)101 static XfdashboardApplicationsMenuModelItem* _xfdashboard_applications_menu_model_item_new(void)
102 {
103 	XfdashboardApplicationsMenuModelItem	*item;
104 
105 	/* Create empty item */
106 	item=g_new0(XfdashboardApplicationsMenuModelItem, 1);
107 
108 	/* Return new empty item */
109 	return(item);
110 }
111 
112 /* A menu was changed and needs to be reloaded */
_xfdashboard_applications_menu_model_on_reload_required(XfdashboardApplicationsMenuModel * self,gpointer inUserData)113 static void _xfdashboard_applications_menu_model_on_reload_required(XfdashboardApplicationsMenuModel *self,
114 																	gpointer inUserData)
115 {
116 	g_return_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(inUserData));
117 
118 	/* Reload menu by filling it again. This also emits all necessary signals. */
119 	XFDASHBOARD_DEBUG(self, APPLICATIONS, "Applications menu has changed and needs to be reloaded.");
120 	_xfdashboard_applications_menu_model_fill_model(self);
121 }
122 
123 /* Clear all data in model and also release all allocated resources needed for this model */
_xfdashboard_applications_menu_model_clear(XfdashboardApplicationsMenuModel * self)124 static void _xfdashboard_applications_menu_model_clear(XfdashboardApplicationsMenuModel *self)
125 {
126 	XfdashboardApplicationsMenuModelPrivate		*priv;
127 
128 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(self));
129 
130 	priv=self->priv;
131 
132 	/* Unset filter (forces all rows being accessible and not being skipped/filtered) */
133 	xfdashboard_model_set_filter(XFDASHBOARD_MODEL(self), NULL, NULL, NULL);
134 
135 	/* Clean up and remove all rows */
136 	xfdashboard_model_remove_all(XFDASHBOARD_MODEL(self));
137 
138 	/* Destroy root menu */
139 	if(priv->rootMenu)
140 	{
141 		g_object_unref(priv->rootMenu);
142 		priv->rootMenu=NULL;
143 	}
144 }
145 
146 /* Helper function to filter model data */
_xfdashboard_applications_menu_model_filter_by_menu(XfdashboardModelIter * inIter,gpointer inUserData)147 static gboolean _xfdashboard_applications_menu_model_filter_by_menu(XfdashboardModelIter *inIter,
148 																	gpointer inUserData)
149 {
150 	XfdashboardApplicationsMenuModel			*model;
151 	XfdashboardApplicationsMenuModelPrivate		*modelPriv;
152 	gboolean									doShow;
153 	GarconMenu									*requestedParentMenu;
154 	XfdashboardApplicationsMenuModelItem		*item;
155 	GarconMenuItemPool							*itemPool;
156 	const gchar									*desktopID;
157 
158 	g_return_val_if_fail(XFDASHBOARD_IS_MODEL_ITER(inIter), FALSE);
159 	g_return_val_if_fail(GARCON_IS_MENU(inUserData), FALSE);
160 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(xfdashboard_model_iter_get_model(inIter)), FALSE);
161 
162 	doShow=FALSE;
163 	requestedParentMenu=GARCON_MENU(inUserData);
164 	model=XFDASHBOARD_APPLICATIONS_MENU_MODEL(xfdashboard_model_iter_get_model(inIter));
165 	modelPriv=model->priv;
166 
167 	/* Get menu element at iterator */
168 	item=(XfdashboardApplicationsMenuModelItem*)xfdashboard_model_iter_get(inIter);
169 	if(item->menuElement==NULL) return(FALSE);
170 
171 	/* Only menu items and sub-menus can be visible */
172 	if(!GARCON_IS_MENU(item->menuElement) && !GARCON_IS_MENU_ITEM(item->menuElement))
173 	{
174 		return(FALSE);
175 	}
176 
177 	/* If menu element is a menu check if it's parent menu is the requested one */
178 	if(GARCON_IS_MENU(item->menuElement))
179 	{
180 		if(requestedParentMenu==item->parentMenu ||
181 			(!requestedParentMenu && item->parentMenu==modelPriv->rootMenu))
182 		{
183 			doShow=TRUE;
184 		}
185 	}
186 		/* Otherwise it is a menu item and check if item is in requested menu */
187 		else
188 		{
189 			/* Get desktop ID of menu item */
190 			desktopID=garcon_menu_item_get_desktop_id(GARCON_MENU_ITEM(item->menuElement));
191 
192 			/* Get menu items of menu */
193 			itemPool=garcon_menu_get_item_pool(item->parentMenu);
194 
195 			/* Determine if menu item at iterator is in menu's item pool */
196 			if(garcon_menu_item_pool_lookup(itemPool, desktopID)!=FALSE) doShow=TRUE;
197 		}
198 
199 	/* If we get here return TRUE to show model data item or FALSE to hide */
200 	return(doShow);
201 }
202 
_xfdashboard_applications_menu_model_filter_by_section(XfdashboardModelIter * inIter,gpointer inUserData)203 static gboolean _xfdashboard_applications_menu_model_filter_by_section(XfdashboardModelIter *inIter,
204 																		gpointer inUserData)
205 {
206 	XfdashboardApplicationsMenuModel			*model;
207 	XfdashboardApplicationsMenuModelPrivate		*modelPriv;
208 	gboolean									doShow;
209 	GarconMenu									*requestedSection;
210 	XfdashboardApplicationsMenuModelItem		*item;
211 
212 	g_return_val_if_fail(XFDASHBOARD_IS_MODEL_ITER(inIter), FALSE);
213 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(xfdashboard_model_iter_get_model(inIter)), FALSE);
214 	g_return_val_if_fail(GARCON_IS_MENU(inUserData), FALSE);
215 
216 	doShow=FALSE;
217 	requestedSection=GARCON_MENU(inUserData);
218 	model=XFDASHBOARD_APPLICATIONS_MENU_MODEL(xfdashboard_model_iter_get_model(inIter));
219 	modelPriv=model->priv;
220 
221 	/* Check if root section is requested */
222 	if(!requestedSection) requestedSection=modelPriv->rootMenu;
223 
224 	/* Get menu element at iterator */
225 	item=(XfdashboardApplicationsMenuModelItem*)xfdashboard_model_iter_get(inIter);
226 
227 	/* If menu element is a menu check if root menu is parent menu and root menu is requested */
228 	if((item->section && item->section==requestedSection) ||
229 		(!item->section && requestedSection==modelPriv->rootMenu))
230 	{
231 		doShow=TRUE;
232 	}
233 
234 	/* If we get here return TRUE to show model data item or FALSE to hide */
235 	return(doShow);
236 }
237 
_xfdashboard_applications_menu_model_filter_empty(XfdashboardModelIter * inIter,gpointer inUserData)238 static gboolean _xfdashboard_applications_menu_model_filter_empty(XfdashboardModelIter *inIter,
239 																	gpointer inUserData)
240 {
241 	g_return_val_if_fail(XFDASHBOARD_IS_MODEL_ITER(inIter), FALSE);
242 	g_return_val_if_fail(GARCON_IS_MENU(inUserData), FALSE);
243 
244 	/* This functions always returns FALSE because each entry is considered empty and hidden */
245 	return(FALSE);
246 }
247 
248 /* Fill model */
_xfdashboard_applications_menu_model_find_similar_menu(XfdashboardApplicationsMenuModel * self,GarconMenu * inMenu,XfdashboardApplicationsMenuModelFillData * inFillData)249 static GarconMenu* _xfdashboard_applications_menu_model_find_similar_menu(XfdashboardApplicationsMenuModel *self,
250 																			GarconMenu *inMenu,
251 																			XfdashboardApplicationsMenuModelFillData *inFillData)
252 {
253 	GarconMenu					*parentMenu;
254 	GSList						*iter;
255 	GarconMenu					*foundMenu;
256 
257 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(self), NULL);
258 	g_return_val_if_fail(GARCON_IS_MENU(inMenu), NULL);
259 	g_return_val_if_fail(inFillData, NULL);
260 
261 	/* Check if menu is visible. Hidden menus do not need to be checked. */
262 	if(!garcon_menu_element_get_visible(GARCON_MENU_ELEMENT(inMenu))) return(NULL);
263 
264 	/* Get parent menu to look for at each menu we iterate */
265 	parentMenu=garcon_menu_get_parent(inMenu);
266 	if(!parentMenu) return(NULL);
267 
268 	/* Iterate through parent menu up to current menu and lookup similar menu.
269 	 * A similar menu is identified by either they share the same directory
270 	 * or match in name, description and icon.
271 	 */
272 	foundMenu=NULL;
273 	for(iter=inFillData->populatedMenus; iter && !foundMenu; iter=g_slist_next(iter))
274 	{
275 		GarconMenu				*iterMenu;
276 
277 		/* Get menu element from list */
278 		iterMenu=GARCON_MENU(iter->data);
279 
280 		/* We can only process menus which have the same parent menu as the
281 		 * requested menu and they need to be visible.
282 		 */
283 		if(garcon_menu_get_parent(iterMenu) &&
284 			garcon_menu_element_get_visible(GARCON_MENU_ELEMENT(iterMenu)))
285 		{
286 			gboolean			isSimilar;
287 			GarconMenuDirectory	*iterMenuDirectory;
288 			GarconMenuDirectory	*menuDirectory;
289 
290 			/* Check if both menus share the same directory. That will be the
291 			 * case if iterator point to the menu which was given as function
292 			 * parameter. So it's safe just to iterate through.
293 			 */
294 			iterMenuDirectory=garcon_menu_get_directory(iterMenu);
295 			menuDirectory=garcon_menu_get_directory(inMenu);
296 
297 			isSimilar=FALSE;
298 			if(iterMenuDirectory && menuDirectory)
299 			{
300 				isSimilar=garcon_menu_directory_equal(iterMenuDirectory, menuDirectory);
301 			}
302 
303 			/* If both menus do not share the same directory, check if they
304 			 * match in name, description and icon.
305 			 */
306 			if(!isSimilar)
307 			{
308 				const gchar		*left;
309 				const gchar		*right;
310 
311 				/* Reset similar flag to TRUE as it will be set to FALSE again
312 				 * on first item not matching.
313 				 */
314 				isSimilar=TRUE;
315 
316 				/* Check title */
317 				if(isSimilar)
318 				{
319 					left=garcon_menu_element_get_name(GARCON_MENU_ELEMENT(inMenu));
320 					right=garcon_menu_element_get_name(GARCON_MENU_ELEMENT(iterMenu));
321 					if(g_strcmp0(left, right)!=0) isSimilar=FALSE;
322 				}
323 
324 
325 				/* Check description */
326 				if(isSimilar)
327 				{
328 					left=garcon_menu_element_get_comment(GARCON_MENU_ELEMENT(inMenu));
329 					right=garcon_menu_element_get_comment(GARCON_MENU_ELEMENT(iterMenu));
330 					if(g_strcmp0(left, right)!=0) isSimilar=FALSE;
331 				}
332 
333 
334 				/* Check icon */
335 				if(isSimilar)
336 				{
337 					left=garcon_menu_element_get_icon_name(GARCON_MENU_ELEMENT(inMenu));
338 					right=garcon_menu_element_get_icon_name(GARCON_MENU_ELEMENT(iterMenu));
339 					if(g_strcmp0(left, right)!=0) isSimilar=FALSE;
340 				}
341 			}
342 
343 			/* If we get and we found a similar menu set result to return */
344 			if(isSimilar) foundMenu=iterMenu;
345 		}
346 	}
347 
348 	/* Return found menu */
349 	return(foundMenu);
350 }
351 
_xfdashboard_applications_menu_model_find_section(XfdashboardApplicationsMenuModel * self,GarconMenu * inMenu,XfdashboardApplicationsMenuModelFillData * inFillData)352 static GarconMenu* _xfdashboard_applications_menu_model_find_section(XfdashboardApplicationsMenuModel *self,
353 																		GarconMenu *inMenu,
354 																		XfdashboardApplicationsMenuModelFillData *inFillData)
355 {
356 	XfdashboardApplicationsMenuModelPrivate		*priv;
357 	GarconMenu									*sectionMenu;
358 	GarconMenu									*parentMenu;
359 
360 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(self), NULL);
361 	g_return_val_if_fail(GARCON_IS_MENU(inMenu), NULL);
362 
363 	priv=self->priv;
364 
365 	/* Finding section is technically the same as looking up similar menu
366 	 * but only at top-level menus. So iterate through all parent menu
367 	 * until menu is found which parent menu is root menu. That is the
368 	 * section and we need to check for a similar one at that level.
369 	 */
370 	sectionMenu=inMenu;
371 	do
372 	{
373 		/* Get parent menu */
374 		parentMenu=garcon_menu_get_parent(sectionMenu);
375 
376 		/* Check if parent menu is root menu stop here */
377 		if(!parentMenu || parentMenu==priv->rootMenu) break;
378 
379 		/* Set current parent menu as found section menu */
380 		sectionMenu=parentMenu;
381 	}
382 	while(parentMenu);
383 
384 	/* Find similar menu to found section menu */
385 	if(sectionMenu)
386 	{
387 		sectionMenu=_xfdashboard_applications_menu_model_find_similar_menu(self, sectionMenu, inFillData);
388 	}
389 
390 	/* Return found section menu */
391 	return(sectionMenu);
392 }
393 
_xfdashboard_applications_menu_model_fill_model_collect_menu(XfdashboardApplicationsMenuModel * self,GarconMenu * inMenu,GarconMenu * inParentMenu,XfdashboardApplicationsMenuModelFillData * inFillData)394 static void _xfdashboard_applications_menu_model_fill_model_collect_menu(XfdashboardApplicationsMenuModel *self,
395 																			GarconMenu *inMenu,
396 																			GarconMenu *inParentMenu,
397 																			XfdashboardApplicationsMenuModelFillData *inFillData)
398 {
399 	XfdashboardApplicationsMenuModelPrivate			*priv;
400 	GarconMenu										*menu;
401 	GarconMenu										*section;
402 	GList											*elements, *element;
403 	XfdashboardApplicationsMenuModelItem			*item;
404 
405 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(self));
406 	g_return_if_fail(GARCON_IS_MENU(inMenu));
407 
408 	priv=self->priv;
409 	section=NULL;
410 	menu=priv->rootMenu;
411 
412 	/* Increase reference on menu going to be processed to keep it alive */
413 	g_object_ref(inMenu);
414 
415 	/* Skip additional check on root menu as it must be processed normally and non-disruptively */
416 	if(inMenu!=priv->rootMenu)
417 	{
418 		/* Find section to add menu to */
419 		section=_xfdashboard_applications_menu_model_find_section(self, inMenu, inFillData);
420 
421 		/* Add menu to model if no duplicate or similar menu exist */
422 		menu=_xfdashboard_applications_menu_model_find_similar_menu(self, inMenu, inFillData);
423 		if(!menu)
424 		{
425 			gchar									*title;
426 			gchar									*description;
427 			const gchar								*temp;
428 
429 			/* To increase performance when sorting of filtering this model by title or description
430 			 * in a case-insensitive way store title and description in lower case.
431 			 */
432 			temp=garcon_menu_element_get_name(GARCON_MENU_ELEMENT(inMenu));
433 			if(temp) title=g_utf8_strdown(temp, -1);
434 				else title=NULL;
435 
436 			temp=garcon_menu_element_get_comment(GARCON_MENU_ELEMENT(inMenu));
437 			if(temp) description=g_utf8_strdown(temp, -1);
438 				else description=NULL;
439 
440 			/* Insert row into model because there is no duplicate
441 			 * and no similar menu
442 			 */
443 			inFillData->sequenceID++;
444 
445 			item=_xfdashboard_applications_menu_model_item_new();
446 			item->sequenceID=inFillData->sequenceID;
447 			if(inMenu) item->menuElement=GARCON_MENU_ELEMENT(g_object_ref(inMenu));
448 			if(inParentMenu) item->parentMenu=g_object_ref(inParentMenu);
449 			if(section) item->section=g_object_ref(section);
450 			if(title) item->title=g_strdup(title);
451 			if(description) item->description=g_strdup(description);
452 
453 			xfdashboard_model_append(XFDASHBOARD_MODEL(self), item, NULL);
454 
455 			/* Add menu to list of populated ones */
456 			inFillData->populatedMenus=g_slist_prepend(inFillData->populatedMenus, inMenu);
457 
458 			/* All menu items should be added to this newly created menu */
459 			menu=inMenu;
460 
461 			/* Find section of newly created menu to */
462 			section=_xfdashboard_applications_menu_model_find_section(self, menu, inFillData);
463 
464 			/* Release allocated resources */
465 			g_free(title);
466 			g_free(description);
467 		}
468 	}
469 
470 	/* Iterate through menu and add menu items and sub-menus */
471 	elements=garcon_menu_get_elements(inMenu);
472 	for(element=elements; element; element=g_list_next(element))
473 	{
474 		GarconMenuElement							*menuElement;
475 
476 		/* Get menu element from list */
477 		menuElement=GARCON_MENU_ELEMENT(element->data);
478 
479 		/* Check if menu element is visible */
480 		if(!menuElement || !garcon_menu_element_get_visible(menuElement)) continue;
481 
482 		/* If element is a menu call this function recursively */
483 		if(GARCON_IS_MENU(menuElement))
484 		{
485 			_xfdashboard_applications_menu_model_fill_model_collect_menu(self, GARCON_MENU(menuElement), menu, inFillData);
486 		}
487 
488 		/* Insert row into model if menu element is a menu item if it does not
489 		 * belong to root menu.
490 		 */
491 		if(GARCON_IS_MENU_ITEM(menuElement) &&
492 			menu!=priv->rootMenu)
493 		{
494 			gchar									*title;
495 			gchar									*description;
496 			const gchar								*temp;
497 
498 			/* To increase performance when sorting of filtering this model by title or description
499 			 * in a case-insensitive way store title and description in lower case.
500 			 */
501 			temp=garcon_menu_element_get_name(GARCON_MENU_ELEMENT(menuElement));
502 			if(temp) title=g_utf8_strdown(temp, -1);
503 				else title=NULL;
504 
505 			temp=garcon_menu_element_get_comment(GARCON_MENU_ELEMENT(menuElement));
506 			if(temp) description=g_utf8_strdown(temp, -1);
507 				else description=NULL;
508 
509 			/* Add menu item to model */
510 			inFillData->sequenceID++;
511 
512 			item=_xfdashboard_applications_menu_model_item_new();
513 			item->sequenceID=inFillData->sequenceID;
514 			if(menuElement) item->menuElement=g_object_ref(menuElement);
515 			if(menu) item->parentMenu=g_object_ref(menu);
516 			if(section) item->section=g_object_ref(section);
517 			if(title) item->title=g_strdup(title);
518 			if(description) item->description=g_strdup(description);
519 
520 			xfdashboard_model_append(XFDASHBOARD_MODEL(self), item, NULL);
521 
522 			/* Release allocated resources */
523 			g_free(title);
524 			g_free(description);
525 		}
526 	}
527 	g_list_free(elements);
528 
529 	/* Release allocated resources */
530 	g_object_unref(inMenu);
531 }
532 
_xfdashboard_applications_menu_model_fill_model(XfdashboardApplicationsMenuModel * self)533 static void _xfdashboard_applications_menu_model_fill_model(XfdashboardApplicationsMenuModel *self)
534 {
535 	XfdashboardApplicationsMenuModelPrivate		*priv;
536 	GarconMenuItemCache							*cache;
537 	XfdashboardApplicationsMenuModelFillData	fillData;
538 
539 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(self));
540 
541 	priv=self->priv;
542 
543 	/* Clear model data */
544 	_xfdashboard_applications_menu_model_clear(self);
545 
546 	/* Clear garcon's menu item cache otherwise some items will not be loaded
547 	 * if this is a reload of the model or a second(, third, ...) instance of model
548 	 */
549 	cache=garcon_menu_item_cache_get_default();
550 	garcon_menu_item_cache_invalidate(cache);
551 	g_object_unref(cache);
552 
553 	/* Load root menu */
554 	priv->rootMenu=xfdashboard_application_database_get_application_menu(priv->appDB);
555 
556 	/* Iterate through menus recursively to add them to model */
557 	fillData.sequenceID=0;
558 	fillData.populatedMenus=NULL;
559 	_xfdashboard_applications_menu_model_fill_model_collect_menu(self, priv->rootMenu, NULL, &fillData);
560 
561 	/* Emit signal */
562 	g_signal_emit(self, XfdashboardApplicationsMenuModelSignals[SIGNAL_LOADED], 0);
563 
564 	/* Release allocated resources at fill data structure */
565 	if(fillData.populatedMenus) g_slist_free(fillData.populatedMenus);
566 }
567 
568 /* Idle callback to fill model */
_xfdashboard_applications_menu_model_init_idle(gpointer inUserData)569 static gboolean _xfdashboard_applications_menu_model_init_idle(gpointer inUserData)
570 {
571 	_xfdashboard_applications_menu_model_fill_model(XFDASHBOARD_APPLICATIONS_MENU_MODEL(inUserData));
572 	return(G_SOURCE_REMOVE);
573 }
574 
575 
576 /* IMPLEMENTATION: GObject */
577 
578 /* Dispose this object */
_xfdashboard_applications_menu_model_dispose(GObject * inObject)579 static void _xfdashboard_applications_menu_model_dispose(GObject *inObject)
580 {
581 	XfdashboardApplicationsMenuModel			*self=XFDASHBOARD_APPLICATIONS_MENU_MODEL(inObject);
582 	XfdashboardApplicationsMenuModelPrivate		*priv=self->priv;
583 
584 	/* Release allocated resources */
585 	if(priv->rootMenu)
586 	{
587 		g_object_unref(priv->rootMenu);
588 		priv->rootMenu=NULL;
589 	}
590 
591 	if(priv->appDB)
592 	{
593 		if(priv->reloadRequiredSignalID)
594 		{
595 			g_signal_handler_disconnect(priv->appDB, priv->reloadRequiredSignalID);
596 			priv->reloadRequiredSignalID=0;
597 		}
598 
599 		g_object_unref(priv->appDB);
600 		priv->appDB=NULL;
601 	}
602 
603 	/* Call parent's class dispose method */
604 	G_OBJECT_CLASS(xfdashboard_applications_menu_model_parent_class)->dispose(inObject);
605 }
606 
607 /* Class initialization
608  * Override functions in parent classes and define properties
609  * and signals
610  */
xfdashboard_applications_menu_model_class_init(XfdashboardApplicationsMenuModelClass * klass)611 static void xfdashboard_applications_menu_model_class_init(XfdashboardApplicationsMenuModelClass *klass)
612 {
613 	GObjectClass			*gobjectClass=G_OBJECT_CLASS(klass);
614 
615 	gobjectClass->dispose=_xfdashboard_applications_menu_model_dispose;
616 
617 	/* Define signals */
618 	XfdashboardApplicationsMenuModelSignals[SIGNAL_LOADED]=
619 		g_signal_new("loaded",
620 						G_TYPE_FROM_CLASS(klass),
621 						G_SIGNAL_RUN_LAST,
622 						G_STRUCT_OFFSET(XfdashboardApplicationsMenuModelClass, loaded),
623 						NULL,
624 						NULL,
625 						g_cclosure_marshal_VOID__VOID,
626 						G_TYPE_NONE,
627 						0);
628 }
629 
630 /* Object initialization
631  * Create private structure and set up default values
632  */
xfdashboard_applications_menu_model_init(XfdashboardApplicationsMenuModel * self)633 static void xfdashboard_applications_menu_model_init(XfdashboardApplicationsMenuModel *self)
634 {
635 	XfdashboardApplicationsMenuModelPrivate	*priv;
636 
637 	priv=self->priv=xfdashboard_applications_menu_model_get_instance_private(self);
638 
639 	/* Set up default values */
640 	priv->rootMenu=NULL;
641 	priv->appDB=NULL;
642 	priv->reloadRequiredSignalID=0;
643 
644 	/* Get application database and connect signals */
645 	priv->appDB=xfdashboard_application_database_get_default();
646 	priv->reloadRequiredSignalID=g_signal_connect_swapped(priv->appDB,
647 															"menu-reload-required",
648 															G_CALLBACK(_xfdashboard_applications_menu_model_on_reload_required),
649 															self);
650 	/* Defer filling model */
651 	clutter_threads_add_idle(_xfdashboard_applications_menu_model_init_idle, self);
652 }
653 
654 
655 /* IMPLEMENTATION: Public API */
656 
657 /* Create a new instance of application menu model */
xfdashboard_applications_menu_model_new(void)658 XfdashboardModel* xfdashboard_applications_menu_model_new(void)
659 {
660 	GObject		*model;
661 
662 	/* Create instance */
663 	model=g_object_new(XFDASHBOARD_TYPE_APPLICATIONS_MENU_MODEL,
664 						"free-data-callback", _xfdashboard_applications_menu_model_item_free,
665 						NULL);
666 	if(!model) return(NULL);
667 
668 	/* Return new instance */
669 	return(XFDASHBOARD_MODEL(model));
670 }
671 
672 /* Get values from application menu model at requested iterator and columns */
xfdashboard_applications_menu_model_get(XfdashboardApplicationsMenuModel * self,XfdashboardModelIter * inIter,...)673 void xfdashboard_applications_menu_model_get(XfdashboardApplicationsMenuModel *self,
674 												XfdashboardModelIter *inIter,
675 												...)
676 {
677 	XfdashboardModel							*model;
678 	XfdashboardApplicationsMenuModelItem		*item;
679 	va_list										args;
680 	gint										column;
681 	gpointer									*storage;
682 
683 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(self));
684 	g_return_if_fail(XFDASHBOARD_IS_MODEL_ITER(inIter));
685 
686 	/* Check if iterator belongs to this model */
687 	model=xfdashboard_model_iter_get_model(inIter);
688 	if(!XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(model) ||
689 		XFDASHBOARD_APPLICATIONS_MENU_MODEL(model)!=self)
690 	{
691 		g_critical("Iterator does not belong to application menu model.");
692 		return;
693 	}
694 
695 	/* Get item from iterator */
696 	item=(XfdashboardApplicationsMenuModelItem*)xfdashboard_model_iter_get(inIter);
697 	g_assert(item);
698 
699 	/* Iterate through column index and pointer where to store value until
700 	 * until end of list (marked with -1) is reached.
701 	 */
702 	va_start(args, inIter);
703 
704 	column=va_arg(args, gint);
705 	while(column!=-1)
706 	{
707 		if(column<0 || column>=XFDASHBOARD_APPLICATIONS_MENU_MODEL_COLUMN_LAST)
708 		{
709 			g_warning("Invalid column number %d added to iter (remember to end your list of columns with a -1)",
710 						column);
711 			break;
712 		}
713 
714 		/* Get generic pointer to storage as it will be casted as necessary
715 		 * when determining which column is requested.
716 		 */
717 		storage=va_arg(args, gpointer*);
718 		if(!storage)
719 		{
720 			g_warning("No storage pointer provided to store value of column number %d",
721 						column);
722 			break;
723 		}
724 
725 		/* Check which column is requested and store value at pointer */
726 		switch(column)
727 		{
728 			case XFDASHBOARD_APPLICATIONS_MENU_MODEL_COLUMN_SEQUENCE_ID:
729 				*((gint*)storage)=item->sequenceID;
730 				break;
731 
732 			case XFDASHBOARD_APPLICATIONS_MENU_MODEL_COLUMN_MENU_ELEMENT:
733 				if(item->menuElement) *storage=g_object_ref(item->menuElement);
734 					else *storage=NULL;
735 				break;
736 
737 			case XFDASHBOARD_APPLICATIONS_MENU_MODEL_COLUMN_PARENT_MENU:
738 				if(item->parentMenu) *storage=g_object_ref(item->parentMenu);
739 					else *storage=NULL;
740 				break;
741 
742 			case XFDASHBOARD_APPLICATIONS_MENU_MODEL_COLUMN_SECTION:
743 				if(item->section) *storage=g_object_ref(item->section);
744 					else *storage=NULL;
745 				break;
746 
747 			case XFDASHBOARD_APPLICATIONS_MENU_MODEL_COLUMN_TITLE:
748 				if(item->title) *((gchar**)storage)=g_strdup(item->title);
749 					else *((gchar**)storage)=g_strdup("");
750 				break;
751 
752 			case XFDASHBOARD_APPLICATIONS_MENU_MODEL_COLUMN_DESCRIPTION:
753 				if(item->description) *((gchar**)storage)=g_strdup(item->description);
754 					else *((gchar**)storage)=g_strdup("");
755 				break;
756 
757 			default:
758 				g_assert_not_reached();
759 				break;
760 		}
761 
762 		/* Continue with next column and storage pointer */
763 		column=va_arg(args, gint);
764 	}
765 
766 	va_end(args);
767 }
768 
769 /* Filter menu items being a direct child item of requested menu */
xfdashboard_applications_menu_model_filter_by_menu(XfdashboardApplicationsMenuModel * self,GarconMenu * inMenu)770 void xfdashboard_applications_menu_model_filter_by_menu(XfdashboardApplicationsMenuModel *self,
771 														GarconMenu *inMenu)
772 {
773 	XfdashboardApplicationsMenuModelPrivate		*priv;
774 
775 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(self));
776 	g_return_if_fail(inMenu==NULL || GARCON_IS_MENU(inMenu));
777 
778 	priv=self->priv;
779 
780 	/* If menu is NULL filter root menu */
781 	if(inMenu==NULL) inMenu=priv->rootMenu;
782 
783 	/* Filter model data */
784 	xfdashboard_model_set_filter(XFDASHBOARD_MODEL(self),
785 									_xfdashboard_applications_menu_model_filter_by_menu,
786 									g_object_ref(inMenu),
787 									g_object_unref);
788 }
789 
790 /* Filter menu items being an indirect child item of requested section */
xfdashboard_applications_menu_model_filter_by_section(XfdashboardApplicationsMenuModel * self,GarconMenu * inSection)791 void xfdashboard_applications_menu_model_filter_by_section(XfdashboardApplicationsMenuModel *self,
792 															GarconMenu *inSection)
793 {
794 	XfdashboardApplicationsMenuModelPrivate		*priv;
795 
796 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_MENU_MODEL(self));
797 	g_return_if_fail(inSection==NULL || GARCON_IS_MENU(inSection));
798 
799 	priv=self->priv;
800 
801 	/* If requested section is NULL filter root menu */
802 	if(!inSection) inSection=priv->rootMenu;
803 
804 	/* Filter model data */
805 	if(inSection)
806 	{
807 		XFDASHBOARD_DEBUG(self, APPLICATIONS,
808 							"Filtering section '%s'",
809 							garcon_menu_element_get_name(GARCON_MENU_ELEMENT(inSection)));
810 		xfdashboard_model_set_filter(XFDASHBOARD_MODEL(self),
811 										_xfdashboard_applications_menu_model_filter_by_section,
812 										g_object_ref(inSection),
813 										g_object_unref);
814 	}
815 		else
816 		{
817 			XFDASHBOARD_DEBUG(self, APPLICATIONS, "Filtering root section because no section requested");
818 			xfdashboard_model_set_filter(XFDASHBOARD_MODEL(self),
819 											_xfdashboard_applications_menu_model_filter_empty,
820 											NULL,
821 											NULL);
822 		}
823 }
824