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