2
* This file is part of libslab.
4
* Copyright (c) 2006 Novell, Inc.
6
* Libslab is free software; you can redistribute it and/or modify it under the
7
* terms of the GNU Lesser General Public License as published by the Free
8
* Software Foundation; either version 2 of the License, or (at your option)
11
* Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
12
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
16
* You should have received a copy of the GNU Lesser General Public License
17
* along with libslab; if not, write to the Free Software Foundation, Inc., 51
18
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25
#include <libgnome/gnome-desktop-item.h>
27
#include <gdk/gdkkeysyms.h>
28
#include <sys/types.h>
33
#include <glib/gi18n-lib.h>
35
#include "app-shell.h"
36
#include "shell-window.h"
37
#include "app-resizer.h"
38
#include "slab-section.h"
39
#include "slab-gnome-util.h"
40
#include "search-bar.h"
42
#include "application-tile.h"
43
#include "themed-icon.h"
45
#define TILE_EXEC_NAME "Tile_desktop_exec_name"
46
#define SECONDS_IN_DAY 86400
47
#define EXIT_SHELL_ON_ACTION_START "exit_shell_on_action_start"
48
#define EXIT_SHELL_ON_ACTION_HELP "exit_shell_on_action_help"
49
#define EXIT_SHELL_ON_ACTION_ADD_REMOVE "exit_shell_on_action_add_remove"
50
#define EXIT_SHELL_ON_ACTION_UPGRADE_UNINSTALL "exit_shell_on_action_upgrade_uninstall"
51
#define NEW_APPS_FILE_KEY "new_apps_file_key"
53
static void create_application_category_sections (AppShellData * app_data);
54
static GtkWidget *create_filter_section (AppShellData * app_data, const gchar * title);
55
static GtkWidget *create_groups_section (AppShellData * app_data, const gchar * title);
56
static GtkWidget *create_actions_section (AppShellData * app_data, const gchar * title,
57
void (*actions_handler) (Tile *, TileEvent *, gpointer));
59
static void generate_category (const char * category, GMenuTreeDirectory * root_dir, AppShellData * app_data, gboolean recursive);
60
static void generate_launchers (GMenuTreeDirectory * root_dir, AppShellData * app_data,
61
CategoryData * cat_data, gboolean recursive);
62
static void generate_new_apps (AppShellData * app_data);
63
static void insert_launcher_into_category (CategoryData * cat_data, GnomeDesktopItem * desktop_item,
64
AppShellData * app_data);
66
static gboolean main_keypress_callback (GtkWidget * widget, GdkEventKey * event,
67
AppShellData * app_data);
68
static gboolean main_delete_callback (GtkWidget * widget, GdkEvent * event,
69
AppShellData * app_data);
70
static void application_launcher_clear_search_bar (AppShellData * app_data);
71
static void launch_selected_app (AppShellData * app_data);
72
static void generate_potential_apps (gpointer catdata, gpointer user_data);
74
static void relayout_shell (AppShellData * app_data);
75
static gboolean handle_filter_changed (NldSearchBar * search_bar, int context, const char *text,
77
static void handle_group_clicked (Tile * tile, TileEvent * event, gpointer user_data);
78
static void set_state (AppShellData * app_data, GtkWidget * widget);
79
static void populate_groups_section (AppShellData * app_data);
80
static void generate_filtered_lists (gpointer catdata, gpointer user_data);
81
static void show_no_results_message (AppShellData * app_data, GtkWidget * containing_vbox);
82
static void populate_application_category_sections (AppShellData * app_data,
83
GtkWidget * containing_vbox);
84
static void populate_application_category_section (AppShellData * app_data, SlabSection * section,
85
GList * launcher_list);
86
static void tile_activated_cb (Tile * tile, TileEvent * event, gpointer user_data);
87
static void handle_launcher_single_clicked (Tile * launcher, gpointer data);
88
static void handle_menu_action_performed (Tile * launcher, TileEvent * event, TileAction * action,
90
static gint application_launcher_compare (gconstpointer a, gconstpointer b);
91
static void gmenu_tree_changed_callback (GMenuTree * tree, gpointer user_data);
92
gboolean regenerate_categories (AppShellData * app_data);
95
hide_shell (AppShellData * app_data)
97
gtk_window_get_position (GTK_WINDOW (app_data->main_app),
98
&app_data->main_app_window_x, &app_data->main_app_window_y);
99
/* printf("x:%d, y:%d\n", app_data->main_app_window_x, app_data->main_app_window_y); */
100
/* clear the search bar now so reshowing is fast and flicker free - BNC#283186 */
101
application_launcher_clear_search_bar (app_data);
102
gtk_widget_hide (app_data->main_app);
106
show_shell (AppShellData * app_data)
108
gtk_widget_show_all (app_data->main_app);
109
if (!app_data->static_actions)
110
gtk_widget_hide_all (app_data->actions_section); /* don't show unless a launcher is selected */
112
if (app_data->main_app_window_shown_once)
113
gtk_window_move (GTK_WINDOW (app_data->main_app),
114
app_data->main_app_window_x, app_data->main_app_window_y);
116
/* if this is the first time shown, need to clear this handler */
118
shell_window_clear_resize_handler (SHELL_WINDOW (app_data->shell));
119
app_data->main_app_window_shown_once = TRUE;
123
create_main_window (AppShellData * app_data, const gchar * app_name, const gchar * title,
124
const gchar * window_icon, gint width, gint height, gboolean hidden)
126
GtkWidget *main_app = gtk_window_new (GTK_WINDOW_TOPLEVEL);
127
app_data->main_app = main_app;
128
gtk_widget_set_name (main_app, app_name);
129
gtk_window_set_title (GTK_WINDOW (main_app), title);
130
/* gtk_window_set_default_size(GTK_WINDOW(main_app), width, height); */
131
gtk_window_set_icon_name (GTK_WINDOW (main_app), window_icon);
132
gtk_container_add (GTK_CONTAINER (main_app), app_data->shell);
134
g_signal_connect (main_app, "delete-event", G_CALLBACK (main_delete_callback), app_data);
135
g_signal_connect (main_app, "key-press-event", G_CALLBACK (main_keypress_callback),
138
gtk_window_set_position (GTK_WINDOW (app_data->main_app), GTK_WIN_POS_CENTER);
140
show_shell (app_data);
146
generate_potential_apps (gpointer catdata, gpointer user_data)
148
GHashTable *app_hash = (GHashTable *) user_data;
149
CategoryData *data = (CategoryData *) catdata;
152
GList *launcher_list = data->filtered_launcher_list;
154
while (launcher_list)
156
g_object_get (launcher_list->data, "tile-uri", &uri, NULL);
157
/* eliminate dups of same app in multiple categories */
158
if (!g_hash_table_lookup (app_hash, uri))
159
g_hash_table_insert (app_hash, uri, launcher_list->data);
162
launcher_list = g_list_next (launcher_list);
167
return_first_entry (gpointer key, gpointer value, gpointer unused)
169
return TRUE; /*better way to pull an entry out ? */
173
launch_selected_app (AppShellData * app_data)
175
GHashTable *app_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
178
g_list_foreach (app_data->categories_list, generate_potential_apps, app_hash);
179
num_apps = g_hash_table_size (app_hash);
182
ApplicationTile *launcher =
183
APPLICATION_TILE (g_hash_table_find (app_hash, return_first_entry, NULL));
184
g_hash_table_destroy (app_hash);
185
handle_launcher_single_clicked (TILE (launcher), app_data);
189
g_hash_table_destroy (app_hash);
193
main_keypress_callback (GtkWidget * widget, GdkEventKey * event, AppShellData * app_data)
195
if (event->keyval == GDK_Return)
197
SlabSection *section = SLAB_SECTION (app_data->filter_section);
198
NldSearchBar *search_bar;
200
/* Make sure our implementation has not changed */
201
g_assert (NLD_IS_SEARCH_BAR (section->contents));
202
search_bar = NLD_SEARCH_BAR (section->contents);
203
if (nld_search_bar_has_focus (search_bar))
205
launch_selected_app (app_data);
210
/* quit on ESC or Ctl-W or Ctl-Q */
211
if (event->keyval == GDK_Escape ||
212
((event->keyval == GDK_w || event->keyval == GDK_W) && (event->state & GDK_CONTROL_MASK)) ||
213
((event->keyval == GDK_q || event->keyval == GDK_Q) && (event->state & GDK_CONTROL_MASK)))
215
if (app_data->exit_on_close)
218
hide_shell (app_data);
225
main_delete_callback (GtkWidget * widget, GdkEvent * event, AppShellData * app_data)
227
if (app_data->exit_on_close)
233
hide_shell (app_data);
234
return TRUE; /* stop the processing of this event */
238
layout_shell (AppShellData * app_data, const gchar * filter_title, const gchar * groups_title,
239
const gchar * actions_title, GSList * actions,
240
void (*actions_handler) (Tile *, TileEvent *, gpointer))
242
GtkWidget *filter_section;
243
GtkWidget *groups_section;
244
GtkWidget *actions_section;
246
GtkWidget *left_vbox;
247
GtkWidget *right_vbox;
251
GtkAdjustment *adjustment;
253
app_data->shell = shell_window_new (app_data);
254
app_data->static_actions = actions;
256
right_vbox = gtk_vbox_new (FALSE, CATEGORY_SPACING);
258
num_cols = SIZING_SCREEN_WIDTH_LARGE_NUMCOLS;
259
if (gdk_screen_width () <= SIZING_SCREEN_WIDTH_LARGE)
261
if (gdk_screen_width () <= SIZING_SCREEN_WIDTH_MEDIUM)
262
num_cols = SIZING_SCREEN_WIDTH_SMALL_NUMCOLS;
264
num_cols = SIZING_SCREEN_WIDTH_MEDIUM_NUMCOLS;
266
app_data->category_layout =
267
app_resizer_new (GTK_VBOX (right_vbox), num_cols, TRUE, app_data);
269
sw = gtk_scrolled_window_new (NULL, NULL);
270
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC,
271
GTK_POLICY_AUTOMATIC);
272
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
273
gtk_container_add (GTK_CONTAINER (sw), app_data->category_layout);
274
adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
275
g_object_set (adjustment, "step-increment", (double) 20, NULL);
277
create_application_category_sections (app_data);
278
populate_application_category_sections (app_data, right_vbox);
279
app_resizer_set_table_cache (APP_RESIZER (app_data->category_layout),
280
app_data->cached_tables_list);
282
gtk_container_set_focus_vadjustment (GTK_CONTAINER (right_vbox),
283
gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw)));
285
left_vbox = gtk_vbox_new (FALSE, 15);
287
filter_section = create_filter_section (app_data, filter_title);
288
app_data->filter_section = filter_section;
289
gtk_box_pack_start (GTK_BOX (left_vbox), filter_section, FALSE, FALSE, 0);
291
groups_section = create_groups_section (app_data, groups_title);
292
app_data->groups_section = groups_section;
293
populate_groups_section (app_data);
294
gtk_box_pack_start (GTK_BOX (left_vbox), groups_section, FALSE, FALSE, 0);
296
actions_section = create_actions_section (app_data, actions_title, actions_handler);
297
app_data->actions_section = actions_section;
298
gtk_box_pack_start (GTK_BOX (left_vbox), actions_section, FALSE, FALSE, 0);
300
shell_window_set_contents (SHELL_WINDOW (app_data->shell), left_vbox, sw);
304
relayout_shell_partial (gpointer user_data)
306
AppShellData *app_data = (AppShellData *) user_data;
307
GtkVBox *vbox = APP_RESIZER (app_data->category_layout)->child;
310
if (app_data->stop_incremental_relayout)
313
if (app_data->incremental_relayout_cat_list != NULL)
315
/* There are still categories to layout */
316
data = (CategoryData *) app_data->incremental_relayout_cat_list->data;
317
if (data->filtered_launcher_list != NULL)
319
populate_application_category_section (app_data, data->section,
320
data->filtered_launcher_list);
321
gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (data->section), TRUE, TRUE,
323
app_data->filtered_out_everything = FALSE;
326
app_data->incremental_relayout_cat_list =
327
g_list_next (app_data->incremental_relayout_cat_list);
331
/* We're done laying out the categories; finish up */
332
if (app_data->filtered_out_everything)
333
show_no_results_message (app_data, GTK_WIDGET (vbox));
335
app_resizer_set_table_cache (APP_RESIZER (app_data->category_layout),
336
app_data->cached_tables_list);
337
populate_groups_section (app_data);
339
gtk_widget_show_all (app_data->category_layout);
340
gdk_window_set_cursor (app_data->shell->window, NULL);
342
app_data->stop_incremental_relayout = TRUE;
347
relayout_shell_incremental (AppShellData * app_data)
349
GtkVBox *vbox = APP_RESIZER (app_data->category_layout)->child;
351
app_data->stop_incremental_relayout = FALSE;
352
app_data->filtered_out_everything = TRUE;
353
app_data->incremental_relayout_cat_list = app_data->categories_list;
355
if (app_data->cached_tables_list)
356
g_list_free (app_data->cached_tables_list);
357
app_data->cached_tables_list = NULL;
359
remove_container_entries (GTK_CONTAINER (vbox));
361
g_idle_add ((GSourceFunc) relayout_shell_partial, app_data);
365
relayout_shell (AppShellData * app_data)
367
GtkWidget *shell = app_data->shell;
368
GtkVBox *vbox = APP_RESIZER (app_data->category_layout)->child;
370
populate_application_category_sections (app_data, GTK_WIDGET (vbox));
371
app_resizer_set_table_cache (APP_RESIZER (app_data->category_layout),
372
app_data->cached_tables_list);
373
populate_groups_section (app_data);
375
gtk_widget_show_all (shell);
376
if (!app_data->static_actions && !app_data->last_clicked_launcher)
377
gtk_widget_hide_all (app_data->actions_section); /* don't show unless a launcher is selected */
381
create_actions_section (AppShellData * app_data, const gchar * title,
382
void (*actions_handler) (Tile *, TileEvent *, gpointer))
384
GtkWidget *section, *launcher;
390
g_assert (app_data != NULL);
392
section = slab_section_new (title, Style1);
393
g_object_ref (section);
395
vbox = gtk_vbox_new (FALSE, 0);
396
slab_section_set_contents (SLAB_SECTION (section), vbox);
398
if (app_data->static_actions)
400
for (actions = app_data->static_actions; actions; actions = actions->next)
404
action = (AppAction *) actions->data;
405
header = gtk_label_new (action->name);
406
gtk_misc_set_alignment (GTK_MISC (header), 0, 0.5);
407
launcher = nameplate_tile_new (NULL, NULL, header, NULL);
409
g_object_set_data (G_OBJECT (launcher), APP_ACTION_KEY, action->item);
410
g_signal_connect (launcher, "tile-activated", G_CALLBACK (actions_handler),
412
gtk_box_pack_start (GTK_BOX (vbox), launcher, FALSE, FALSE, 0);
414
a11y_cat = gtk_widget_get_accessible (GTK_WIDGET (launcher));
415
atk_object_set_name (a11y_cat, action->name);
423
create_groups_section (AppShellData * app_data, const gchar * title)
428
g_assert (app_data != NULL);
430
section = slab_section_new (title, Style1);
431
g_object_ref (section);
433
vbox = gtk_vbox_new (FALSE, 0);
434
slab_section_set_contents (SLAB_SECTION (section), vbox);
440
populate_groups_section (AppShellData * app_data)
442
SlabSection *section = SLAB_SECTION (app_data->groups_section);
446
/* Make sure our implementation has not changed and it's still a GtkVBox */
447
g_assert (GTK_IS_VBOX (section->contents));
449
vbox = GTK_VBOX (section->contents);
450
remove_container_entries (GTK_CONTAINER (vbox));
452
cat_list = app_data->categories_list;
455
CategoryData *data = (CategoryData *) cat_list->data;
456
if (NULL != data->filtered_launcher_list)
458
gtk_widget_set_state (GTK_WIDGET (data->group_launcher), GTK_STATE_NORMAL);
459
gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (data->group_launcher),
463
while (NULL != (cat_list = g_list_next (cat_list)));
467
handle_group_clicked (Tile * tile, TileEvent * event, gpointer user_data)
469
AppShellData *app_data = (AppShellData *) user_data;
470
GtkWidget *section = NULL;
473
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tile), GROUP_POSITION_NUMBER_KEY));
475
GList *cat_list = app_data->categories_list;
480
CategoryData *cat_data = (CategoryData *) cat_list->data;
482
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cat_data->group_launcher),
483
GROUP_POSITION_NUMBER_KEY));
484
if (pos == clicked_pos)
486
section = GTK_WIDGET (cat_data->section);
490
if (NULL != cat_data->filtered_launcher_list)
492
total += GTK_WIDGET (cat_data->section)->allocation.height +
496
while (NULL != (cat_list = g_list_next (cat_list)));
498
g_assert (section != NULL);
499
set_state (app_data, section);
501
app_resizer_set_vadjustment_value (app_data->category_layout, total);
505
set_state (AppShellData * app_data, GtkWidget * widget)
507
if (app_data->selected_group)
509
slab_section_set_selected (app_data->selected_group, FALSE);
510
app_data->selected_group = NULL;
515
app_data->selected_group = SLAB_SECTION (widget);
516
slab_section_set_selected (SLAB_SECTION (widget), TRUE);
518
gtk_widget_queue_draw (app_data->shell);
522
create_filter_section (AppShellData * app_data, const gchar * title)
526
GtkWidget *search_bar;
528
section = slab_section_new (title, Style1);
529
g_object_ref (section);
531
search_bar = nld_search_bar_new ();
532
nld_search_bar_set_search_timeout (NLD_SEARCH_BAR (search_bar), 0);
533
slab_section_set_contents (SLAB_SECTION (section), search_bar);
535
g_signal_connect (G_OBJECT (search_bar), "search", G_CALLBACK (handle_filter_changed),
542
handle_filter_changed_delayed (gpointer user_data)
544
AppShellData *app_data = (AppShellData *) user_data;
546
g_list_foreach (app_data->categories_list, generate_filtered_lists,
547
(gpointer) app_data->filter_string);
548
app_data->last_clicked_launcher = NULL;
550
/* showing the updates incremtally is very visually distracting. Much worse than just blanking until
551
the incremental work is done and then doing one show. It would be nice to optimize this though
552
somehow and not even show any change but the cursor change until all the work is done. But since
553
we do the work incrementally in an idle loop I don't know how else besides hiding to not show
556
/* gdk_window_freeze_updates(app_data->category_layout->window); */
557
gtk_widget_hide (app_data->category_layout);
558
app_data->busy_cursor =
559
gdk_cursor_new_for_display (gtk_widget_get_display (app_data->shell), GDK_WATCH);
560
gdk_window_set_cursor (app_data->shell->window, app_data->busy_cursor);
561
gdk_cursor_unref (app_data->busy_cursor);
563
set_state (app_data, NULL);
564
app_resizer_set_vadjustment_value (app_data->category_layout, 0);
566
relayout_shell_incremental (app_data);
568
app_data->filter_changed_timeout = 0;
573
handle_filter_changed (NldSearchBar * search_bar, int context, const char *text, gpointer data)
575
AppShellData *app_data;
577
app_data = (AppShellData *) data;
579
if (app_data->filter_string)
580
g_free (app_data->filter_string);
581
app_data->filter_string = g_strdup (text);
583
if (app_data->filter_changed_timeout)
584
g_source_remove (app_data->filter_changed_timeout);
586
app_data->filter_changed_timeout =
587
g_timeout_add (75, handle_filter_changed_delayed, app_data);
588
app_data->stop_incremental_relayout = TRUE;
594
generate_filtered_lists (gpointer catdata, gpointer user_data)
596
CategoryData *data = (CategoryData *) catdata;
598
/* Fixme - everywhere you use ascii you need to fix up for multibyte */
599
gchar *filter_string = g_ascii_strdown (user_data, -1);
600
gchar *temp1, *temp2;
601
GList *launcher_list = data->launcher_list;
603
g_list_free (data->filtered_launcher_list);
604
data->filtered_launcher_list = NULL;
608
ApplicationTile *launcher = APPLICATION_TILE (launcher_list->data);
609
const gchar *filename;
614
/* Since the filter may remove this entry from the
615
container it will not get a mouse out event */
616
gtk_widget_set_state (GTK_WIDGET (launcher), GTK_STATE_NORMAL);
617
filename = g_object_get_data (G_OBJECT (launcher), TILE_EXEC_NAME); /* do I need to free this */
619
temp1 = g_ascii_strdown (launcher->name, -1);
620
if (launcher->description)
621
temp2 = g_ascii_strdown (launcher->description, -1);
622
if (g_strrstr (temp1, filter_string) || (launcher->description
623
&& g_strrstr (temp2, filter_string))
624
|| g_strrstr (filename, filter_string))
626
data->filtered_launcher_list =
627
g_list_append (data->filtered_launcher_list, launcher);
634
while (NULL != (launcher_list = g_list_next (launcher_list)));
635
g_free (filter_string);
639
delete_old_data (AppShellData * app_data)
644
g_assert (app_data != NULL);
645
g_assert (app_data->categories_list != NULL);
647
cat_list = app_data->categories_list;
651
CategoryData *data = (CategoryData *) cat_list->data;
652
gtk_widget_destroy (GTK_WIDGET (data->section));
653
gtk_widget_destroy (GTK_WIDGET (data->group_launcher));
654
g_object_unref (data->section);
655
g_object_unref (data->group_launcher);
656
g_free (data->category);
658
for (temp = data->launcher_list; temp; temp = g_list_next (temp))
660
g_free (g_object_get_data (G_OBJECT (temp->data), TILE_EXEC_NAME));
661
g_object_unref (temp->data);
664
g_list_free (data->launcher_list);
665
g_list_free (data->filtered_launcher_list);
668
while (NULL != (cat_list = g_list_next (cat_list)));
670
g_list_free (app_data->categories_list);
671
app_data->categories_list = NULL;
672
app_data->selected_group = NULL;
676
create_application_category_sections (AppShellData * app_data)
682
g_assert (app_data != NULL);
683
g_assert (app_data->categories_list != NULL); /* Fixme - pop up a dialog box and then close */
685
cat_list = app_data->categories_list;
689
CategoryData *data = (CategoryData *) cat_list->data;
690
GtkWidget *header = gtk_label_new (data->category);
695
gtk_misc_set_alignment (GTK_MISC (header), 0, 0.5);
696
data->group_launcher = TILE (nameplate_tile_new (NULL, NULL, header, NULL));
697
g_object_ref (data->group_launcher);
699
g_object_set_data (G_OBJECT (data->group_launcher), GROUP_POSITION_NUMBER_KEY,
700
GINT_TO_POINTER (pos));
702
g_signal_connect (data->group_launcher, "tile-activated",
703
G_CALLBACK (handle_group_clicked), app_data);
704
a11y_cat = gtk_widget_get_accessible (GTK_WIDGET (data->group_launcher));
705
atk_object_set_name (a11y_cat, data->category);
707
markup = g_markup_printf_escaped ("<span size=\"x-large\" weight=\"bold\">%s</span>",
709
data->section = SLAB_SECTION (slab_section_new_with_markup (markup, Style2));
711
/* as we filter these will be added/removed from parent container and we dont want them destroyed */
712
g_object_ref (data->section);
715
hbox = gtk_hbox_new (FALSE, 0);
716
table = gtk_table_new (0, 0, TRUE);
717
gtk_table_set_col_spacings (GTK_TABLE (table), 5);
718
gtk_table_set_row_spacings (GTK_TABLE (table), 5);
719
gtk_box_pack_start (GTK_BOX (hbox), table, FALSE, FALSE, 15);
720
slab_section_set_contents (SLAB_SECTION (data->section), hbox);
722
while (NULL != (cat_list = g_list_next (cat_list)));
726
show_no_results_message (AppShellData * app_data, GtkWidget * containing_vbox)
732
if (!app_data->filtered_out_everything_widget)
738
app_data->filtered_out_everything_widget = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
739
g_object_ref (app_data->filtered_out_everything_widget);
741
hbox = gtk_hbox_new (FALSE, 0);
742
image = themed_icon_new ("face-surprise", GTK_ICON_SIZE_DIALOG);
743
gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
745
label = gtk_label_new (NULL);
746
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
747
gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 15);
748
app_data->filtered_out_everything_widget_label = GTK_LABEL (label);
750
gtk_container_add (GTK_CONTAINER (app_data->filtered_out_everything_widget), hbox);
753
str1 = g_markup_printf_escaped ("<b>%s</b>", app_data->filter_string);
754
str2 = g_strdup_printf (_("Your filter \"%s\" does not match any items."), str1);
755
markup = g_strdup_printf ("<span size=\"large\"><b>%s</b></span>\n\n%s",
756
_("No matches found."), str2);
757
gtk_label_set_text (app_data->filtered_out_everything_widget_label, markup);
758
gtk_label_set_use_markup (app_data->filtered_out_everything_widget_label, TRUE);
759
gtk_box_pack_start (GTK_BOX (containing_vbox), app_data->filtered_out_everything_widget,
767
populate_application_category_sections (AppShellData * app_data, GtkWidget * containing_vbox)
769
GList *cat_list = app_data->categories_list;
770
gboolean filtered_out_everything = TRUE;
771
if (app_data->cached_tables_list)
772
g_list_free (app_data->cached_tables_list);
773
app_data->cached_tables_list = NULL;
775
remove_container_entries (GTK_CONTAINER (containing_vbox));
778
CategoryData *data = (CategoryData *) cat_list->data;
779
if (NULL != data->filtered_launcher_list)
781
populate_application_category_section (app_data, data->section,
782
data->filtered_launcher_list);
783
gtk_box_pack_start (GTK_BOX (containing_vbox), GTK_WIDGET (data->section),
785
filtered_out_everything = FALSE;
788
while (NULL != (cat_list = g_list_next (cat_list)));
790
if (TRUE == filtered_out_everything)
791
show_no_results_message (app_data, containing_vbox);
795
populate_application_category_section (AppShellData * app_data, SlabSection * section,
796
GList * launcher_list)
802
g_assert (GTK_IS_HBOX (section->contents));
803
hbox = GTK_WIDGET (section->contents);
805
children = gtk_container_get_children (GTK_CONTAINER (hbox));
806
table = children->data;
807
g_list_free (children);
809
/* Make sure our implementation has not changed and it's still a GtkTable */
810
g_assert (GTK_IS_TABLE (table));
812
app_data->cached_tables_list = g_list_append (app_data->cached_tables_list, table);
814
app_resizer_layout_table_default (APP_RESIZER (app_data->category_layout), table,
820
regenerate_categories (AppShellData * app_data)
822
delete_old_data (app_data);
823
generate_categories (app_data);
824
create_application_category_sections (app_data);
825
relayout_shell (app_data);
827
return FALSE; /* remove this function from the list */
831
gmenu_tree_changed_callback (GMenuTree * old_tree, gpointer user_data)
834
This method only gets called on the first change (gmenu appears to ignore subsequent) until
835
we reget the root dir which we can't do in this method because if we do for some reason this
836
method then gets called multiple times for one actual change. This actually is okay because
837
it's probably a good idea to wait a couple seconds to regenerate the categories in case there
838
are multiple quick changes being made, no sense regenerating multiple times.
840
g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 3000, (GSourceFunc) regenerate_categories,
845
appshelldata_new (const gchar * menu_name, NewAppConfig * new_apps, const gchar * gconf_keys_prefix,
846
GtkIconSize icon_size, gboolean show_tile_generic_name, gboolean exit_on_close)
848
AppShellData *app_data = g_new0 (AppShellData, 1);
849
app_data->gconf_prefix = gconf_keys_prefix;
850
app_data->new_apps = new_apps;
851
app_data->menu_name = menu_name;
852
app_data->icon_size = icon_size;
853
app_data->stop_incremental_relayout = TRUE;
854
app_data->show_tile_generic_name = show_tile_generic_name;
855
app_data->exit_on_close = exit_on_close;
860
generate_categories (AppShellData * app_data)
862
GMenuTreeDirectory *root_dir;
863
GSList *contents, *l;
864
gboolean need_misc = FALSE;
868
app_data->tree = gmenu_tree_lookup (app_data->menu_name, GMENU_TREE_FLAGS_NONE);
869
gmenu_tree_add_monitor (app_data->tree, gmenu_tree_changed_callback, app_data);
871
root_dir = gmenu_tree_get_root_directory (app_data->tree);
873
contents = gmenu_tree_directory_get_contents (root_dir);
876
if (!root_dir || !contents)
878
GtkWidget *dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
879
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Failure loading - %s",
880
app_data->menu_name);
881
gtk_dialog_run (GTK_DIALOG (dialog));
882
gtk_widget_destroy (dialog);
883
exit (1); /* Fixme - is there a GNOME/GTK way to do this. */
886
for (l = contents; l; l = l->next)
888
const char *category;
889
GMenuTreeItem *item = l->data;
891
switch (gmenu_tree_item_get_type (item))
893
case GMENU_TREE_ITEM_DIRECTORY:
894
category = gmenu_tree_directory_get_name ((GMenuTreeDirectory*)item);
895
generate_category(category, (GMenuTreeDirectory*)item, app_data, TRUE);
897
case GMENU_TREE_ITEM_ENTRY:
904
gmenu_tree_item_unref (item);
906
g_slist_free (contents);
909
generate_category (_("Other"), root_dir, app_data, FALSE);
913
g_hash_table_destroy (app_data->hash);
914
app_data->hash = NULL;
917
gmenu_tree_item_unref (root_dir);
919
if (app_data->new_apps && (app_data->new_apps->max_items > 0))
920
generate_new_apps (app_data);
924
generate_category (const char * category, GMenuTreeDirectory * root_dir, AppShellData * app_data, gboolean recursive)
927
/* This is not needed. GMenu already returns an ordered, non duplicate list
930
g_list_find_custom (app_data->categories_list, category,
931
category_name_compare);
935
data = g_new0 (CategoryData, 1);
936
data->category = g_strdup (category);
937
app_data->categories_list =
938
/* use the gmenu order instead of alphabetical */
939
g_list_append (app_data->categories_list, data);
940
/* g_list_insert_sorted (app_data->categories_list, data, category_data_compare); */
945
data = list_entry->data;
949
if (app_data->hash) /* used to eliminate dups on a per category basis. */
950
g_hash_table_destroy (app_data->hash);
951
app_data->hash = g_hash_table_new (g_str_hash, g_str_equal);
952
generate_launchers (root_dir, app_data, data, recursive);
956
check_specific_apps_hack (GnomeDesktopItem * item)
958
static const gchar *COMMAND_LINE_LOCKDOWN_GCONF_KEY =
959
"/desktop/gnome/lockdown/disable_command_line";
960
static const gchar *COMMAND_LINE_LOCKDOWN_DESKTOP_CATEGORY = "TerminalEmulator";
961
static gboolean got_lockdown_value = FALSE;
962
static gboolean command_line_lockdown;
967
if (!got_lockdown_value)
969
got_lockdown_value = TRUE;
970
command_line_lockdown = get_slab_gconf_bool (COMMAND_LINE_LOCKDOWN_GCONF_KEY);
973
/* This seems like an ugly hack but it's the way it's currently done in the old control center */
974
exec = gnome_desktop_item_get_string (item, GNOME_DESKTOP_ITEM_EXEC);
976
/* discard xscreensaver if gnome-screensaver is installed */
977
if ((exec && !strcmp (exec, "xscreensaver-demo"))
978
&& (path = g_find_program_in_path ("gnome-screensaver-preferences")))
984
/* discard gnome-keyring-manager if CASA is installed */
985
if ((exec && !strcmp (exec, "gnome-keyring-manager"))
986
&& (path = g_find_program_in_path ("CASAManager.sh")))
992
/* discard terminals if lockdown key is set */
993
if (command_line_lockdown)
995
const gchar *categories =
996
gnome_desktop_item_get_string (item, GNOME_DESKTOP_ITEM_CATEGORIES);
997
if (g_strrstr (categories, COMMAND_LINE_LOCKDOWN_DESKTOP_CATEGORY))
999
/* printf ("eliminating %s\n", gnome_desktop_item_get_location (item)); */
1008
generate_launchers (GMenuTreeDirectory * root_dir, AppShellData * app_data, CategoryData * cat_data, gboolean recursive)
1010
GnomeDesktopItem *desktop_item;
1011
const gchar *desktop_file;
1012
GSList *contents, *l;
1014
contents = gmenu_tree_directory_get_contents (root_dir);
1015
for (l = contents; l; l = l->next)
1017
switch (gmenu_tree_item_get_type (l->data))
1019
case GMENU_TREE_ITEM_DIRECTORY:
1020
/* g_message ("Found sub-category %s", gmenu_tree_directory_get_name (l->data)); */
1022
generate_launchers (l->data, app_data, cat_data, TRUE);
1024
case GMENU_TREE_ITEM_ENTRY:
1025
/* g_message ("Found item name is:%s", gmenu_tree_entry_get_name (l->data)); */
1026
desktop_file = gmenu_tree_entry_get_desktop_file_path (l->data);
1029
if (g_hash_table_lookup (app_data->hash, desktop_file))
1031
break; /* duplicate */
1033
/* Fixme - make sure it's safe to store this without duping it. As far as I can tell it is
1034
safe as long as I don't hang on to this anylonger than I hang on to the GMenuTreeEntry*
1035
which brings up another point - am I supposed to free these or does freeing the top level recurse
1037
g_hash_table_insert (app_data->hash, (gpointer) desktop_file,
1038
(gpointer) desktop_file);
1040
desktop_item = gnome_desktop_item_new_from_file (desktop_file, 0, NULL);
1043
g_critical ("Failure - gnome_desktop_item_new_from_file(%s)",
1047
if (!check_specific_apps_hack (desktop_item))
1048
insert_launcher_into_category (cat_data, desktop_item, app_data);
1049
gnome_desktop_item_unref (desktop_item);
1055
gmenu_tree_item_unref (l->data);
1057
g_slist_free (contents);
1061
generate_new_apps (AppShellData * app_data)
1063
GHashTable *all_apps_cache = NULL;
1065
GError *error = NULL;
1066
gchar *separator = "\n";
1070
gchar *all_apps_file_name;
1071
gchar **all_apps_split;
1073
gboolean got_new_apps;
1074
CategoryData *new_apps_category = NULL;
1075
GList *categories, *launchers;
1076
GHashTable *new_apps_dups;
1078
gconf_key = g_strdup_printf ("%s%s", app_data->gconf_prefix, NEW_APPS_FILE_KEY);
1079
basename = get_slab_gconf_string (gconf_key);
1083
g_warning ("Failure getting gconf key NEW_APPS_FILE_KEY");
1087
all_apps_file_name = g_build_filename (g_get_home_dir (), basename, NULL);
1090
if (!g_file_get_contents (all_apps_file_name, &all_apps, NULL, &error))
1092
/* If file does not exist, this is the first time this user has run this, create the baseline file */
1093
GList *categories, *launchers;
1097
g_error_free (error);
1100
/* best initial size determined by running on a couple different platforms */
1101
gstr = g_string_sized_new (10000);
1103
for (categories = app_data->categories_list; categories; categories = categories->next)
1105
CategoryData *data = categories->data;
1106
for (launchers = data->launcher_list; launchers; launchers = launchers->next)
1108
Tile *tile = TILE (launchers->data);
1109
GnomeDesktopItem *item =
1110
application_tile_get_desktop_item (APPLICATION_TILE (tile));
1111
const gchar *uri = gnome_desktop_item_get_location (item);
1112
g_string_append (gstr, uri);
1113
g_string_append (gstr, separator);
1117
dirname = g_path_get_dirname (all_apps_file_name);
1118
g_mkdir_with_parents (dirname, 0700); /* creates if does not exist */
1121
if (!g_file_set_contents (all_apps_file_name, gstr->str, -1, &error))
1122
g_warning ("Error setting all apps file:%s\n", error->message);
1124
g_string_free (gstr, TRUE);
1125
g_free (all_apps_file_name);
1129
all_apps_cache = g_hash_table_new (g_str_hash, g_str_equal);
1130
all_apps_split = g_strsplit (all_apps, separator, -1);
1131
for (x = 0; all_apps_split[x]; x++)
1133
g_hash_table_insert (all_apps_cache, all_apps_split[x], all_apps_split[x]);
1136
got_new_apps = FALSE;
1137
new_apps_dups = g_hash_table_new (g_str_hash, g_str_equal);
1138
for (categories = app_data->categories_list; categories; categories = categories->next)
1140
CategoryData *cat_data = categories->data;
1141
for (launchers = cat_data->launcher_list; launchers; launchers = launchers->next)
1143
Tile *tile = TILE (launchers->data);
1144
GnomeDesktopItem *item =
1145
application_tile_get_desktop_item (APPLICATION_TILE (tile));
1146
const gchar *uri = gnome_desktop_item_get_location (item);
1147
if (!g_hash_table_lookup (all_apps_cache, uri))
1153
if (g_hash_table_lookup (new_apps_dups, uri))
1155
/* if a desktop file is in 2 or more top level categories, only show it once */
1156
/* printf("Discarding Newapp duplicate:%s\n", uri); */
1159
g_hash_table_insert (new_apps_dups, (gpointer) uri, (gpointer) uri);
1163
new_apps_category = g_new0 (CategoryData, 1);
1164
new_apps_category->category =
1165
g_strdup (app_data->new_apps->name);
1166
app_data->new_apps->garray =
1167
g_array_sized_new (FALSE, TRUE,
1168
sizeof (NewAppData *),
1169
app_data->new_apps->max_items);
1171
/* should not need this, but a bug in glib does not actually clear the elements until you call this method */
1172
g_array_set_size (app_data->new_apps->garray, app_data->new_apps->max_items);
1173
got_new_apps = TRUE;
1176
file = g_file_new_for_uri (uri);
1177
info = g_file_query_info (file,
1178
G_FILE_ATTRIBUTE_TIME_MODIFIED,
1183
g_object_unref (file);
1184
g_warning ("Cant get vfs info for %s\n", uri);
1187
filetime = (long) g_file_info_get_attribute_uint64 (info,
1188
G_FILE_ATTRIBUTE_TIME_MODIFIED);
1189
g_object_unref (info);
1190
g_object_unref (file);
1192
for (x = 0; x < app_data->new_apps->max_items; x++)
1194
NewAppData *temp_data = (NewAppData *)
1195
g_array_index (app_data->new_apps->garray, NewAppData *, x);
1196
if (!temp_data || filetime > temp_data->time) /* if this slot is empty or we are newer than this slot */
1198
NewAppData *temp = g_new0 (NewAppData, 1);
1199
temp->time = filetime;
1201
g_array_insert_val (app_data->new_apps->garray, x,
1209
g_hash_table_destroy (new_apps_dups);
1210
g_hash_table_destroy (all_apps_cache);
1214
for (x = 0; x < app_data->new_apps->max_items; x++)
1217
(NewAppData *) g_array_index (app_data->new_apps->garray,
1221
insert_launcher_into_category (new_apps_category, data->item,
1228
app_data->categories_list =
1229
g_list_prepend (app_data->categories_list, new_apps_category);
1231
g_array_free (app_data->new_apps->garray, TRUE);
1234
g_free (all_apps_file_name);
1235
g_strfreev (all_apps_split);
1239
insert_launcher_into_category (CategoryData * cat_data, GnomeDesktopItem * desktop_item,
1240
AppShellData * app_data)
1242
GtkWidget *launcher;
1243
static GtkSizeGroup *icon_group = NULL;
1247
GtkWidget *tile_icon;
1250
icon_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
1253
application_tile_new_full (gnome_desktop_item_get_location (desktop_item),
1254
app_data->icon_size, app_data->show_tile_generic_name, app_data->gconf_prefix);
1255
gtk_widget_set_size_request (launcher, SIZING_TILE_WIDTH, -1);
1258
g_strdup (gnome_desktop_item_get_string (desktop_item, GNOME_DESKTOP_ITEM_EXEC));
1259
g_strdelimit (filepath, " ", '\0'); /* just want the file name - no args or replacements */
1260
filename = g_strrstr (filepath, "/");
1262
g_stpcpy (filepath, filename + 1);
1263
filename = g_ascii_strdown (filepath, -1);
1265
g_object_set_data (G_OBJECT (launcher), TILE_EXEC_NAME, filename);
1267
tile_icon = NAMEPLATE_TILE (launcher)->image;
1268
gtk_size_group_add_widget (icon_group, tile_icon);
1270
g_signal_connect (launcher, "tile-activated", G_CALLBACK (tile_activated_cb), app_data);
1272
/* Note that this will handle the case of the action being launched via the side panel as
1273
well as directly from the context menu of an individual launcher, because they both
1274
funnel through tile_button_action_activate.
1276
g_signal_connect (launcher, "tile-action-triggered",
1277
G_CALLBACK (handle_menu_action_performed), app_data);
1279
/* These will be inserted/removed from tables as the filter changes and we dont want them */
1280
/* destroyed when they are removed */
1281
g_object_ref (launcher);
1283
/* use alphabetical order instead of the gmenu order. We group all sub items in each top level
1284
category together, ignoring sub menus, so we also ignore sub menu layout hints */
1285
cat_data->launcher_list =
1286
/* g_list_insert (cat_data->launcher_list, launcher, -1); */
1287
g_list_insert_sorted (cat_data->launcher_list, launcher, application_launcher_compare);
1288
cat_data->filtered_launcher_list =
1289
/* g_list_insert (cat_data->filtered_launcher_list, launcher, -1); */
1290
g_list_insert_sorted (cat_data->filtered_launcher_list, launcher, application_launcher_compare);
1294
application_launcher_compare (gconstpointer a, gconstpointer b)
1296
ApplicationTile *launcher1 = APPLICATION_TILE (a);
1297
ApplicationTile *launcher2 = APPLICATION_TILE (b);
1299
gchar *val1 = launcher1->name;
1300
gchar *val2 = launcher2->name;
1302
if (val1 == NULL || val2 == NULL)
1304
g_assert_not_reached ();
1306
return g_ascii_strcasecmp (val1, val2);
1310
application_launcher_clear_search_bar (AppShellData * app_data)
1312
SlabSection *section = SLAB_SECTION (app_data->filter_section);
1313
NldSearchBar *search_bar;
1314
g_assert (NLD_IS_SEARCH_BAR (section->contents));
1315
search_bar = NLD_SEARCH_BAR (section->contents);
1316
nld_search_bar_set_text (search_bar, "", TRUE);
1321
category_name_compare (gconstpointer a, gconstpointer b)
1323
CategoryData *data = (CategoryData *) a;
1324
const gchar *category = b;
1326
if (category == NULL || data->category == NULL)
1328
g_assert_not_reached ();
1330
return g_ascii_strcasecmp (category, data->category);
1335
tile_activated_cb (Tile * tile, TileEvent * event, gpointer user_data)
1337
switch (event->type)
1339
case TILE_EVENT_ACTIVATED_SINGLE_CLICK:
1340
case TILE_EVENT_ACTIVATED_KEYBOARD:
1341
handle_launcher_single_clicked (tile, user_data);
1350
handle_launcher_single_clicked (Tile * launcher, gpointer data)
1352
AppShellData *app_data = (AppShellData *) data;
1355
tile_trigger_action (launcher, launcher->actions[APPLICATION_TILE_ACTION_START]);
1357
gconf_key = g_strdup_printf ("%s%s", app_data->gconf_prefix, EXIT_SHELL_ON_ACTION_START);
1358
if (get_slab_gconf_bool (gconf_key))
1360
if (app_data->exit_on_close)
1363
hide_shell (app_data);
1369
handle_menu_action_performed (Tile * launcher, TileEvent * event, TileAction * action,
1372
AppShellData *app_data = (AppShellData *) data;
1376
if (action == launcher->actions[APPLICATION_TILE_ACTION_START])
1378
temp = g_strdup_printf ("%s%s", app_data->gconf_prefix, EXIT_SHELL_ON_ACTION_START);
1381
else if (action == launcher->actions[APPLICATION_TILE_ACTION_HELP])
1383
temp = g_strdup_printf ("%s%s", app_data->gconf_prefix, EXIT_SHELL_ON_ACTION_HELP);
1386
else if (action == launcher->actions[APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU]
1387
|| action == launcher->actions[APPLICATION_TILE_ACTION_UPDATE_STARTUP])
1389
temp = g_strdup_printf ("%s%s", app_data->gconf_prefix,
1390
EXIT_SHELL_ON_ACTION_ADD_REMOVE);
1393
else if (action == launcher->actions[APPLICATION_TILE_ACTION_UPGRADE_PACKAGE]
1394
|| action == launcher->actions[APPLICATION_TILE_ACTION_UNINSTALL_PACKAGE])
1396
temp = g_strdup_printf ("%s%s", app_data->gconf_prefix,
1397
EXIT_SHELL_ON_ACTION_UPGRADE_UNINSTALL);
1402
if (get_slab_gconf_bool (temp))
1404
if (app_data->exit_on_close)
1407
hide_shell (app_data);
1412
g_warning ("Unknown Action");