summaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorzhuyaliang <[email protected]>2023-10-25 21:43:03 +0800
committerraveit65 <[email protected]>2023-11-05 12:00:50 +0100
commitc2e94e1925f3c7acfa3fd3352dc8fdafe6375adc (patch)
tree3ba609bf57b7bb3eab18d35d78bd1dc86773fba6 /shell
parent23fc7b675ebac7a9b02257a31203974d72f584b1 (diff)
downloadmate-control-center-c2e94e1925f3c7acfa3fd3352dc8fdafe6375adc.tar.bz2
mate-control-center-c2e94e1925f3c7acfa3fd3352dc8fdafe6375adc.tar.xz
Remove libslab library
libslab is only used in mate-c-c and there is no need to provide a library
Diffstat (limited to 'shell')
-rw-r--r--shell/Makefile.am55
-rw-r--r--shell/app-resizer.c321
-rw-r--r--shell/app-resizer.h74
-rw-r--r--shell/app-shell.c1448
-rw-r--r--shell/app-shell.h142
-rw-r--r--shell/application-tile.c764
-rw-r--r--shell/application-tile.h67
-rw-r--r--shell/bookmark-agent.c1244
-rw-r--r--shell/bookmark-agent.h98
-rw-r--r--shell/control-center.c2
-rw-r--r--shell/double-click-detector.c87
-rw-r--r--shell/double-click-detector.h58
-rw-r--r--shell/libslab-utils.c70
-rw-r--r--shell/libslab-utils.h14
-rw-r--r--shell/mate-utils.c82
-rw-r--r--shell/mate-utils.h34
-rw-r--r--shell/nameplate-tile.c274
-rw-r--r--shell/nameplate-tile.h57
-rw-r--r--shell/nld-marshal.list1
-rw-r--r--shell/search-bar.c238
-rw-r--r--shell/search-bar.h66
-rw-r--r--shell/shell-window.c83
-rw-r--r--shell/shell-window.h65
-rw-r--r--shell/slab-mate-util.c160
-rw-r--r--shell/slab-mate-util.h39
-rw-r--r--shell/slab-section.c233
-rw-r--r--shell/slab-section.h72
-rw-r--r--shell/slab.h40
-rw-r--r--shell/themed-icon.c159
-rw-r--r--shell/themed-icon.h54
-rw-r--r--shell/tile-action.c109
-rw-r--r--shell/tile.c529
-rw-r--r--shell/tile.h127
33 files changed, 6857 insertions, 9 deletions
diff --git a/shell/Makefile.am b/shell/Makefile.am
index d3e4d8bd..5f0b5f7d 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -1,21 +1,48 @@
-REAL_LIBSLAB_CFLAGS = -I$(top_srcdir)/libslab
-REAL_LIBSLAB_LIBS = $(top_builddir)/libslab/libmate-slab.la
-
AM_CPPFLAGS = \
-I$(top_srcdir) \
$(WARN_CFLAGS) \
- $(REAL_LIBSLAB_CFLAGS) \
$(MATECC_SHELL_CFLAGS) \
-DMATELOCALEDIR="\"$(datadir)/locale\""
bin_PROGRAMS = mate-control-center
mate_control_center_SOURCES = \
- control-center.c
+ $(MARSHAL_GENERATED) \
+ app-resizer.c \
+ app-shell.c \
+ application-tile.c \
+ bookmark-agent.c \
+ control-center.c \
+ double-click-detector.c \
+ mate-utils.c \
+ libslab-utils.c \
+ nameplate-tile.c \
+ search-bar.c \
+ shell-window.c \
+ slab-mate-util.c \
+ slab-section.c \
+ themed-icon.c \
+ themed-icon.h \
+ tile-action.c \
+ tile.c \
+ application-tile.h \
+ app-resizer.h \
+ app-shell.h \
+ bookmark-agent.h \
+ double-click-detector.h \
+ libslab-utils.h \
+ mate-utils.h \
+ nameplate-tile.h \
+ search-bar.h \
+ shell-window.h \
+ slab.h \
+ slab-mate-util.h \
+ slab-section.h \
+ themed-icon.h \
+ tile.h
mate_control_center_LDADD = \
- $(MATECC_SHELL_LIBS) \
- $(REAL_LIBSLAB_LIBS)
+ $(MATECC_SHELL_LIBS)
sysdir = $(datadir)/applications
sys_in_files = matecc.desktop.in
@@ -37,10 +64,22 @@ else
$(AM_V_GEN) sed '/^# Translators/d' < $< > $@
endif
+MARSHAL_GENERATED = nld-marshal.c nld-marshal.h
+
+nld-marshal.h: nld-marshal.list
+ ( @GLIB_GENMARSHAL@ --prefix=nld_marshal $(srcdir)/nld-marshal.list --header > nld-marshal.tmp \
+ && mv nld-marshal.tmp nld-marshal.h ) \
+ || ( rm -f nld-marshal.tmp && exit 1 )
+
+nld-marshal.c: nld-marshal.h
+ ( (echo '#include "nld-marshal.h"'; @GLIB_GENMARSHAL@ --prefix=nld_marshal $(srcdir)/nld-marshal.list --body) > nld-marshal.tmp \
+ && mv nld-marshal.tmp nld-marshal.c ) \
+ || ( rm -f nld-marshal.tmp && exit 1 )
+
menudir = $(sysconfdir)/xdg/menus
menu_DATA = matecc.menu
-EXTRA_DIST = $(sys_in_files) matecc.directory.desktop.in matecc.menu
+EXTRA_DIST = $(sys_in_files) matecc.directory.desktop.in matecc.menu nld-marshal.list
DISTCLEANFILES = $(sys_DATA) matecc.directory
diff --git a/shell/app-resizer.c b/shell/app-resizer.c
new file mode 100644
index 00000000..0e1a15b8
--- /dev/null
+++ b/shell/app-resizer.c
@@ -0,0 +1,321 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <gtk/gtk.h>
+
+#include "app-shell.h"
+#include "app-resizer.h"
+
+static void app_resizer_size_allocate (GtkWidget * resizer, GtkAllocation * allocation);
+static gboolean app_resizer_paint_window (GtkWidget * widget, cairo_t * cr, AppShellData * app_data);
+
+G_DEFINE_TYPE (AppResizer, app_resizer, GTK_TYPE_LAYOUT);
+
+static void
+app_resizer_class_init (AppResizerClass * klass)
+{
+ GtkWidgetClass *widget_class;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ widget_class->size_allocate = app_resizer_size_allocate;
+}
+
+static void
+app_resizer_init (AppResizer * window)
+{
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (window)),
+ GTK_STYLE_CLASS_VIEW);
+}
+
+void
+remove_container_entries (GtkContainer * widget)
+{
+ GList *children, *l;
+
+ children = gtk_container_get_children (widget);
+ for (l = children; l; l = l->next)
+ {
+ GtkWidget *child = GTK_WIDGET (l->data);
+ gtk_container_remove (GTK_CONTAINER (widget), GTK_WIDGET (child));
+ }
+
+ if (children)
+ g_list_free (children);
+}
+
+static void
+resize_table (AppResizer *widget, GtkGrid * table, gint columns)
+{
+ remove_container_entries (GTK_CONTAINER (table));
+ widget->column = columns;
+}
+
+static void
+relayout_table (AppResizer *widget, GtkGrid * table, GList * element_list)
+{
+ gint row = 0, col = 0;
+ do
+ {
+ GtkWidget *element = GTK_WIDGET (element_list->data);
+ gtk_grid_attach (table, element, col, row, 1, 1);
+ col++;
+ if (col == widget->column)
+ {
+ col = 0;
+ row++;
+ }
+ }
+ while (NULL != (element_list = g_list_next (element_list)));
+}
+
+void
+app_resizer_layout_table_default (AppResizer * widget, GtkGrid * table, GList * element_list)
+{
+ resize_table (widget, table, widget->cur_num_cols);
+ relayout_table (widget, table, element_list);
+}
+
+static void
+relayout_tables (AppResizer * widget, gint num_cols)
+{
+ GtkGrid *table;
+ GList *table_list, *launcher_list;
+
+ for (table_list = widget->cached_tables_list; table_list != NULL;
+ table_list = g_list_next (table_list))
+ {
+ table = GTK_GRID (table_list->data);
+ launcher_list = gtk_container_get_children (GTK_CONTAINER (table));
+ launcher_list = g_list_reverse (launcher_list); /* Fixme - ugly hack because table stores prepend */
+ resize_table (widget, table, num_cols);
+ relayout_table (widget, table, launcher_list);
+ g_list_free (launcher_list);
+ }
+}
+
+static gint
+calculate_num_cols (AppResizer * resizer, gint avail_width)
+{
+ if (resizer->table_elements_homogeneous)
+ {
+ gint num_cols;
+
+ if (resizer->cached_element_width == -1)
+ {
+ GtkGrid *table = GTK_GRID (resizer->cached_tables_list->data);
+ GList *children = gtk_container_get_children (GTK_CONTAINER (table));
+ GtkWidget *table_element = GTK_WIDGET (children->data);
+ gint natural_width;
+ g_list_free (children);
+
+ gtk_widget_get_preferred_width (table_element, NULL, &natural_width);
+ resizer->cached_element_width = natural_width;
+ resizer->cached_table_spacing = gtk_grid_get_column_spacing (table);
+ }
+
+ num_cols =
+ (avail_width +
+ resizer->cached_table_spacing) / (resizer->cached_element_width +
+ resizer->cached_table_spacing);
+ return num_cols;
+ }
+ else
+ g_assert_not_reached (); /* Fixme - implement... */
+}
+
+static gint
+relayout_tables_if_needed (AppResizer * widget, gint avail_width, gint current_num_cols)
+{
+ gint num_cols = calculate_num_cols (widget, avail_width);
+ if (num_cols < 1)
+ {
+ num_cols = 1; /* just horiz scroll if avail_width is less than one column */
+ }
+
+ if (current_num_cols != num_cols)
+ {
+ relayout_tables (widget, num_cols);
+ current_num_cols = num_cols;
+ }
+ return current_num_cols;
+}
+
+void
+app_resizer_set_table_cache (AppResizer * widget, GList * cache_list)
+{
+ widget->cached_tables_list = cache_list;
+}
+
+static void
+app_resizer_size_allocate (GtkWidget * widget, GtkAllocation * allocation)
+{
+ AppResizer *resizer = APP_RESIZER (widget);
+ GtkWidget *child = GTK_WIDGET (APP_RESIZER (resizer)->child);
+ GtkAllocation widget_allocation;
+ GtkRequisition child_requisition;
+
+ static gboolean first_time = TRUE;
+ gint new_num_cols;
+
+ if (first_time)
+ {
+ /* we are letting the first show be the "natural" size of the child widget so do nothing. */
+ if (GTK_WIDGET_CLASS (app_resizer_parent_class)->size_allocate)
+ (*GTK_WIDGET_CLASS (app_resizer_parent_class)->size_allocate) (widget, allocation);
+
+ first_time = FALSE;
+ gtk_widget_get_allocation (child, &widget_allocation);
+ gtk_layout_set_size (GTK_LAYOUT (resizer), widget_allocation.width,
+ widget_allocation.height);
+ return;
+ }
+
+ gtk_widget_get_preferred_size (child, &child_requisition, NULL);
+
+ if (!resizer->cached_tables_list) /* if everthing is currently filtered out - just return */
+ {
+ GtkAllocation child_allocation;
+
+ if (GTK_WIDGET_CLASS (app_resizer_parent_class)->size_allocate)
+ (*GTK_WIDGET_CLASS (app_resizer_parent_class)->size_allocate) (widget, allocation);
+
+ /* We want the message to center itself and only scroll if it's bigger than the available real size. */
+ child_allocation.x = 0;
+ child_allocation.y = 0;
+ child_allocation.width = MAX (allocation->width, child_requisition.width);
+ child_allocation.height = MAX (allocation->height, child_requisition.height);
+
+ gtk_widget_size_allocate (child, &child_allocation);
+ gtk_layout_set_size (GTK_LAYOUT (resizer), child_allocation.width,
+ child_allocation.height);
+ return;
+ }
+ GtkRequisition other_requisiton;
+ gtk_widget_get_preferred_size (GTK_WIDGET (resizer->cached_tables_list->data), &other_requisiton, NULL);
+
+ new_num_cols =
+ relayout_tables_if_needed (APP_RESIZER (resizer), allocation->width,
+ resizer->cur_num_cols);
+ if (resizer->cur_num_cols != new_num_cols)
+ {
+ GtkRequisition req;
+
+ /* Have to do this so that it requests, and thus gets allocated, new amount */
+ gtk_widget_get_preferred_size (child, &req, NULL);
+
+ resizer->cur_num_cols = new_num_cols;
+ }
+
+ if (GTK_WIDGET_CLASS (app_resizer_parent_class)->size_allocate)
+ (*GTK_WIDGET_CLASS (app_resizer_parent_class)->size_allocate) (widget, allocation);
+ gtk_widget_get_allocation (child, &widget_allocation);
+ gtk_layout_set_size (GTK_LAYOUT (resizer), widget_allocation.width,
+ widget_allocation.height);
+}
+
+GtkWidget *
+app_resizer_new (GtkBox * child, gint initial_num_columns, gboolean homogeneous,
+ AppShellData * app_data)
+{
+ AppResizer *widget;
+
+ g_assert (child != NULL);
+
+ widget = g_object_new (APP_RESIZER_TYPE, NULL);
+ widget->cached_element_width = -1;
+ widget->cur_num_cols = initial_num_columns;
+ widget->table_elements_homogeneous = homogeneous;
+ widget->setting_style = FALSE;
+ widget->app_data = app_data;
+
+ g_signal_connect (widget, "draw",
+ G_CALLBACK (app_resizer_paint_window),
+ app_data);
+
+ gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (child));
+ widget->child = child;
+
+ return GTK_WIDGET (widget);
+}
+
+void
+app_resizer_set_vadjustment_value (GtkWidget * widget, gdouble value)
+{
+ GtkAdjustment *adjust;
+
+ adjust = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
+
+ gdouble upper = gtk_adjustment_get_upper (adjust);
+ gdouble page_size = gtk_adjustment_get_page_size (adjust);
+ if (value > upper - page_size)
+ {
+ value = upper - page_size;
+ }
+ gtk_adjustment_set_value (adjust, value);
+}
+
+static gboolean
+app_resizer_paint_window (GtkWidget * widget, cairo_t * cr, AppShellData * app_data)
+{
+ cairo_save(cr);
+ GtkStyleContext *context;
+ GdkRGBA *bg_rgba = NULL;
+
+ GtkAllocation widget_allocation;
+ gtk_widget_get_allocation (widget, &widget_allocation);
+
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_get (context,
+ GTK_STATE_FLAG_NORMAL,
+ "background-color", &bg_rgba,
+ NULL);
+
+ gdk_cairo_set_source_rgba (cr, bg_rgba);
+ cairo_set_line_width(cr, 1);
+
+ cairo_rectangle(cr, widget_allocation.x, widget_allocation.y, widget_allocation.width, widget_allocation.height);
+ cairo_stroke_preserve(cr);
+ cairo_fill(cr);
+
+ if (app_data->selected_group)
+ {
+ GtkWidget *selected_widget = GTK_WIDGET (app_data->selected_group);
+ GdkRGBA *rgba;
+ GtkAllocation selected_widget_allocation;
+ gtk_widget_get_allocation (selected_widget, &selected_widget_allocation);
+
+ gtk_style_context_get (context,
+ GTK_STATE_FLAG_PRELIGHT,
+ "background-color", &rgba,
+ NULL);
+
+ gdk_cairo_set_source_rgba (cr, rgba);
+ cairo_set_line_width(cr, 1);
+ cairo_rectangle(cr, selected_widget_allocation.x, selected_widget_allocation.y, selected_widget_allocation.width, selected_widget_allocation.height);
+ cairo_stroke_preserve(cr);
+ cairo_fill(cr);
+ gdk_rgba_free (rgba);
+ }
+
+ cairo_restore(cr);
+ gdk_rgba_free (bg_rgba);
+
+ return FALSE;
+}
diff --git a/shell/app-resizer.h b/shell/app-resizer.h
new file mode 100644
index 00000000..1c65f533
--- /dev/null
+++ b/shell/app-resizer.h
@@ -0,0 +1,74 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __APP_RESIZER_H__
+#define __APP_RESIZER_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "app-shell.h"
+
+G_BEGIN_DECLS
+
+#define INITIAL_NUM_COLS 3
+#define APP_RESIZER_TYPE (app_resizer_get_type ())
+#define APP_RESIZER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), APP_RESIZER_TYPE, AppResizer))
+#define APP_RESIZER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), APP_RESIZER_TYPE, AppResizerClass))
+#define IS_APP_RESIZER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), APP_RESIZER_TYPE))
+#define IS_APP_RESIZER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), APP_RESIZER_TYPE))
+#define APP_RESIZER_GET_CLASS(obj) (G_TYPE_CHECK_GET_CLASS ((obj), APP_RESIZER_TYPE, AppResizerClass))
+
+typedef struct _AppResizer AppResizer;
+typedef struct _AppResizerClass AppResizerClass;
+
+struct _AppResizer
+{
+ GtkLayout parent;
+
+ GtkBox *child;
+ GList *cached_tables_list;
+ gint cached_element_width;
+ gint cached_table_spacing;
+ gboolean table_elements_homogeneous;
+ gint cur_num_cols;
+ gboolean setting_style;
+
+ guint column;
+ AppShellData *app_data;
+};
+
+struct _AppResizerClass
+{
+ GtkLayoutClass parent_class;
+};
+
+void remove_container_entries (GtkContainer * widget);
+
+GType app_resizer_get_type (void);
+GtkWidget *app_resizer_new (GtkBox * child, gint initial_num_columns, gboolean homogeneous,
+ AppShellData * app_data);
+void app_resizer_set_table_cache (AppResizer * widget, GList * cache_list);
+void app_resizer_layout_table_default (AppResizer * widget, GtkGrid * table, GList * element_list);
+void app_resizer_set_vadjustment_value (GtkWidget * widget, gdouble value);
+
+G_END_DECLS
+
+#endif /* __APP_RESIZER_H__ */
diff --git a/shell/app-shell.c b/shell/app-shell.c
new file mode 100644
index 00000000..0e681c2e
--- /dev/null
+++ b/shell/app-shell.c
@@ -0,0 +1,1448 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libmate-desktop/mate-desktop-item.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "app-shell.h"
+#include "shell-window.h"
+#include "app-resizer.h"
+#include "slab-section.h"
+#include "slab-mate-util.h"
+#include "search-bar.h"
+
+#include "application-tile.h"
+#include "themed-icon.h"
+
+#define TILE_EXEC_NAME "Tile_desktop_exec_name"
+#define CC_SCHEMA "org.mate.control-center"
+#define EXIT_SHELL_ON_ACTION_START "cc-exit-shell-on-action-start"
+#define EXIT_SHELL_ON_ACTION_HELP "cc-exit-shell-on-action-help"
+#define EXIT_SHELL_ON_ACTION_ADD_REMOVE "cc-exit-shell-on-action-add-remove"
+#define EXIT_SHELL_ON_ACTION_UPGRADE_UNINSTALL "cc-exit-shell-on-action-upgrade-uninstall"
+
+static void create_application_category_sections (AppShellData * app_data);
+static GtkWidget *create_filter_section (AppShellData * app_data, const gchar * title);
+static GtkWidget *create_groups_section (AppShellData * app_data, const gchar * title);
+static GtkWidget *create_actions_section (AppShellData * app_data, const gchar * title,
+ void (*actions_handler) (Tile *, TileEvent *, gpointer));
+
+static void generate_category (const char * category, MateMenuTreeDirectory * root_dir, AppShellData * app_data, gboolean recursive);
+static void generate_launchers (MateMenuTreeDirectory * root_dir, AppShellData * app_data,
+ CategoryData * cat_data, gboolean recursive);
+static void generate_new_apps (AppShellData * app_data);
+static void insert_launcher_into_category (CategoryData * cat_data, MateDesktopItem * desktop_item,
+ AppShellData * app_data);
+
+static gboolean main_keypress_callback (GtkWidget * widget, GdkEventKey * event,
+ AppShellData * app_data);
+static gboolean main_delete_callback (GtkWidget * widget, GdkEvent * event,
+ AppShellData * app_data);
+static void application_launcher_clear_search_bar (AppShellData * app_data);
+static void launch_selected_app (AppShellData * app_data);
+static void generate_potential_apps (gpointer catdata, gpointer user_data);
+
+static void relayout_shell (AppShellData * app_data);
+static gboolean handle_filter_changed (NldSearchBar * search_bar, const char *text,
+ gpointer user_data);
+static void handle_group_clicked (Tile * tile, TileEvent * event, gpointer user_data);
+static void set_state (AppShellData * app_data, GtkWidget * widget);
+static void populate_groups_section (AppShellData * app_data);
+static void generate_filtered_lists (gpointer catdata, gpointer user_data);
+static void show_no_results_message (AppShellData * app_data, GtkWidget * containing_vbox);
+static void populate_application_category_sections (AppShellData * app_data,
+ GtkWidget * containing_vbox);
+static void populate_application_category_section (AppShellData * app_data, SlabSection * section,
+ GList * launcher_list);
+static void tile_activated_cb (Tile * tile, TileEvent * event, gpointer user_data);
+static void handle_launcher_single_clicked (Tile * launcher, gpointer data);
+static void handle_menu_action_performed (Tile * launcher, TileEvent * event, TileAction * action,
+ gpointer data);
+static gint application_launcher_compare (gconstpointer a, gconstpointer b);
+static void matemenu_tree_changed_callback (MateMenuTree * tree, gpointer user_data);
+gboolean regenerate_categories (AppShellData * app_data);
+
+void
+hide_shell (AppShellData * app_data)
+{
+ gtk_window_get_position (GTK_WINDOW (app_data->main_app),
+ &app_data->main_app_window_x, &app_data->main_app_window_y);
+ /* clear the search bar now so reshowing is fast and flicker free - BNC#283186 */
+ application_launcher_clear_search_bar (app_data);
+ gtk_widget_hide (app_data->main_app);
+}
+
+void
+show_shell (AppShellData * app_data)
+{
+ gtk_widget_show_all (app_data->main_app);
+ if (!app_data->static_actions)
+ gtk_widget_hide (app_data->actions_section); /* don't show unless a launcher is selected */
+
+ if (app_data->main_app_window_shown_once)
+ gtk_window_move (GTK_WINDOW (app_data->main_app),
+ app_data->main_app_window_x, app_data->main_app_window_y);
+
+ /* if this is the first time shown, need to clear this handler */
+ else
+ shell_window_clear_resize_handler (SHELL_WINDOW (app_data->shell));
+ app_data->main_app_window_shown_once = TRUE;
+}
+
+gboolean
+create_main_window (AppShellData * app_data, const gchar * app_name, const gchar * title,
+ const gchar * window_icon, gint width, gint height, gboolean hidden)
+{
+ GtkWidget *main_app = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ app_data->main_app = main_app;
+ gtk_widget_set_name (main_app, app_name);
+ gtk_window_set_title (GTK_WINDOW (main_app), title);
+ gtk_window_set_default_size(GTK_WINDOW(main_app), width, height);
+ gtk_window_set_icon_name (GTK_WINDOW (main_app), window_icon);
+ gtk_container_add (GTK_CONTAINER (main_app), app_data->shell);
+
+ g_signal_connect (main_app, "delete-event", G_CALLBACK (main_delete_callback), app_data);
+ g_signal_connect (main_app, "key-press-event", G_CALLBACK (main_keypress_callback),
+ app_data);
+
+ gtk_window_set_position (GTK_WINDOW (app_data->main_app), GTK_WIN_POS_CENTER);
+ if (!hidden)
+ show_shell (app_data);
+
+ return TRUE;
+}
+
+static void
+generate_potential_apps (gpointer catdata, gpointer user_data)
+{
+ GHashTable *app_hash = (GHashTable *) user_data;
+ CategoryData *data = (CategoryData *) catdata;
+ gchar *uri;
+
+ GList *launcher_list = data->filtered_launcher_list;
+
+ while (launcher_list)
+ {
+ g_object_get (launcher_list->data, "tile-uri", &uri, NULL);
+ /* eliminate dups of same app in multiple categories */
+ if (!g_hash_table_lookup (app_hash, uri))
+ g_hash_table_insert (app_hash, uri, launcher_list->data);
+ else
+ g_free (uri);
+ launcher_list = g_list_next (launcher_list);
+ }
+}
+
+static gboolean
+return_first_entry (gpointer key, gpointer value, gpointer unused)
+{
+ return TRUE; /*better way to pull an entry out ? */
+}
+
+static void
+launch_selected_app (AppShellData * app_data)
+{
+ GHashTable *app_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ guint num_apps;
+
+ g_list_foreach (app_data->categories_list, generate_potential_apps, app_hash);
+ num_apps = g_hash_table_size (app_hash);
+ if (num_apps == 1)
+ {
+ ApplicationTile *launcher =
+ APPLICATION_TILE (g_hash_table_find (app_hash, return_first_entry, NULL));
+ g_hash_table_destroy (app_hash);
+ handle_launcher_single_clicked (TILE (launcher), app_data);
+ return;
+ }
+
+ g_hash_table_destroy (app_hash);
+}
+
+static gboolean
+main_keypress_callback (GtkWidget * widget, GdkEventKey * event, AppShellData * app_data)
+{
+ GApplication *app;
+
+ if (event->keyval == GDK_KEY_Return)
+ {
+ SlabSection *section = SLAB_SECTION (app_data->filter_section);
+ NldSearchBar *search_bar;
+
+ /* Make sure our implementation has not changed */
+ g_assert (NLD_IS_SEARCH_BAR (section->contents));
+ search_bar = NLD_SEARCH_BAR (section->contents);
+ if (nld_search_bar_has_focus (search_bar))
+ {
+ launch_selected_app (app_data);
+ return TRUE;
+ }
+ }
+
+ /* quit on ESC or Ctl-W or Ctl-Q */
+ if (event->keyval == GDK_KEY_Escape ||
+ ((event->keyval == GDK_KEY_w || event->keyval == GDK_KEY_W) && (event->state & GDK_CONTROL_MASK)) ||
+ ((event->keyval == GDK_KEY_q || event->keyval == GDK_KEY_Q) && (event->state & GDK_CONTROL_MASK)))
+ {
+ if (app_data->exit_on_close)
+ {
+ app=g_application_get_default();
+ g_application_quit(app);
+ }
+ else
+ hide_shell (app_data);
+
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+main_delete_callback (GtkWidget * widget, GdkEvent * event, AppShellData * app_data)
+{
+ GApplication *app;
+
+ if (app_data->exit_on_close)
+ {
+ app=g_application_get_default();
+ g_application_quit(app);
+ return FALSE;
+ }
+
+ hide_shell (app_data);
+ return TRUE; /* stop the processing of this event */
+}
+
+void
+layout_shell (AppShellData * app_data, const gchar * filter_title, const gchar * groups_title,
+ const gchar * actions_title, GSList * actions,
+ void (*actions_handler) (Tile *, TileEvent *, gpointer))
+{
+ GtkWidget *filter_section;
+ GtkWidget *groups_section;
+ GtkWidget *actions_section;
+
+ GtkWidget *left_vbox;
+ GtkWidget *right_vbox;
+ gint num_cols;
+
+ GtkWidget *sw;
+ GtkAdjustment *adjustment;
+
+ app_data->shell = shell_window_new (app_data);
+ app_data->static_actions = actions;
+
+ right_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+
+ num_cols = SIZING_SCREEN_WIDTH_LARGE_NUMCOLS;
+
+ GdkRectangle geometry = {0};
+
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+
+ display= gdk_screen_get_display (gdk_screen_get_default ());
+ monitor = gdk_display_get_monitor (display, 0);
+ gdk_monitor_get_geometry (monitor, &geometry);
+
+ if (geometry.width <= SIZING_SCREEN_WIDTH_LARGE)
+ {
+ if (geometry.width <= SIZING_SCREEN_WIDTH_MEDIUM)
+ num_cols = SIZING_SCREEN_WIDTH_SMALL_NUMCOLS;
+ else
+ num_cols = SIZING_SCREEN_WIDTH_MEDIUM_NUMCOLS;
+ }
+ app_data->category_layout =
+ app_resizer_new (GTK_BOX (right_vbox), num_cols, TRUE, app_data);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_vexpand (GTK_WIDGET (sw), TRUE);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (sw), app_data->category_layout);
+ adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
+ g_object_set (adjustment, "step-increment", (double) 20, NULL);
+
+ create_application_category_sections (app_data);
+ populate_application_category_sections (app_data, right_vbox);
+ app_resizer_set_table_cache (APP_RESIZER (app_data->category_layout),
+ app_data->cached_tables_list);
+
+ gtk_container_set_focus_vadjustment (GTK_CONTAINER (right_vbox),
+ gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw)));
+
+ left_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 15);
+
+ filter_section = create_filter_section (app_data, filter_title);
+ app_data->filter_section = filter_section;
+ gtk_box_pack_start (GTK_BOX (left_vbox), filter_section, FALSE, FALSE, 0);
+
+ groups_section = create_groups_section (app_data, groups_title);
+ app_data->groups_section = groups_section;
+ populate_groups_section (app_data);
+ gtk_box_pack_start (GTK_BOX (left_vbox), groups_section, FALSE, FALSE, 0);
+
+ actions_section = create_actions_section (app_data, actions_title, actions_handler);
+ app_data->actions_section = actions_section;
+ gtk_box_pack_start (GTK_BOX (left_vbox), actions_section, FALSE, FALSE, 0);
+
+ shell_window_set_contents (SHELL_WINDOW (app_data->shell), left_vbox, sw);
+}
+
+static gboolean
+relayout_shell_partial (gpointer user_data)
+{
+ AppShellData *app_data = (AppShellData *) user_data;
+ GtkBox *vbox = APP_RESIZER (app_data->category_layout)->child;
+ CategoryData *data;
+
+ if (app_data->stop_incremental_relayout)
+ return FALSE;
+
+ if (app_data->incremental_relayout_cat_list != NULL)
+ {
+ /* There are still categories to layout */
+ data = (CategoryData *) app_data->incremental_relayout_cat_list->data;
+ if (data->filtered_launcher_list != NULL)
+ {
+ populate_application_category_section (app_data, data->section,
+ data->filtered_launcher_list);
+ gtk_box_pack_start (vbox, GTK_WIDGET (data->section), TRUE, TRUE,
+ 0);
+ app_data->filtered_out_everything = FALSE;
+ }
+
+ app_data->incremental_relayout_cat_list =
+ g_list_next (app_data->incremental_relayout_cat_list);
+ return TRUE;
+ }
+
+ /* We're done laying out the categories; finish up */
+ if (app_data->filtered_out_everything)
+ show_no_results_message (app_data, GTK_WIDGET (vbox));
+
+ app_resizer_set_table_cache (APP_RESIZER (app_data->category_layout),
+ app_data->cached_tables_list);
+ populate_groups_section (app_data);
+
+ gtk_widget_show_all (app_data->category_layout);
+ gdk_window_set_cursor (gtk_widget_get_window (app_data->shell), NULL);
+
+ app_data->stop_incremental_relayout = TRUE;
+ return FALSE;
+}
+
+static void
+relayout_shell_incremental (AppShellData * app_data)
+{
+ GtkBox *vbox = APP_RESIZER (app_data->category_layout)->child;
+
+ app_data->stop_incremental_relayout = FALSE;
+ app_data->filtered_out_everything = TRUE;
+ app_data->incremental_relayout_cat_list = app_data->categories_list;
+
+ if (app_data->cached_tables_list)
+ g_list_free (app_data->cached_tables_list);
+ app_data->cached_tables_list = NULL;
+
+ remove_container_entries (GTK_CONTAINER (vbox));
+
+ g_idle_add ((GSourceFunc) relayout_shell_partial, app_data);
+}
+
+static void
+relayout_shell (AppShellData * app_data)
+{
+ GtkWidget *shell = app_data->shell;
+ GtkBox *vbox = APP_RESIZER (app_data->category_layout)->child;
+
+ populate_application_category_sections (app_data, GTK_WIDGET (vbox));
+ app_resizer_set_table_cache (APP_RESIZER (app_data->category_layout),
+ app_data->cached_tables_list);
+ populate_groups_section (app_data);
+
+ gtk_widget_show_all (shell);
+ if (!app_data->static_actions && !app_data->last_clicked_launcher)
+ gtk_widget_hide (app_data->actions_section); /* don't show unless a launcher is selected */
+}
+
+static GtkWidget *
+create_actions_section (AppShellData * app_data, const gchar * title,
+ void (*actions_handler) (Tile *, TileEvent *, gpointer))
+{
+ GtkWidget *section, *launcher;
+ GtkWidget *vbox;
+ GSList *actions;
+ AppAction *action;
+ AtkObject *a11y_cat;
+
+ g_assert (app_data != NULL);
+
+ section = slab_section_new (title, Style1);
+ g_object_ref (section);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ slab_section_set_contents (SLAB_SECTION (section), vbox);
+
+ if (app_data->static_actions)
+ {
+ for (actions = app_data->static_actions; actions; actions = actions->next)
+ {
+ GtkWidget *header;
+
+ action = (AppAction *) actions->data;
+ header = gtk_label_new (action->name);
+ gtk_label_set_line_wrap (GTK_LABEL (header), TRUE);
+ gtk_label_set_max_width_chars (GTK_LABEL (header), 0);
+ gtk_label_set_xalign (GTK_LABEL (header), 0.0);
+ launcher = nameplate_tile_new (NULL, NULL, header, NULL);
+
+ g_object_set_data (G_OBJECT (launcher), APP_ACTION_KEY, action->item);
+ g_signal_connect (launcher, "tile-activated", G_CALLBACK (actions_handler),
+ app_data);
+ gtk_box_pack_start (GTK_BOX (vbox), launcher, FALSE, FALSE, 0);
+
+ a11y_cat = gtk_widget_get_accessible (GTK_WIDGET (launcher));
+ atk_object_set_name (a11y_cat, action->name);
+ }
+ }
+
+ return section;
+}
+
+static GtkWidget *
+create_groups_section (AppShellData * app_data, const gchar * title)
+{
+ GtkWidget *section;
+ GtkWidget *vbox;
+
+ g_assert (app_data != NULL);
+
+ section = slab_section_new (title, Style1);
+ g_object_ref (section);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ slab_section_set_contents (SLAB_SECTION (section), vbox);
+
+ return section;
+}
+
+static void
+populate_groups_section (AppShellData * app_data)
+{
+ SlabSection *section = SLAB_SECTION (app_data->groups_section);
+ GtkBox *vbox;
+ GList *cat_list;
+
+ vbox = GTK_BOX (section->contents);
+ remove_container_entries (GTK_CONTAINER (vbox));
+
+ cat_list = app_data->categories_list;
+ do
+ {
+ CategoryData *data = (CategoryData *) cat_list->data;
+ if (NULL != data->filtered_launcher_list)
+ {
+ gtk_widget_set_state_flags (GTK_WIDGET (data->group_launcher), GTK_STATE_FLAG_NORMAL, FALSE);
+ gtk_box_pack_start (vbox, GTK_WIDGET (data->group_launcher),
+ FALSE, FALSE, 0);
+ }
+ }
+ while (NULL != (cat_list = g_list_next (cat_list)));
+}
+
+static void
+handle_group_clicked (Tile * tile, TileEvent * event, gpointer user_data)
+{
+ AppShellData *app_data = (AppShellData *) user_data;
+ GtkWidget *section = NULL;
+ GtkAllocation allocation;
+
+ gint clicked_pos =
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tile), GROUP_POSITION_NUMBER_KEY));
+
+ GList *cat_list = app_data->categories_list;
+
+ gint total = 0;
+ do
+ {
+ CategoryData *cat_data = (CategoryData *) cat_list->data;
+ gint pos =
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cat_data->group_launcher),
+ GROUP_POSITION_NUMBER_KEY));
+ if (pos == clicked_pos)
+ {
+ section = GTK_WIDGET (cat_data->section);
+ break;
+ }
+
+ if (NULL != cat_data->filtered_launcher_list)
+ {
+ gtk_widget_get_allocation (GTK_WIDGET (cat_data->section), &allocation);
+ total += allocation.height;
+ }
+ }
+ while (NULL != (cat_list = g_list_next (cat_list)));
+
+ g_assert (section != NULL);
+ set_state (app_data, section);
+
+ app_resizer_set_vadjustment_value (app_data->category_layout, total);
+}
+
+static void
+set_state (AppShellData * app_data, GtkWidget * widget)
+{
+ if (app_data->selected_group)
+ {
+ slab_section_set_selected (app_data->selected_group, FALSE);
+ app_data->selected_group = NULL;
+ }
+
+ if (widget)
+ {
+ app_data->selected_group = SLAB_SECTION (widget);
+ slab_section_set_selected (SLAB_SECTION (widget), TRUE);
+ }
+ gtk_widget_queue_draw (app_data->shell);
+}
+
+static GtkWidget *
+create_filter_section (AppShellData * app_data, const gchar * title)
+{
+ GtkWidget *section;
+
+ GtkWidget *search_bar;
+
+ section = slab_section_new (title, Style1);
+ g_object_ref (section);
+
+ search_bar = nld_search_bar_new ();
+ nld_search_bar_set_search_timeout (NLD_SEARCH_BAR (search_bar), 0);
+ slab_section_set_contents (SLAB_SECTION (section), search_bar);
+
+ g_signal_connect (search_bar, "search",
+ G_CALLBACK (handle_filter_changed),
+ app_data);
+
+ return section;
+}
+
+static gboolean
+handle_filter_changed_delayed (gpointer user_data)
+{
+ AppShellData *app_data = (AppShellData *) user_data;
+
+ g_list_foreach (app_data->categories_list, generate_filtered_lists,
+ (gpointer) app_data->filter_string);
+ app_data->last_clicked_launcher = NULL;
+
+ /* showing the updates incremtally is very visually distracting. Much worse than just blanking until
+ the incremental work is done and then doing one show. It would be nice to optimize this though
+ somehow and not even show any change but the cursor change until all the work is done. But since
+ we do the work incrementally in an idle loop I don't know how else besides hiding to not show
+ incremental updates
+ */
+ /* gdk_window_freeze_updates(app_data->category_layout->window); */
+ gtk_widget_hide (app_data->category_layout);
+ app_data->busy_cursor =
+ gdk_cursor_new_for_display (gtk_widget_get_display (app_data->shell), GDK_WATCH);
+ gdk_window_set_cursor (gtk_widget_get_window (app_data->shell), app_data->busy_cursor);
+ g_object_unref (app_data->busy_cursor);
+
+ set_state (app_data, NULL);
+ app_resizer_set_vadjustment_value (app_data->category_layout, 0);
+
+ relayout_shell_incremental (app_data);
+
+ app_data->filter_changed_timeout = 0;
+ return FALSE;
+}
+
+static gboolean
+handle_filter_changed (NldSearchBar * search_bar, const char *text, gpointer data)
+{
+ AppShellData *app_data;
+
+ app_data = (AppShellData *) data;
+
+ if (app_data->filter_string)
+ g_free (app_data->filter_string);
+ app_data->filter_string = g_strdup (text);
+
+ if (app_data->filter_changed_timeout)
+ g_source_remove (app_data->filter_changed_timeout);
+
+ app_data->filter_changed_timeout =
+ g_timeout_add (75, handle_filter_changed_delayed, app_data);
+ app_data->stop_incremental_relayout = TRUE;
+
+ return FALSE;
+}
+
+static void
+generate_filtered_lists (gpointer catdata, gpointer user_data)
+{
+ CategoryData *data = (CategoryData *) catdata;
+
+ /* Fixme - everywhere you use ascii you need to fix up for multibyte */
+ gchar *filter_string = g_ascii_strdown (user_data, -1);
+ gchar *temp1, *temp2;
+ GList *launcher_list = data->launcher_list;
+
+ g_list_free (data->filtered_launcher_list);
+ data->filtered_launcher_list = NULL;
+
+ do
+ {
+ ApplicationTile *launcher = APPLICATION_TILE (launcher_list->data);
+ const gchar *filename;
+
+ temp1 = NULL;
+ temp2 = NULL;
+
+ /* Since the filter may remove this entry from the
+ container it will not get a mouse out event */
+ gtk_widget_set_state_flags (GTK_WIDGET (launcher), GTK_STATE_FLAG_NORMAL, FALSE);
+ filename = g_object_get_data (G_OBJECT (launcher), TILE_EXEC_NAME); /* do I need to free this */
+
+ temp1 = g_ascii_strdown (launcher->name, -1);
+ if (launcher->description)
+ temp2 = g_ascii_strdown (launcher->description, -1);
+ if (g_strrstr (temp1, filter_string) || (launcher->description
+ && g_strrstr (temp2, filter_string))
+ || g_strrstr (filename, filter_string))
+ {
+ data->filtered_launcher_list =
+ g_list_append (data->filtered_launcher_list, launcher);
+ }
+ if (temp1)
+ g_free (temp1);
+ if (temp2)
+ g_free (temp2);
+ }
+ while (NULL != (launcher_list = g_list_next (launcher_list)));
+ g_free (filter_string);
+}
+
+static void
+delete_old_data (AppShellData * app_data)
+{
+ GList *temp;
+ GList *cat_list;
+
+ g_assert (app_data != NULL);
+ g_assert (app_data->categories_list != NULL);
+
+ cat_list = app_data->categories_list;
+
+ do
+ {
+ CategoryData *data = (CategoryData *) cat_list->data;
+ gtk_widget_destroy (GTK_WIDGET (data->section));
+ gtk_widget_destroy (GTK_WIDGET (data->group_launcher));
+ g_object_unref (data->section);
+ g_object_unref (data->group_launcher);
+ g_free (data->category);
+
+ for (temp = data->launcher_list; temp; temp = g_list_next (temp))
+ {
+ g_free (g_object_get_data (G_OBJECT (temp->data), TILE_EXEC_NAME));
+ g_object_unref (temp->data);
+ }
+
+ g_list_free (data->launcher_list);
+ g_list_free (data->filtered_launcher_list);
+ g_free (data);
+ }
+ while (NULL != (cat_list = g_list_next (cat_list)));
+
+ g_list_free (app_data->categories_list);
+ app_data->categories_list = NULL;
+ app_data->selected_group = NULL;
+}
+
+static void
+create_application_category_sections (AppShellData * app_data)
+{
+ GList *cat_list;
+ AtkObject *a11y_cat;
+ gint pos = 0;
+
+ g_assert (app_data != NULL);
+ g_assert (app_data->categories_list != NULL); /* Fixme - pop up a dialog box and then close */
+
+ cat_list = app_data->categories_list;
+
+ do
+ {
+ CategoryData *data = (CategoryData *) cat_list->data;
+ GtkWidget *header = gtk_label_new (data->category);
+ gchar *markup;
+ GtkWidget *hbox;
+ GtkWidget *table;
+
+ gtk_label_set_xalign (GTK_LABEL (header), 0.0);
+ data->group_launcher = TILE (nameplate_tile_new (NULL, NULL, header, NULL));
+ g_object_ref (data->group_launcher);
+
+ g_object_set_data (G_OBJECT (data->group_launcher), GROUP_POSITION_NUMBER_KEY,
+ GINT_TO_POINTER (pos));
+ pos++;
+ g_signal_connect (data->group_launcher, "tile-activated",
+ G_CALLBACK (handle_group_clicked), app_data);
+ a11y_cat = gtk_widget_get_accessible (GTK_WIDGET (data->group_launcher));
+ atk_object_set_name (a11y_cat, data->category);
+
+ markup = g_markup_printf_escaped ("<span size=\"x-large\" weight=\"bold\">%s</span>",
+ data->category);
+ data->section = SLAB_SECTION (slab_section_new_with_markup (markup, Style2));
+
+ /* as we filter these will be added/removed from parent container and we dont want them destroyed */
+ g_object_ref (data->section);
+ g_free (markup);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ table = gtk_grid_new ();
+ gtk_grid_set_column_spacing (GTK_GRID (table), 5);
+ gtk_grid_set_row_spacing (GTK_GRID (table), 5);
+ gtk_box_pack_start (GTK_BOX (hbox), table, FALSE, FALSE, 15);
+ slab_section_set_contents (SLAB_SECTION (data->section), hbox);
+ }
+ while (NULL != (cat_list = g_list_next (cat_list)));
+}
+
+static void
+show_no_results_message (AppShellData * app_data, GtkWidget * containing_vbox)
+{
+ gchar *markup;
+ gchar *str1;
+ gchar *str2;
+
+ if (!app_data->filtered_out_everything_widget)
+ {
+ GtkWidget *image;
+ GtkWidget *label;
+
+ app_data->filtered_out_everything_widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_set_halign (app_data->filtered_out_everything_widget, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (app_data->filtered_out_everything_widget, GTK_ALIGN_CENTER);
+ g_object_ref (app_data->filtered_out_everything_widget);
+
+ image = themed_icon_new ("face-surprise", GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (app_data->filtered_out_everything_widget), image, FALSE, FALSE, 0);
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_box_pack_start (GTK_BOX (app_data->filtered_out_everything_widget), label, TRUE, TRUE, 15);
+ app_data->filtered_out_everything_widget_label = GTK_LABEL (label);
+ }
+
+ str1 = g_markup_printf_escaped ("<b>%s</b>", app_data->filter_string);
+ str2 = g_strdup_printf (_("Your filter \"%s\" does not match any items."), str1);
+ markup = g_strdup_printf ("<span size=\"large\"><b>%s</b></span>\n\n%s",
+ _("No matches found."), str2);
+ gtk_label_set_text (app_data->filtered_out_everything_widget_label, markup);
+ gtk_label_set_use_markup (app_data->filtered_out_everything_widget_label, TRUE);
+ gtk_box_pack_start (GTK_BOX (containing_vbox), app_data->filtered_out_everything_widget,
+ TRUE, TRUE, 0);
+ g_free (str1);
+ g_free (str2);
+ g_free (markup);
+}
+
+static void
+populate_application_category_sections (AppShellData * app_data, GtkWidget * containing_vbox)
+{
+ GList *cat_list = app_data->categories_list;
+ gboolean filtered_out_everything = TRUE;
+ if (app_data->cached_tables_list)
+ g_list_free (app_data->cached_tables_list);
+ app_data->cached_tables_list = NULL;
+
+ remove_container_entries (GTK_CONTAINER (containing_vbox));
+ do
+ {
+ CategoryData *data = (CategoryData *) cat_list->data;
+ if (NULL != data->filtered_launcher_list)
+ {
+ populate_application_category_section (app_data, data->section,
+ data->filtered_launcher_list);
+ gtk_box_pack_start (GTK_BOX (containing_vbox), GTK_WIDGET (data->section),
+ TRUE, TRUE, 0);
+ filtered_out_everything = FALSE;
+ }
+ }
+ while (NULL != (cat_list = g_list_next (cat_list)));
+
+ if (TRUE == filtered_out_everything)
+ show_no_results_message (app_data, containing_vbox);
+}
+
+static void
+populate_application_category_section (AppShellData * app_data, SlabSection * section,
+ GList * launcher_list)
+{
+ GtkWidget *hbox;
+ GtkGrid *table;
+ GList *children;
+
+ hbox = GTK_WIDGET (section->contents);
+
+ children = gtk_container_get_children (GTK_CONTAINER (hbox));
+ table = children->data;
+ g_list_free (children);
+
+ /* Make sure our implementation has not changed and it's still a GtkGrid */
+ g_assert (GTK_IS_GRID (table));
+
+ app_data->cached_tables_list = g_list_append (app_data->cached_tables_list, table);
+
+ app_resizer_layout_table_default (APP_RESIZER (app_data->category_layout), table,
+ launcher_list);
+
+}
+
+gboolean
+regenerate_categories (AppShellData * app_data)
+{
+ delete_old_data (app_data);
+ generate_categories (app_data);
+ create_application_category_sections (app_data);
+ relayout_shell (app_data);
+
+ return FALSE; /* remove this function from the list */
+}
+
+static void
+matemenu_tree_changed_callback (MateMenuTree * old_tree, gpointer user_data)
+{
+ /*
+ This method only gets called on the first change (matemenu appears to ignore subsequent) until
+ we reget the root dir which we can't do in this method because if we do for some reason this
+ method then gets called multiple times for one actual change. This actually is okay because
+ it's probably a good idea to wait a couple seconds to regenerate the categories in case there
+ are multiple quick changes being made, no sense regenerating multiple times.
+ */
+ GError *error = NULL;
+ AppShellData * app_data = user_data;
+ if (!matemenu_tree_load_sync (app_data->tree, &error)) {
+ g_warning ("Menu tree loading got error:%s\n", error->message);
+ g_object_unref (app_data->tree);
+ app_data->tree = NULL;
+ g_error_free (error);
+ } else {
+ g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 3000, (GSourceFunc) regenerate_categories,
+ user_data, NULL);
+ }
+}
+
+AppShellData *
+appshelldata_new (const gchar * menu_name, GtkIconSize icon_size, gboolean show_tile_generic_name, gboolean exit_on_close, gint new_apps_max_items)
+{
+ AppShellData *app_data = g_new0 (AppShellData, 1);
+ app_data->settings = g_settings_new (CC_SCHEMA);
+ app_data->menu_name = menu_name;
+ app_data->icon_size = icon_size;
+ app_data->stop_incremental_relayout = TRUE;
+ app_data->show_tile_generic_name = show_tile_generic_name;
+ app_data->exit_on_close = exit_on_close;
+ if (new_apps_max_items > 0) {
+ app_data->new_apps = g_new0 (NewAppConfig, 1);
+ app_data->new_apps->max_items = new_apps_max_items;
+ app_data->new_apps->name = _("New Applications");
+ }
+ return app_data;
+}
+
+void
+generate_categories (AppShellData * app_data)
+{
+ MateMenuTreeDirectory *root_dir;
+ gboolean need_misc = FALSE;
+ MateMenuTreeIter *iter;
+ MateMenuTreeItemType type;
+
+ if (!app_data->tree)
+ {
+ GError *error = NULL;
+
+ app_data->tree = matemenu_tree_new (app_data->menu_name, MATEMENU_TREE_FLAGS_NONE);
+ g_signal_connect (app_data->tree, "changed", G_CALLBACK (matemenu_tree_changed_callback), app_data);
+ if (! matemenu_tree_load_sync (app_data->tree, &error)) {
+ g_warning("Menu tree loading got error:%s\n", error->message);
+ g_error_free(error);
+ g_object_unref(app_data->tree);
+ app_data->tree = NULL;
+ }
+ }
+
+ if (app_data->tree != NULL)
+ root_dir = matemenu_tree_get_root_directory (app_data->tree);
+ else
+ root_dir = NULL;
+
+ if ( app_data->tree == NULL || root_dir == NULL) {
+ GtkWidget *dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Failure loading - %s",
+ app_data->menu_name);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+ exit (1); /* Fixme - is there a MATE/GTK way to do this. */
+ }
+
+ iter = matemenu_tree_directory_iter (root_dir);
+ while ((type = matemenu_tree_iter_next (iter)) != MATEMENU_TREE_ITEM_INVALID) {
+ gpointer item;
+ const char *category;
+ switch (type) {
+ case MATEMENU_TREE_ITEM_DIRECTORY:
+ item = matemenu_tree_iter_get_directory (iter);
+ category = matemenu_tree_directory_get_name (item);
+ generate_category(category, item, app_data, TRUE);
+ matemenu_tree_item_unref (item);
+ break;
+ case MATEMENU_TREE_ITEM_ENTRY:
+ need_misc = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ matemenu_tree_iter_unref(iter);
+
+ if (need_misc)
+ generate_category (_("Other"), root_dir, app_data, FALSE);
+
+ if (app_data->hash)
+ {
+ g_hash_table_destroy (app_data->hash);
+ app_data->hash = NULL;
+ }
+
+ matemenu_tree_item_unref (root_dir);
+
+ if (app_data->new_apps && (app_data->new_apps->max_items > 0))
+ generate_new_apps (app_data);
+}
+
+static void
+generate_category (const char * category, MateMenuTreeDirectory * root_dir, AppShellData * app_data, gboolean recursive)
+{
+ CategoryData *data;
+ /* This is not needed. MateMenu already returns an ordered, non duplicate list
+ GList *list_entry;
+ list_entry =
+ g_list_find_custom (app_data->categories_list, category,
+ category_name_compare);
+ if (!list_entry)
+ {
+ */
+ data = g_new0 (CategoryData, 1);
+ data->category = g_strdup (category);
+ app_data->categories_list =
+ /* use the matemenu order instead of alphabetical */
+ g_list_append (app_data->categories_list, data);
+ /* g_list_insert_sorted (app_data->categories_list, data, category_data_compare); */
+ /*
+ }
+ else
+ {
+ data = list_entry->data;
+ }
+ */
+
+ if (app_data->hash) /* used to eliminate dups on a per category basis. */
+ g_hash_table_destroy (app_data->hash);
+ app_data->hash = g_hash_table_new (g_str_hash, g_str_equal);
+ generate_launchers (root_dir, app_data, data, recursive);
+}
+
+static gboolean
+check_specific_apps_hack (MateDesktopItem * item)
+{
+ static const gchar *COMMAND_LINE_LOCKDOWN_SCHEMA = "org.mate.lockdown";
+ static const gchar *COMMAND_LINE_LOCKDOWN_KEY = "disable-command-line";
+ static const gchar *COMMAND_LINE_LOCKDOWN_DESKTOP_CATEGORY = "TerminalEmulator";
+ static gboolean got_lockdown_value = FALSE;
+ static gboolean command_line_lockdown;
+
+ gchar *path;
+ const char *exec;
+
+ if (!got_lockdown_value)
+ {
+ got_lockdown_value = TRUE;
+ GSettings *lockdown_settings;
+ lockdown_settings = g_settings_new (COMMAND_LINE_LOCKDOWN_SCHEMA);
+ command_line_lockdown = g_settings_get_boolean (lockdown_settings, COMMAND_LINE_LOCKDOWN_KEY);
+ g_object_unref (lockdown_settings);
+ }
+
+ /* This seems like an ugly hack but it's the way it's currently done in the old control center */
+ exec = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_EXEC);
+
+ /* discard xscreensaver if mate-screensaver is installed */
+ if ((exec && !strcmp (exec, "xscreensaver-demo"))
+ && (path = g_find_program_in_path ("mate-screensaver-preferences")))
+ {
+ g_free (path);
+ return TRUE;
+ }
+
+ /* discard gnome-keyring-manager if CASA is installed */
+ if ((exec && !strcmp (exec, "gnome-keyring-manager"))
+ && (path = g_find_program_in_path ("CASAManager.sh")))
+ {
+ g_free (path);
+ return TRUE;
+ }
+
+ /* discard terminals if lockdown key is set */
+ if (command_line_lockdown)
+ {
+ const gchar *categories =
+ mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_CATEGORIES);
+ if (g_strrstr (categories, COMMAND_LINE_LOCKDOWN_DESKTOP_CATEGORY))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+generate_launchers (MateMenuTreeDirectory * root_dir, AppShellData * app_data, CategoryData * cat_data, gboolean recursive)
+{
+ MateDesktopItem *desktop_item;
+ const gchar *desktop_file;
+ MateMenuTreeIter *iter;
+ MateMenuTreeItemType type;
+
+ iter = matemenu_tree_directory_iter (root_dir);
+ while ((type = matemenu_tree_iter_next (iter)) != MATEMENU_TREE_ITEM_INVALID) {
+ gpointer item;
+ switch (type) {
+ case MATEMENU_TREE_ITEM_DIRECTORY:
+ item = matemenu_tree_iter_get_directory(iter);
+ /* g_message ("Found sub-category %s", matemenu_tree_directory_get_name (item)); */
+ if (recursive)
+ generate_launchers (item, app_data, cat_data, TRUE);
+ matemenu_tree_item_unref (item);
+ break;
+ case MATEMENU_TREE_ITEM_ENTRY:
+ item = matemenu_tree_iter_get_entry(iter);
+ /* g_message ("Found item name is:%s", matemenu_tree_entry_get_desktop_file_id(item)); */
+ desktop_file = matemenu_tree_entry_get_desktop_file_path (item);
+ if (desktop_file)
+ {
+ if (g_hash_table_lookup (app_data->hash, desktop_file))
+ {
+ break; /* duplicate */
+ }
+ /* Fixme - make sure it's safe to store this without duping it. As far as I can tell it is
+ safe as long as I don't hang on to this anylonger than I hang on to the MateMenuTreeEntry*
+ which brings up another point - am I supposed to free these or does freeing the top level recurse
+ */
+ g_hash_table_insert (app_data->hash, (gpointer) desktop_file,
+ (gpointer) desktop_file);
+ }
+ desktop_item = mate_desktop_item_new_from_file (desktop_file, 0, NULL);
+ if (!desktop_item)
+ {
+ g_critical ("Failure - mate_desktop_item_new_from_file(%s)",
+ desktop_file);
+ break;
+ }
+ if (!check_specific_apps_hack (desktop_item))
+ insert_launcher_into_category (cat_data, desktop_item, app_data);
+ mate_desktop_item_unref (desktop_item);
+ matemenu_tree_item_unref (item);
+ break;
+ default:
+ break;
+ }
+ }
+ matemenu_tree_iter_unref(iter);
+}
+
+static void
+generate_new_apps (AppShellData * app_data)
+{
+ GHashTable *all_apps_cache = NULL;
+ gchar *all_apps;
+ GError *error = NULL;
+ gchar *separator = "\n";
+
+ gchar *all_apps_file_name;
+ gchar **all_apps_split;
+ gint x;
+ gboolean got_new_apps;
+ CategoryData *new_apps_category = NULL;
+ GList *categories, *launchers;
+ GHashTable *new_apps_dups;
+
+ all_apps_file_name = g_build_filename (g_get_user_config_dir (), "mate", "ab-newapps.txt", NULL);
+
+ if (!g_file_get_contents (all_apps_file_name, &all_apps, NULL, &error))
+ {
+ /* If file does not exist, this is the first time this user has run this, create the baseline file */
+ GList *categories, *launchers;
+ GString *gstr;
+ gchar *dirname;
+
+ g_error_free (error);
+ error = NULL;
+
+ /* best initial size determined by running on a couple different platforms */
+ gstr = g_string_sized_new (10000);
+
+ for (categories = app_data->categories_list; categories; categories = categories->next)
+ {
+ CategoryData *data = categories->data;
+ for (launchers = data->launcher_list; launchers; launchers = launchers->next)
+ {
+ Tile *tile = TILE (launchers->data);
+ MateDesktopItem *item =
+ application_tile_get_desktop_item (APPLICATION_TILE (tile));
+ const gchar *uri = mate_desktop_item_get_location (item);
+ g_string_append (gstr, uri);
+ g_string_append (gstr, separator);
+ }
+ }
+
+ dirname = g_path_get_dirname (all_apps_file_name);
+ g_mkdir_with_parents (dirname, 0700); /* creates if does not exist */
+ g_free (dirname);
+
+ if (!g_file_set_contents (all_apps_file_name, gstr->str, -1, &error))
+ g_warning ("Error setting all apps file:%s\n", error->message);
+
+ g_string_free (gstr, TRUE);
+ g_free (all_apps_file_name);
+ return;
+ }
+
+ all_apps_cache = g_hash_table_new (g_str_hash, g_str_equal);
+ all_apps_split = g_strsplit (all_apps, separator, -1);
+ for (x = 0; all_apps_split[x]; x++)
+ {
+ g_hash_table_insert (all_apps_cache, all_apps_split[x], all_apps_split[x]);
+ }
+
+ got_new_apps = FALSE;
+ new_apps_dups = g_hash_table_new (g_str_hash, g_str_equal);
+ for (categories = app_data->categories_list; categories; categories = categories->next)
+ {
+ CategoryData *cat_data = categories->data;
+ for (launchers = cat_data->launcher_list; launchers; launchers = launchers->next)
+ {
+ Tile *tile = TILE (launchers->data);
+ MateDesktopItem *item =
+ application_tile_get_desktop_item (APPLICATION_TILE (tile));
+ const gchar *uri = mate_desktop_item_get_location (item);
+ if (!g_hash_table_lookup (all_apps_cache, uri))
+ {
+ GFile *file;
+ GFileInfo *info;
+ long filetime;
+
+ if (g_hash_table_lookup (new_apps_dups, uri))
+ {
+ /* if a desktop file is in 2 or more top level categories, only show it once */
+ break;
+ }
+ g_hash_table_insert (new_apps_dups, (gpointer) uri, (gpointer) uri);
+
+ if (!got_new_apps)
+ {
+ new_apps_category = g_new0 (CategoryData, 1);
+ new_apps_category->category =
+ g_strdup (app_data->new_apps->name);
+ app_data->new_apps->garray =
+ g_array_sized_new (FALSE, TRUE,
+ sizeof (NewAppData *),
+ app_data->new_apps->max_items);
+
+ /* should not need this, but a bug in glib does not actually clear the elements until you call this method */
+ g_array_set_size (app_data->new_apps->garray, app_data->new_apps->max_items);
+ got_new_apps = TRUE;
+ }
+
+ file = g_file_new_for_uri (uri);
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ 0, NULL, NULL);
+
+ if (!info)
+ {
+ g_object_unref (file);
+ g_warning ("Cant get vfs info for %s\n", uri);
+ if (new_apps_category) {
+ g_free (new_apps_category->category);
+ g_free (new_apps_category);
+ }
+ g_free (all_apps_file_name);
+ g_strfreev (all_apps_split);
+ return;
+ }
+ filetime = (long) g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ g_object_unref (info);
+ g_object_unref (file);
+
+ for (x = 0; x < app_data->new_apps->max_items; x++)
+ {
+ NewAppData *temp_data = (NewAppData *)
+ g_array_index (app_data->new_apps->garray, NewAppData *, x);
+ if (!temp_data || filetime > temp_data->time) /* if this slot is empty or we are newer than this slot */
+ {
+ NewAppData *temp = g_new0 (NewAppData, 1);
+ temp->time = filetime;
+ temp->item = item;
+ g_array_insert_val (app_data->new_apps->garray, x,
+ temp);
+ break;
+ }
+ }
+ }
+ }
+ }
+ g_hash_table_destroy (new_apps_dups);
+ g_hash_table_destroy (all_apps_cache);
+
+ if (got_new_apps)
+ {
+ for (x = 0; x < app_data->new_apps->max_items; x++)
+ {
+ NewAppData *data =
+ (NewAppData *) g_array_index (app_data->new_apps->garray,
+ NewAppData *, x);
+ if (data)
+ {
+ insert_launcher_into_category (new_apps_category, data->item,
+ app_data);
+ g_free (data);
+ }
+ else
+ break;
+ }
+ app_data->categories_list =
+ g_list_prepend (app_data->categories_list, new_apps_category);
+
+ g_array_free (app_data->new_apps->garray, TRUE);
+ }
+ g_free (all_apps);
+ g_free (all_apps_file_name);
+ g_strfreev (all_apps_split);
+}
+
+static void
+insert_launcher_into_category (CategoryData * cat_data, MateDesktopItem * desktop_item,
+ AppShellData * app_data)
+{
+ GtkWidget *launcher;
+ static GtkSizeGroup *icon_group = NULL;
+
+ gchar *filepath;
+ gchar *filename;
+ GtkWidget *tile_icon;
+
+ if (!icon_group)
+ icon_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ launcher =
+ application_tile_new_full (mate_desktop_item_get_location (desktop_item),
+ app_data->icon_size, app_data->show_tile_generic_name);
+ gtk_widget_set_size_request (launcher, SIZING_TILE_WIDTH, -1);
+
+ filepath =
+ g_strdup (mate_desktop_item_get_string (desktop_item, MATE_DESKTOP_ITEM_EXEC));
+ g_strdelimit (filepath, " ", '\0'); /* just want the file name - no args or replacements */
+ filename = g_strrstr (filepath, "/");
+ if (filename)
+ g_stpcpy (filepath, filename + 1);
+ filename = g_ascii_strdown (filepath, -1);
+ g_free (filepath);
+ g_object_set_data (G_OBJECT (launcher), TILE_EXEC_NAME, filename);
+
+ tile_icon = NAMEPLATE_TILE (launcher)->image;
+ gtk_size_group_add_widget (icon_group, tile_icon);
+
+ g_signal_connect (launcher, "tile-activated", G_CALLBACK (tile_activated_cb), app_data);
+
+ /* Note that this will handle the case of the action being launched via the side panel as
+ well as directly from the context menu of an individual launcher, because they both
+ funnel through tile_button_action_activate.
+ */
+ g_signal_connect (launcher, "tile-action-triggered",
+ G_CALLBACK (handle_menu_action_performed), app_data);
+
+ /* These will be inserted/removed from tables as the filter changes and we dont want them */
+ /* destroyed when they are removed */
+ g_object_ref (launcher);
+
+ /* use alphabetical order instead of the matemenu order. We group all sub items in each top level
+ category together, ignoring sub menus, so we also ignore sub menu layout hints */
+ cat_data->launcher_list =
+ /* g_list_insert (cat_data->launcher_list, launcher, -1); */
+ g_list_insert_sorted (cat_data->launcher_list, launcher, application_launcher_compare);
+ cat_data->filtered_launcher_list =
+ /* g_list_insert (cat_data->filtered_launcher_list, launcher, -1); */
+ g_list_insert_sorted (cat_data->filtered_launcher_list, launcher, application_launcher_compare);
+}
+
+static gint
+application_launcher_compare (gconstpointer a, gconstpointer b)
+{
+ ApplicationTile *launcher1 = APPLICATION_TILE (a);
+ ApplicationTile *launcher2 = APPLICATION_TILE (b);
+
+ gchar *val1 = launcher1->name;
+ gchar *val2 = launcher2->name;
+
+ if (val1 == NULL || val2 == NULL)
+ {
+ g_assert_not_reached ();
+ }
+ return g_ascii_strcasecmp (val1, val2);
+}
+
+static void
+application_launcher_clear_search_bar (AppShellData * app_data)
+{
+ SlabSection *section = SLAB_SECTION (app_data->filter_section);
+ NldSearchBar *search_bar;
+ g_assert (NLD_IS_SEARCH_BAR (section->contents));
+ search_bar = NLD_SEARCH_BAR (section->contents);
+ nld_search_bar_set_text (search_bar, "", TRUE);
+}
+
+/*
+static gint
+category_name_compare (gconstpointer a, gconstpointer b)
+{
+ CategoryData *data = (CategoryData *) a;
+ const gchar *category = b;
+
+ if (category == NULL || data->category == NULL)
+ {
+ g_assert_not_reached ();
+ }
+ return g_ascii_strcasecmp (category, data->category);
+}
+*/
+
+static void
+tile_activated_cb (Tile * tile, TileEvent * event, gpointer user_data)
+{
+ switch (event->type)
+ {
+ case TILE_EVENT_ACTIVATED_SINGLE_CLICK:
+ case TILE_EVENT_ACTIVATED_KEYBOARD:
+ handle_launcher_single_clicked (tile, user_data);
+ break;
+ default:
+ break;
+ }
+
+}
+
+static void
+handle_launcher_single_clicked (Tile * launcher, gpointer data)
+{
+ GApplication *app;
+ AppShellData *app_data = (AppShellData *) data;
+
+ tile_trigger_action (launcher, launcher->actions[APPLICATION_TILE_ACTION_START]);
+
+ if (g_settings_get_boolean (app_data->settings, EXIT_SHELL_ON_ACTION_START))
+ {
+ if (app_data->exit_on_close)
+ {
+ app=g_application_get_default();
+ g_application_quit(app);
+ }
+ else
+ hide_shell (app_data);
+ }
+}
+
+static void
+handle_menu_action_performed (Tile * launcher, TileEvent * event, TileAction * action,
+ gpointer data)
+{
+ GApplication *app;
+ AppShellData *app_data = (AppShellData *) data;
+ gchar *temp;
+
+ temp = NULL;
+ if (action == launcher->actions[APPLICATION_TILE_ACTION_START])
+ {
+ temp = EXIT_SHELL_ON_ACTION_START;
+ }
+
+ else if (action == launcher->actions[APPLICATION_TILE_ACTION_HELP])
+ {
+ temp = EXIT_SHELL_ON_ACTION_HELP;
+ }
+
+ else if (action == launcher->actions[APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU]
+ || action == launcher->actions[APPLICATION_TILE_ACTION_UPDATE_STARTUP])
+ {
+ temp = EXIT_SHELL_ON_ACTION_ADD_REMOVE;
+ }
+
+ else if (action == launcher->actions[APPLICATION_TILE_ACTION_UPGRADE_PACKAGE]
+ || action == launcher->actions[APPLICATION_TILE_ACTION_UNINSTALL_PACKAGE])
+ {
+ temp = EXIT_SHELL_ON_ACTION_UPGRADE_UNINSTALL;
+ }
+
+ if (temp)
+ {
+ if (g_settings_get_boolean (app_data->settings, temp))
+ {
+ if (app_data->exit_on_close)
+ {
+ app=g_application_get_default();
+ g_application_quit(app);
+ }
+ else
+ hide_shell (app_data);
+ }
+ }
+ else
+ g_warning ("Unknown Action");
+}
diff --git a/shell/app-shell.h b/shell/app-shell.h
new file mode 100644
index 00000000..97e22b60
--- /dev/null
+++ b/shell/app-shell.h
@@ -0,0 +1,142 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __APP_SHELL_H__
+#define __APP_SHELL_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#define MATEMENU_I_KNOW_THIS_IS_UNSTABLE
+#include <matemenu-tree.h>
+#include <libmate-desktop/mate-desktop-item.h>
+
+#include "slab-section.h"
+#include "tile.h"
+
+G_BEGIN_DECLS
+
+#define GROUP_POSITION_NUMBER_KEY "Unique Group Position Number"
+#define APP_ACTION_KEY "Unique Application Action Key"
+
+/* constants for initial sizing */
+#define SIZING_SCREEN_WIDTH_LARGE 1024
+#define SIZING_SCREEN_WIDTH_MEDIUM 800
+#define SIZING_SCREEN_WIDTH_SMALL 640
+#define SIZING_SCREEN_WIDTH_LARGE_NUMCOLS 3
+#define SIZING_SCREEN_WIDTH_MEDIUM_NUMCOLS 2
+#define SIZING_SCREEN_WIDTH_SMALL_NUMCOLS 1
+#define SIZING_TILE_WIDTH 230
+#define SIZING_HEIGHT_PERCENT 0.8
+
+typedef struct
+{
+ const gchar *name;
+ gint max_items;
+ GArray *garray;
+} NewAppConfig;
+
+typedef struct _AppShellData
+{
+ GtkWidget *main_app;
+ gint main_app_window_x;
+ gint main_app_window_y;
+ gboolean main_app_window_shown_once;
+
+ GtkWidget *shell;
+ GtkWidget *groups_section;
+
+ GtkWidget *actions_section;
+ /*
+ NULL - if the available actions depend on the current tile selected
+ NON-NULL - a list of AppAction that are always shown
+ */
+ GSList *static_actions;
+
+ GtkWidget *filter_section;
+ gchar *filter_string;
+ GdkCursor *busy_cursor;
+
+ GtkWidget *category_layout;
+ GList *categories_list;
+ GList *cached_tables_list; /* list of currently showing (not filtered out) tables */
+ Tile *last_clicked_launcher;
+ SlabSection *selected_group;
+ GtkIconSize icon_size;
+ const gchar *menu_name;
+ NewAppConfig *new_apps;
+ MateMenuTree *tree;
+ GHashTable *hash;
+
+ guint filter_changed_timeout;
+ gboolean stop_incremental_relayout;
+ GList *incremental_relayout_cat_list;
+ gboolean filtered_out_everything;
+ GtkWidget *filtered_out_everything_widget;
+ GtkLabel *filtered_out_everything_widget_label;
+
+ gboolean show_tile_generic_name;
+ gboolean exit_on_close;
+
+ GSettings *settings;
+} AppShellData;
+
+typedef struct
+{
+ gchar *category;
+ Tile *group_launcher;
+
+ SlabSection *section;
+ GList *launcher_list;
+ GList *filtered_launcher_list;
+} CategoryData;
+
+typedef struct
+{
+ gchar *name;
+ MateDesktopItem *item;
+} AppAction;
+
+typedef struct
+{
+ long time;
+ MateDesktopItem *item;
+} NewAppData;
+
+void generate_categories (AppShellData * app_data);
+
+/* If new_apps_max_items is 0 then the new applications category is not created */
+AppShellData *appshelldata_new (const gchar * menu_name,
+ GtkIconSize icon_size, gboolean show_tile_generic_name, gboolean exit_on_close, gint new_apps_max_items);
+
+void layout_shell (AppShellData * app_data, const gchar * filter_title, const gchar * groups_title,
+ const gchar * actions_title, GSList * actions,
+ void (*actions_handler) (Tile *, TileEvent *, gpointer));
+
+gboolean create_main_window (AppShellData * app_data, const gchar * app_name, const gchar * title,
+ const gchar * window_icon, gint width, gint height, gboolean hidden);
+
+void hide_shell (AppShellData * app_data);
+
+void show_shell (AppShellData * app_data);
+
+G_END_DECLS
+
+#endif /* __APP_SHELL_H__ */
diff --git a/shell/application-tile.c b/shell/application-tile.c
new file mode 100644
index 00000000..ab1b4cf5
--- /dev/null
+++ b/shell/application-tile.c
@@ -0,0 +1,764 @@
+/*
+ * This file is part of libtile.
+ *
+ * Copyright (c) 2006, 2007 Novell, Inc.
+ *
+ * Libtile is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libtile is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "application-tile.h"
+#include "config.h"
+
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+#include <unistd.h>
+
+#include "slab-mate-util.h"
+#include "libslab-utils.h"
+#include "bookmark-agent.h"
+#include "themed-icon.h"
+
+typedef enum {
+ APP_IN_USER_STARTUP_DIR,
+ APP_NOT_IN_STARTUP_DIR,
+ APP_NOT_ELIGIBLE
+} StartupStatus;
+
+static void application_tile_get_property (GObject *, guint, GValue *, GParamSpec *);
+static void application_tile_set_property (GObject *, guint, const GValue *, GParamSpec *);
+static void application_tile_finalize (GObject *);
+
+static void application_tile_setup (ApplicationTile *);
+
+static GtkWidget *create_header (const gchar *);
+static GtkWidget *create_subheader (const gchar *);
+
+static void header_size_allocate_cb (GtkWidget *, GtkAllocation *, gpointer);
+
+static void start_trigger (Tile *, TileEvent *, TileAction *);
+static void help_trigger (Tile *, TileEvent *, TileAction *);
+static void user_apps_trigger (Tile *, TileEvent *, TileAction *);
+static void startup_trigger (Tile *, TileEvent *, TileAction *);
+
+static void add_to_user_list (ApplicationTile *);
+static void remove_from_user_list (ApplicationTile *);
+static void add_to_startup_list (ApplicationTile *);
+static void remove_from_startup_list (ApplicationTile *);
+
+static void update_user_list_menu_item (ApplicationTile *);
+static void agent_notify_cb (GObject *, GParamSpec *, gpointer);
+
+static StartupStatus get_desktop_item_startup_status (MateDesktopItem *);
+static void update_startup_menu_item (ApplicationTile *);
+
+typedef struct {
+ MateDesktopItem *desktop_item;
+
+ gchar *image_id;
+ gboolean image_is_broken;
+ GtkIconSize image_size;
+
+ gboolean show_generic_name;
+ StartupStatus startup_status;
+
+ BookmarkAgent *agent;
+ BookmarkStoreStatus agent_status;
+ gboolean is_bookmarked;
+ gulong notify_signal_id;
+} ApplicationTilePrivate;
+
+enum {
+ PROP_0,
+ PROP_APPLICATION_NAME,
+ PROP_APPLICATION_DESCRIPTION
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ApplicationTile, application_tile, NAMEPLATE_TILE_TYPE)
+
+static void
+application_tile_class_init (ApplicationTileClass *app_tile_class)
+{
+ GObjectClass *g_obj_class = G_OBJECT_CLASS (app_tile_class);
+
+ g_obj_class->get_property = application_tile_get_property;
+ g_obj_class->set_property = application_tile_set_property;
+ g_obj_class->finalize = application_tile_finalize;
+
+ g_object_class_install_property (
+ g_obj_class, PROP_APPLICATION_NAME,
+ g_param_spec_string (
+ "application-name", "application-name",
+ "the name of the application", NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ g_obj_class, PROP_APPLICATION_DESCRIPTION,
+ g_param_spec_string (
+ "application-description", "application-description",
+ "the name of the application", NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+GtkWidget *
+application_tile_new (const gchar *desktop_item_id)
+{
+ return application_tile_new_full (desktop_item_id, GTK_ICON_SIZE_DND, TRUE);
+}
+
+GtkWidget *
+application_tile_new_full (const gchar *desktop_item_id,
+ GtkIconSize image_size, gboolean show_generic_name)
+{
+ ApplicationTile *this;
+ ApplicationTilePrivate *priv;
+
+ const gchar *uri = NULL;
+
+ MateDesktopItem *desktop_item;
+
+ desktop_item = load_desktop_item_from_unknown (desktop_item_id);
+
+ if (
+ desktop_item &&
+ mate_desktop_item_get_entry_type (desktop_item) == MATE_DESKTOP_ITEM_TYPE_APPLICATION
+ )
+ uri = mate_desktop_item_get_location (desktop_item);
+
+ if (! uri) {
+ if (desktop_item)
+ mate_desktop_item_unref (desktop_item);
+
+ return NULL;
+ }
+
+ this = g_object_new (APPLICATION_TILE_TYPE, "tile-uri", uri, NULL);
+ priv = application_tile_get_instance_private (this);
+
+ priv->image_size = image_size;
+ priv->desktop_item = desktop_item;
+ priv->show_generic_name = show_generic_name;
+
+ application_tile_setup (this);
+
+ return GTK_WIDGET (this);
+}
+
+static void
+application_tile_init (ApplicationTile *tile)
+{
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (tile);
+
+ priv->desktop_item = NULL;
+ priv->image_id = NULL;
+ priv->image_is_broken = TRUE;
+
+ priv->agent = NULL;
+ priv->agent_status = BOOKMARK_STORE_ABSENT;
+ priv->is_bookmarked = FALSE;
+ priv->notify_signal_id = 0;
+
+ tile->name = tile->description = NULL;
+}
+
+static void
+application_tile_finalize (GObject *g_object)
+{
+ ApplicationTile *tile = APPLICATION_TILE (g_object);
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (tile);
+
+ if (tile->name) {
+ g_free (tile->name);
+ tile->name = NULL;
+ }
+ if (tile->description) {
+ g_free (tile->description);
+ tile->description = NULL;
+ }
+
+ if (priv->desktop_item) {
+ mate_desktop_item_unref (priv->desktop_item);
+ priv->desktop_item = NULL;
+ }
+ if (priv->image_id) {
+ g_free (priv->image_id);
+ priv->image_id = NULL;
+ }
+
+ if (priv->notify_signal_id)
+ g_signal_handler_disconnect (priv->agent, priv->notify_signal_id);
+
+ g_object_unref (G_OBJECT (priv->agent));
+
+ G_OBJECT_CLASS (application_tile_parent_class)->finalize (g_object);
+}
+
+static void
+application_tile_get_property (GObject *g_obj, guint prop_id, GValue *value, GParamSpec *param_spec)
+{
+ ApplicationTile *tile = APPLICATION_TILE (g_obj);
+
+ switch (prop_id) {
+ case PROP_APPLICATION_NAME:
+ g_value_set_string (value, tile->name);
+ break;
+
+ case PROP_APPLICATION_DESCRIPTION:
+ g_value_set_string (value, tile->description);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+application_tile_set_property (GObject *g_obj, guint prop_id, const GValue *value, GParamSpec *param_spec)
+{
+ ApplicationTile *tile = APPLICATION_TILE (g_obj);
+
+ switch (prop_id) {
+ case PROP_APPLICATION_NAME:
+ if (tile->name)
+ g_free (tile->name);
+ tile->name = g_strdup (g_value_get_string (value));
+ break;
+
+ case PROP_APPLICATION_DESCRIPTION:
+ if (tile->description)
+ g_free (tile->description);
+ tile->description = g_strdup (g_value_get_string (value));
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+application_tile_setup (ApplicationTile *this)
+{
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+
+ GtkWidget *image;
+ GtkWidget *header;
+ GtkWidget *subheader;
+ GtkMenu *context_menu;
+ AtkObject *accessible;
+
+ TileAction **actions;
+ TileAction *action;
+ GtkWidget *menu_item;
+ GtkContainer *menu_ctnr;
+
+ gchar *name;
+ gchar *desc;
+
+ gchar *comment;
+
+ gchar *markup;
+ gchar *str;
+
+ if (! priv->desktop_item) {
+ priv->desktop_item = load_desktop_item_from_unknown (TILE (this)->uri);
+
+ if (! priv->desktop_item)
+ return;
+ }
+
+ priv->image_id = g_strdup (mate_desktop_item_get_localestring (priv->desktop_item, "Icon"));
+ image = themed_icon_new (priv->image_id, priv->image_size);
+
+ gchar *filename = g_filename_from_uri (mate_desktop_item_get_location (priv->desktop_item), NULL, NULL);
+ GKeyFile *keyfile = g_key_file_new ();
+ g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
+
+ name = g_key_file_get_locale_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, "Name", NULL, NULL);
+ desc = g_key_file_get_locale_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, "GenericName", NULL, NULL);
+ comment = g_key_file_get_locale_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, "Comment", NULL, NULL);
+
+ accessible = gtk_widget_get_accessible (GTK_WIDGET (this));
+ if (name)
+ atk_object_set_name (accessible, name);
+ if (desc)
+ atk_object_set_description (accessible, desc);
+
+ header = create_header (name);
+
+ /*if no GenericName then just show and center the Name */
+ if (desc && priv->show_generic_name
+ && (!name || strcmp(name, desc) != 0))
+ subheader = create_subheader (desc);
+ else
+ subheader = NULL;
+
+ context_menu = GTK_MENU (gtk_menu_new ());
+
+ g_object_set (
+ G_OBJECT (this),
+ "nameplate-image", image,
+ "nameplate-header", header,
+ "nameplate-subheader", subheader,
+ "context-menu", context_menu,
+ "application-name", name,
+ "application-description", desc,
+ NULL);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (this), comment);
+
+ priv->agent = bookmark_agent_get_instance (BOOKMARK_STORE_USER_APPS);
+ g_object_get (G_OBJECT (priv->agent), BOOKMARK_AGENT_STORE_STATUS_PROP, & priv->agent_status, NULL);
+
+ priv->notify_signal_id = g_signal_connect (
+ G_OBJECT (priv->agent), "notify", G_CALLBACK (agent_notify_cb), this);
+
+ priv->startup_status = get_desktop_item_startup_status (priv->desktop_item);
+
+ actions = g_new0 (TileAction *, 6);
+
+ TILE (this)->actions = actions;
+ TILE (this)->n_actions = 6;
+
+ menu_ctnr = GTK_CONTAINER (TILE (this)->context_menu);
+
+/* make start action */
+
+ str = g_strdup_printf (_("Start %s"), this->name);
+ markup = g_markup_printf_escaped ("<b>%s</b>", str);
+ action = tile_action_new (TILE (this), start_trigger, markup, TILE_ACTION_OPENS_NEW_WINDOW);
+ actions [APPLICATION_TILE_ACTION_START] = action;
+ g_free (markup);
+ g_free (str);
+
+ menu_item = GTK_WIDGET (tile_action_get_menu_item (action));
+
+ gtk_container_add (menu_ctnr, menu_item);
+
+ TILE (this)->default_action = action;
+
+/* insert separator */
+
+ gtk_container_add (menu_ctnr, gtk_separator_menu_item_new ());
+
+/* make help action */
+
+ if (mate_desktop_item_get_string (priv->desktop_item, "DocPath")) {
+ action = tile_action_new (
+ TILE (this), help_trigger, _("Help"),
+ TILE_ACTION_OPENS_NEW_WINDOW | TILE_ACTION_OPENS_HELP);
+
+ menu_item = GTK_WIDGET (tile_action_get_menu_item (action));
+ gtk_container_add (menu_ctnr, menu_item);
+ }
+ else {
+ action = NULL;
+ }
+
+ actions [APPLICATION_TILE_ACTION_HELP] = action;
+
+/* insert separator */
+
+ if (action != NULL)
+ gtk_container_add (menu_ctnr, gtk_separator_menu_item_new ());
+
+/* make "add/remove to favorites" action */
+
+ update_user_list_menu_item (this);
+
+/* make "add/remove to startup" action */
+
+ if (priv->startup_status != APP_NOT_ELIGIBLE) {
+ action = tile_action_new (TILE (this), startup_trigger, NULL, 0);
+ actions [APPLICATION_TILE_ACTION_UPDATE_STARTUP] = action;
+
+ update_startup_menu_item (this);
+
+ menu_item = GTK_WIDGET (tile_action_get_menu_item (action));
+
+ gtk_container_add (menu_ctnr, menu_item);
+ }
+
+ gtk_widget_show_all (GTK_WIDGET (TILE (this)->context_menu));
+
+ g_free (name);
+ g_free (desc);
+ g_free (comment);
+ g_free (filename);
+ g_key_file_unref (keyfile);
+}
+
+static GtkWidget *
+create_header (const gchar *name)
+{
+ GtkWidget *header;
+
+ header = gtk_label_new (name);
+ gtk_label_set_line_wrap (GTK_LABEL (header), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (header), 0.0);
+
+ g_signal_connect (
+ G_OBJECT (header),
+ "size-allocate",
+ G_CALLBACK (header_size_allocate_cb),
+ NULL);
+
+ return header;
+}
+
+static void
+set_background_color (GtkWidget *widget,
+ GdkRGBA *rgba)
+{
+ gchar *css;
+ GtkCssProvider *provider;
+
+ provider = gtk_css_provider_new ();
+
+ css = g_strdup_printf ("* { background-color: %s;}",
+ gdk_rgba_to_string (rgba));
+ gtk_css_provider_load_from_data (provider, css, -1, NULL);
+ g_free (css);
+
+ gtk_style_context_add_provider (gtk_widget_get_style_context (widget),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ g_object_unref (provider);
+}
+
+static GtkWidget *
+create_subheader (const gchar *desc)
+{
+ GtkWidget *subheader;
+ GtkStyleContext *context;
+ GdkRGBA *rgba = NULL;
+
+ subheader = gtk_label_new (desc);
+ gtk_label_set_ellipsize (GTK_LABEL (subheader), PANGO_ELLIPSIZE_END);
+ gtk_label_set_xalign (GTK_LABEL (subheader), 0.0);
+ context = gtk_widget_get_style_context (subheader);
+ gtk_style_context_get (context,
+ GTK_STATE_FLAG_INSENSITIVE,
+ "background-color", &rgba,
+ NULL);
+
+ set_background_color (subheader, rgba);
+
+ return subheader;
+}
+
+static void
+start_trigger (Tile *tile, TileEvent *event, TileAction *action)
+{
+ ApplicationTile *this = APPLICATION_TILE (tile);
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+ open_desktop_item_exec (priv->desktop_item);
+}
+
+static void
+help_trigger (Tile *tile, TileEvent *event, TileAction *action)
+{
+ ApplicationTile *this = APPLICATION_TILE (tile);
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+ open_desktop_item_help (priv->desktop_item);
+}
+
+static void
+user_apps_trigger (Tile *tile, TileEvent *event, TileAction *action)
+{
+ ApplicationTile *this = APPLICATION_TILE (tile);
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+
+ if (priv->is_bookmarked)
+ remove_from_user_list (this);
+ else
+ add_to_user_list (this);
+
+ update_user_list_menu_item (this);
+}
+
+static void
+add_to_user_list (ApplicationTile *this)
+{
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+
+ BookmarkItem *item;
+
+ item = g_new0 (BookmarkItem, 1);
+ item->uri = TILE (this)->uri;
+ item->mime_type = "application/x-desktop";
+
+ bookmark_agent_add_item (priv->agent, item);
+ g_free (item);
+
+ priv->is_bookmarked = TRUE;
+}
+
+static void
+remove_from_user_list (ApplicationTile *this)
+{
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+
+ bookmark_agent_remove_item (priv->agent, TILE (this)->uri);
+
+ priv->is_bookmarked = FALSE;
+}
+
+static void
+startup_trigger (Tile *tile, TileEvent *event, TileAction *action)
+{
+ ApplicationTile *this = APPLICATION_TILE (tile);
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+
+ switch (priv->startup_status) {
+ case APP_IN_USER_STARTUP_DIR:
+ remove_from_startup_list (this);
+ break;
+
+ case APP_NOT_IN_STARTUP_DIR:
+ add_to_startup_list (this);
+ break;
+
+ default:
+ break;
+ }
+
+ update_startup_menu_item (this);
+}
+
+static void
+add_to_startup_list (ApplicationTile *this)
+{
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+
+ gchar *desktop_item_filename;
+ gchar *desktop_item_basename;
+
+ gchar *startup_dir;
+ gchar *dst_filename;
+
+ const gchar *src_uri;
+ gchar *dst_uri;
+
+ desktop_item_filename =
+ g_filename_from_uri (mate_desktop_item_get_location (priv->desktop_item), NULL,
+ NULL);
+
+ g_return_if_fail (desktop_item_filename != NULL);
+
+ desktop_item_basename = g_path_get_basename (desktop_item_filename);
+
+ startup_dir = g_build_filename (g_get_user_config_dir (), "autostart", NULL);
+
+ if (! g_file_test (startup_dir, G_FILE_TEST_EXISTS))
+ g_mkdir_with_parents (startup_dir, 0700);
+
+ dst_filename = g_build_filename (startup_dir, desktop_item_basename, NULL);
+
+ src_uri = mate_desktop_item_get_location (priv->desktop_item);
+ dst_uri = g_filename_to_uri (dst_filename, NULL, NULL);
+
+ copy_file (src_uri, dst_uri);
+ priv->startup_status = APP_IN_USER_STARTUP_DIR;
+
+ g_free (desktop_item_filename);
+ g_free (desktop_item_basename);
+ g_free (startup_dir);
+ g_free (dst_filename);
+ g_free (dst_uri);
+}
+
+static void
+remove_from_startup_list (ApplicationTile *this)
+{
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+
+ gchar *ditem_filename;
+ gchar *ditem_basename;
+ gchar *src_filename;
+
+ ditem_filename =
+ g_filename_from_uri (mate_desktop_item_get_location (priv->desktop_item), NULL,
+ NULL);
+
+ g_return_if_fail (ditem_filename != NULL);
+
+ ditem_basename = g_path_get_basename (ditem_filename);
+
+ src_filename = g_build_filename (g_get_user_config_dir (), "autostart", ditem_basename, NULL);
+
+ priv->startup_status = APP_NOT_IN_STARTUP_DIR;
+ if (g_file_test (src_filename, G_FILE_TEST_EXISTS))
+ {
+ if(g_file_test (src_filename, G_FILE_TEST_IS_DIR))
+ g_assert_not_reached ();
+ g_unlink (src_filename);
+ }
+
+ g_free (ditem_filename);
+ g_free (ditem_basename);
+ g_free (src_filename);
+}
+
+MateDesktopItem *
+application_tile_get_desktop_item (ApplicationTile *tile)
+{
+ ApplicationTilePrivate *priv;
+
+ priv = application_tile_get_instance_private (tile);
+ return priv->desktop_item;
+}
+
+static void
+update_user_list_menu_item (ApplicationTile *this)
+{
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+
+ TileAction *action;
+ GtkWidget *item;
+
+ if (priv->agent_status == BOOKMARK_STORE_ABSENT) {
+ if (TILE (this)->actions [APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU])
+ g_object_unref (TILE (this)->actions [APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU]);
+
+ TILE (this)->actions [APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU] = NULL;
+ }
+ else if (! TILE (this)->actions [APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU]) {
+ TILE (this)->actions [APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU] =
+ tile_action_new (TILE (this), user_apps_trigger, NULL, 0);
+
+ tile_action_set_menu_item_label (
+ TILE (this)->actions [APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU], "blah");
+
+ item = GTK_WIDGET (tile_action_get_menu_item (
+ TILE (this)->actions [APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU]));
+ gtk_menu_shell_insert (GTK_MENU_SHELL (TILE (this)->context_menu), item, 4);
+
+ gtk_widget_show_all (item);
+ }
+ else {
+ /* do nothing */ ;
+ }
+
+ action = TILE (this)->actions [APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU];
+
+ if (! action)
+ return;
+
+ priv->is_bookmarked = bookmark_agent_has_item (priv->agent, TILE (this)->uri);
+
+ if (priv->is_bookmarked)
+ tile_action_set_menu_item_label (action, _("Remove from Favorites"));
+ else
+ tile_action_set_menu_item_label (action, _("Add to Favorites"));
+
+ item = GTK_WIDGET (tile_action_get_menu_item (action));
+
+ if (! GTK_IS_MENU_ITEM (item))
+ return;
+
+ g_object_get (G_OBJECT (priv->agent), BOOKMARK_AGENT_STORE_STATUS_PROP, & priv->agent_status, NULL);
+
+ gtk_widget_set_sensitive (item, (priv->agent_status != BOOKMARK_STORE_DEFAULT_ONLY));
+}
+
+static StartupStatus
+get_desktop_item_startup_status (MateDesktopItem *desktop_item)
+{
+ gchar *filename;
+ gchar *basename;
+
+ const gchar * const * global_dirs;
+ gchar *global_target;
+ gchar *user_target;
+
+ StartupStatus retval;
+ gint x;
+
+ filename = g_filename_from_uri (mate_desktop_item_get_location (desktop_item), NULL, NULL);
+ if (!filename)
+ return APP_NOT_ELIGIBLE;
+ basename = g_path_get_basename (filename);
+
+ retval = APP_NOT_IN_STARTUP_DIR;
+ global_dirs = g_get_system_config_dirs();
+ for(x=0; global_dirs[x]; x++)
+ {
+ global_target = g_build_filename (global_dirs[x], "autostart", basename, NULL);
+ if (g_file_test (global_target, G_FILE_TEST_EXISTS))
+ {
+ retval = APP_NOT_ELIGIBLE;
+ g_free (global_target);
+ break;
+ }
+ g_free (global_target);
+ }
+
+ /* mate-session currently checks these dirs also. see startup-programs.c */
+ if (retval != APP_NOT_ELIGIBLE)
+ {
+ global_dirs = g_get_system_data_dirs();
+ for(x=0; global_dirs[x]; x++)
+ {
+ global_target = g_build_filename (global_dirs[x], "mate", "autostart", basename, NULL);
+ if (g_file_test (global_target, G_FILE_TEST_EXISTS))
+ {
+ retval = APP_NOT_ELIGIBLE;
+ g_free (global_target);
+ break;
+ }
+ g_free (global_target);
+ }
+ }
+
+ if (retval != APP_NOT_ELIGIBLE)
+ {
+ user_target = g_build_filename (g_get_user_config_dir (), "autostart", basename, NULL);
+ if (g_file_test (user_target, G_FILE_TEST_EXISTS))
+ retval = APP_IN_USER_STARTUP_DIR;
+ g_free (user_target);
+ }
+
+ g_free (basename);
+ g_free (filename);
+
+ return retval;
+}
+
+static void
+update_startup_menu_item (ApplicationTile *this)
+{
+ TileAction *action = TILE (this)->actions [APPLICATION_TILE_ACTION_UPDATE_STARTUP];
+ ApplicationTilePrivate *priv = application_tile_get_instance_private (this);
+
+ if (!action)
+ return;
+
+ if (priv->startup_status == APP_IN_USER_STARTUP_DIR)
+ tile_action_set_menu_item_label (action, _("Remove from Startup Programs"));
+ else
+ tile_action_set_menu_item_label (action, _("Add to Startup Programs"));
+}
+
+static void
+header_size_allocate_cb (GtkWidget *widget, GtkAllocation *alloc, gpointer user_data)
+{
+ gtk_widget_set_size_request (widget, alloc->width, -1);
+}
+
+static void
+agent_notify_cb (GObject *g_obj, GParamSpec *pspec, gpointer user_data)
+{
+ update_user_list_menu_item (APPLICATION_TILE (user_data));
+}
diff --git a/shell/application-tile.h b/shell/application-tile.h
new file mode 100644
index 00000000..dea0bde9
--- /dev/null
+++ b/shell/application-tile.h
@@ -0,0 +1,67 @@
+/*
+ * This file is part of libtile.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libtile is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libtile is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __APPLICATION_TILE_H__
+#define __APPLICATION_TILE_H__
+
+#include "nameplate-tile.h"
+
+#include <glib.h>
+#include <libmate-desktop/mate-desktop-item.h>
+
+G_BEGIN_DECLS
+
+#define APPLICATION_TILE_TYPE (application_tile_get_type ())
+#define APPLICATION_TILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), APPLICATION_TILE_TYPE, ApplicationTile))
+#define APPLICATION_TILE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), APPLICATION_TILE_TYPE, ApplicationTileClass))
+#define IS_APPLICATION_TILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), APPLICATION_TILE_TYPE))
+#define IS_APPLICATION_TILE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), APPLICATION_TILE_TYPE))
+#define APPLICATION_TILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), APPLICATION_TILE_TYPE, ApplicationTileClass))
+#define APPLICATION_TILE_ACTION_START 0
+#define APPLICATION_TILE_ACTION_HELP 1
+#define APPLICATION_TILE_ACTION_UPDATE_MAIN_MENU 2
+#define APPLICATION_TILE_ACTION_UPDATE_STARTUP 3
+#define APPLICATION_TILE_ACTION_UPGRADE_PACKAGE 4
+#define APPLICATION_TILE_ACTION_UNINSTALL_PACKAGE 5
+
+typedef struct
+{
+ NameplateTile nameplate_tile;
+
+ gchar *name;
+ gchar *description;
+} ApplicationTile;
+
+typedef struct
+{
+ NameplateTileClass nameplate_tile_class;
+} ApplicationTileClass;
+
+GType application_tile_get_type (void);
+
+GtkWidget *application_tile_new (const gchar * desktop_item_id);
+GtkWidget *application_tile_new_full (const gchar * desktop_item_id,
+ GtkIconSize icon_size, gboolean show_generic_name);
+
+MateDesktopItem *application_tile_get_desktop_item (ApplicationTile * tile);
+
+G_END_DECLS
+
+#endif /* __APPLICATION_TILE_H__ */
diff --git a/shell/bookmark-agent.c b/shell/bookmark-agent.c
new file mode 100644
index 00000000..0f5f1bee
--- /dev/null
+++ b/shell/bookmark-agent.c
@@ -0,0 +1,1244 @@
+/*
+ * This file is part of the Main Menu.
+ *
+ * Copyright (c) 2007 Novell, Inc.
+ *
+ * The Main Menu is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * The Main Menu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * the Main Menu; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "bookmark-agent.h"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#else
+# define PACKAGE "mate-main-menu"
+#endif
+
+#include <gtk/gtk.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#include "libslab-utils.h"
+
+#define USER_APPS_STORE_FILE_NAME "applications.xbel"
+#define USER_DOCS_STORE_FILE_NAME "documents.xbel"
+#define USER_DIRS_STORE_FILE_NAME "places.xbel"
+#define SYSTEM_STORE_FILE_NAME "system-items.xbel"
+#define CALC_TEMPLATE_FILE_NAME "empty.ots"
+#define WRITER_TEMPLATE_FILE_NAME "empty.ott"
+
+#define GTK_BOOKMARKS_FILE "bookmarks"
+
+#define TYPE_IS_RECENT(type) ((type) == BOOKMARK_STORE_RECENT_APPS || (type) == BOOKMARK_STORE_RECENT_DOCS)
+
+typedef struct {
+ BookmarkStoreType type;
+
+ BookmarkItem **items;
+ gint n_items;
+ BookmarkStoreStatus status;
+
+ GBookmarkFile *store;
+ gboolean needs_sync;
+
+ gchar *store_path;
+ gchar *user_store_path;
+ gboolean user_modifiable;
+ gboolean reorderable;
+ const gchar *store_filename;
+
+ GFileMonitor *store_monitor;
+ GFileMonitor *user_store_monitor;
+
+ void (* update_path) (BookmarkAgent *);
+ void (* load_store) (BookmarkAgent *);
+ void (* save_store) (BookmarkAgent *);
+ void (* create_item) (BookmarkAgent *, const gchar *);
+
+ gchar *gtk_store_path;
+ GFileMonitor *gtk_store_monitor;
+} BookmarkAgentPrivate;
+
+enum {
+ PROP_0,
+ PROP_ITEMS,
+ PROP_STATUS
+};
+
+static BookmarkAgent *instances [BOOKMARK_STORE_N_TYPES];
+
+static BookmarkAgentClass *bookmark_agent_parent_class = NULL;
+
+static void bookmark_agent_base_init (BookmarkAgentClass *);
+static void bookmark_agent_class_init (BookmarkAgentClass *);
+static void bookmark_agent_init (BookmarkAgent *);
+static BookmarkAgent *bookmark_agent_new (BookmarkStoreType );
+
+static void get_property (GObject *, guint, GValue *, GParamSpec *);
+static void set_property (GObject *, guint, const GValue *, GParamSpec *);
+static void finalize (GObject *);
+
+static void update_agent (BookmarkAgent *);
+static void update_items (BookmarkAgent *);
+static void save_store (BookmarkAgent *);
+static gint get_rank (BookmarkAgent *, const gchar *);
+static void set_rank (BookmarkAgent *, const gchar *, gint);
+
+static void load_xbel_store (BookmarkAgent *);
+static void load_places_store (BookmarkAgent *);
+static void update_user_spec_path (BookmarkAgent *);
+static void save_xbel_store (BookmarkAgent *);
+static void create_app_item (BookmarkAgent *, const gchar *);
+static void create_doc_item (BookmarkAgent *, const gchar *);
+static void create_dir_item (BookmarkAgent *, const gchar *);
+
+static void store_monitor_cb (GFileMonitor *, GFile *, GFile *,
+ GFileMonitorEvent, gpointer);
+static void weak_destroy_cb (gpointer, GObject *);
+
+static gchar *find_package_data_file (const gchar *filename);
+
+static gint BookmarkAgent_private_offset;
+
+static inline gpointer bookmark_agent_get_instance_private (BookmarkAgent *this)
+{
+ return (G_STRUCT_MEMBER_P (this, BookmarkAgent_private_offset));
+}
+
+GType
+bookmark_agent_get_type ()
+{
+ static GType g_define_type_id = 0;
+
+ if (G_UNLIKELY (g_define_type_id == 0)) {
+ static const GTypeInfo info = {
+ sizeof (BookmarkAgentClass),
+ (GBaseInitFunc) bookmark_agent_base_init,
+ NULL,
+ (GClassInitFunc) bookmark_agent_class_init,
+ NULL, NULL,
+ sizeof (BookmarkAgent), 0,
+ (GInstanceInitFunc) bookmark_agent_init,
+ NULL
+ };
+
+ g_define_type_id = g_type_register_static (
+ G_TYPE_OBJECT, "BookmarkAgent", & info, 0);
+ G_ADD_PRIVATE (BookmarkAgent);
+ }
+
+ return g_define_type_id;
+}
+
+gboolean
+bookmark_agent_has_item (BookmarkAgent *this, const gchar *uri)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+ return g_bookmark_file_has_item (priv->store, uri);
+}
+
+void
+bookmark_agent_add_item (BookmarkAgent *this, const BookmarkItem *item)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ if (! item)
+ return;
+
+ g_return_if_fail (priv->user_modifiable);
+ g_return_if_fail (item->uri);
+ g_return_if_fail (item->mime_type);
+
+ g_bookmark_file_set_mime_type (priv->store, item->uri, item->mime_type);
+
+ if (item->mtime)
+#if GLIB_CHECK_VERSION(2,66,0)
+ g_bookmark_file_set_modified_date_time (priv->store, item->uri, item->mtime);
+#else
+ g_bookmark_file_set_modified (priv->store, item->uri, item->mtime);
+#endif
+
+ if (item->title)
+ g_bookmark_file_set_title (priv->store, item->uri, item->title);
+
+ g_bookmark_file_add_application (priv->store, item->uri, item->app_name, item->app_exec);
+
+ set_rank (this, item->uri, g_bookmark_file_get_size (priv->store) - 1);
+
+ save_store (this);
+}
+
+void
+bookmark_agent_move_item (BookmarkAgent *this, const gchar *uri, const gchar *uri_new)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ GError *error = NULL;
+
+ if (! TYPE_IS_RECENT (priv->type))
+ return;
+
+ gtk_recent_manager_move_item (gtk_recent_manager_get_default (), uri, uri_new, &error);
+ if (error) {
+ g_warning ("Unable to update %s with renamed file, [%s] -> [%s]: %s",
+ priv->store_path, uri, uri_new, error->message);
+ g_error_free (error);
+ }
+}
+
+void
+bookmark_agent_purge_items (BookmarkAgent *this)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ GError *error = NULL;
+
+ gchar **uris = NULL;
+ gsize uris_len;
+ gint i;
+ g_return_if_fail (priv->user_modifiable);
+
+ uris = g_bookmark_file_get_uris (priv->store, &uris_len);
+ if (TYPE_IS_RECENT (priv->type)) {
+ for (i = 0; i < uris_len; i++) {
+ gtk_recent_manager_remove_item (gtk_recent_manager_get_default (), uris [i], &error);
+ if (error) {
+ g_warning ("Unable to remove [%s] from %s: %s",
+ priv->store_path, uris [i], error->message);
+ g_error_free (error);
+ }
+ }
+ } else {
+ for (i = 0; i < uris_len; i++) {
+ g_bookmark_file_remove_item (priv->store, uris [i], NULL);
+ }
+ save_store (this);
+ }
+ g_strfreev (uris);
+}
+
+void
+bookmark_agent_remove_item (BookmarkAgent *this, const gchar *uri)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+ gint rank;
+
+ GError *error = NULL;
+
+ gchar **uris = NULL;
+ gint rank_i;
+ gint i;
+
+ g_return_if_fail (priv->user_modifiable);
+
+ if (! bookmark_agent_has_item (this, uri))
+ return;
+
+ if (TYPE_IS_RECENT (priv->type)) {
+ gtk_recent_manager_remove_item (gtk_recent_manager_get_default (), uri, &error);
+ if (error) {
+ g_warning ("Unable to remove [%s] from %s: %s", priv->store_path, uri, error->message);
+ g_error_free (error);
+ }
+ }
+ else {
+ rank = get_rank (this, uri);
+
+ g_bookmark_file_remove_item (priv->store, uri, NULL);
+
+ if (rank >= 0) {
+ uris = g_bookmark_file_get_uris (priv->store, NULL);
+
+ for (i = 0; uris && uris [i]; ++i) {
+ rank_i = get_rank (this, uris [i]);
+
+ if (rank_i > rank)
+ set_rank (this, uris [i], rank_i - 1);
+ }
+
+ g_strfreev (uris);
+ }
+
+ save_store (this);
+ }
+}
+
+void
+bookmark_agent_reorder_items (BookmarkAgent *this, const gchar **uris)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gint i;
+
+ g_return_if_fail (priv->reorderable);
+
+ for (i = 0; uris && uris [i]; ++i)
+ set_rank (this, uris [i], i);
+
+ save_store (this);
+}
+
+#if !GLIB_CHECK_VERSION(2,66,0)
+static gint
+recent_item_mru_comp_func (gconstpointer a, gconstpointer b)
+{
+ return ((BookmarkItem *) b)->mtime - ((BookmarkItem *) a)->mtime;
+}
+#endif
+
+static GList *
+make_items_from_bookmark_file (BookmarkAgent *this, GBookmarkFile *store)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+ gchar **uris;
+ gint i;
+ GList *items_ordered;
+
+ if (!store)
+ return NULL;
+
+ uris = g_bookmark_file_get_uris (store, NULL);
+ items_ordered = NULL;
+
+ for (i = 0; uris && uris [i]; ++i) {
+ gboolean include;
+
+ if (priv->type == BOOKMARK_STORE_RECENT_APPS)
+ include = g_bookmark_file_has_group (store, uris [i], "recently-used-apps", NULL);
+ else
+ include = ! g_bookmark_file_get_is_private (store, uris [i], NULL);
+
+ if (include) {
+ BookmarkItem *item;
+
+ item = g_new0 (BookmarkItem, 1);
+
+ item->uri = g_strdup (uris [i]);
+ item->mime_type = g_bookmark_file_get_mime_type (store, uris [i], NULL);
+#if GLIB_CHECK_VERSION(2,66,0)
+ item->mtime = g_bookmark_file_get_modified_date_time (store, uris [i], NULL);
+#else
+ item->mtime = g_bookmark_file_get_modified (store, uris [i], NULL);
+#endif
+
+ items_ordered = g_list_prepend (items_ordered, item);
+ }
+ }
+
+#if GLIB_CHECK_VERSION(2,66,0)
+ items_ordered = g_list_sort (items_ordered, g_date_time_compare);
+#else
+ items_ordered = g_list_sort (items_ordered, recent_item_mru_comp_func);
+#endif
+ g_strfreev (uris);
+
+ return items_ordered;
+}
+
+void
+bookmark_agent_update_from_bookmark_file (BookmarkAgent *this, GBookmarkFile *store)
+{
+ BookmarkAgentPrivate *priv;
+ GList *items_ordered;
+ GList *node;
+
+ g_return_if_fail (IS_BOOKMARK_AGENT (this));
+
+ priv = bookmark_agent_get_instance_private (this);
+
+ items_ordered = make_items_from_bookmark_file (this, store);
+
+ g_bookmark_file_free (priv->store);
+ priv->store = g_bookmark_file_new ();
+
+ for (node = items_ordered; node; node = node->next) {
+ BookmarkItem *item;
+
+ item = (BookmarkItem *) node->data;
+
+ g_bookmark_file_set_mime_type (priv->store, item->uri, item->mime_type);
+ #if GLIB_CHECK_VERSION(2,66,0)
+ g_bookmark_file_set_modified_date_time (priv->store, item->uri, item->mtime);
+ #else
+ g_bookmark_file_set_modified (priv->store, item->uri, item->mtime);
+ #endif
+
+ bookmark_item_free (item);
+ }
+
+ g_list_free (items_ordered);
+
+ update_items (this);
+}
+
+void
+bookmark_item_free (BookmarkItem *item)
+{
+ if (! item)
+ return;
+
+ g_free (item->uri);
+ g_free (item->title);
+ g_free (item->mime_type);
+ g_free (item->icon);
+ g_free (item->app_name);
+ g_free (item->app_exec);
+ g_free (item);
+}
+
+static void
+bookmark_agent_base_init (BookmarkAgentClass *this_class)
+{
+ gint i;
+
+ for (i = 0; i < BOOKMARK_STORE_N_TYPES; ++i)
+ instances [i] = NULL;
+}
+
+static void
+bookmark_agent_class_init (BookmarkAgentClass *this_class)
+{
+ GObjectClass *g_obj_class = G_OBJECT_CLASS (this_class);
+
+ GParamSpec *items_pspec;
+ GParamSpec *status_pspec;
+
+ if (BookmarkAgent_private_offset != 0)
+ g_type_class_adjust_private_offset (this_class, &BookmarkAgent_private_offset);
+
+ g_obj_class->get_property = get_property;
+ g_obj_class->set_property = set_property;
+ g_obj_class->finalize = finalize;
+
+ items_pspec = g_param_spec_pointer (
+ BOOKMARK_AGENT_ITEMS_PROP, BOOKMARK_AGENT_ITEMS_PROP,
+ "the null-terminated list which contains the bookmark items in this store",
+ G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+
+ status_pspec = g_param_spec_int (
+ BOOKMARK_AGENT_STORE_STATUS_PROP, BOOKMARK_AGENT_STORE_STATUS_PROP, "the status of the store",
+ BOOKMARK_STORE_DEFAULT_ONLY, BOOKMARK_STORE_USER, BOOKMARK_STORE_DEFAULT,
+ G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+
+ g_object_class_install_property (g_obj_class, PROP_ITEMS, items_pspec);
+ g_object_class_install_property (g_obj_class, PROP_STATUS, status_pspec);
+
+ bookmark_agent_parent_class = g_type_class_peek_parent (this_class);
+}
+
+static void
+bookmark_agent_init (BookmarkAgent *this)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ priv->type = -1;
+
+ priv->items = NULL;
+ priv->n_items = 0;
+ priv->status = BOOKMARK_STORE_ABSENT;
+
+ priv->store = NULL;
+ priv->needs_sync = FALSE;
+
+ priv->store_path = NULL;
+ priv->user_store_path = NULL;
+ priv->user_modifiable = FALSE;
+ priv->reorderable = FALSE;
+ priv->store_filename = NULL;
+
+ priv->store_monitor = NULL;
+ priv->user_store_monitor = NULL;
+
+ priv->update_path = NULL;
+ priv->load_store = NULL;
+ priv->save_store = NULL;
+ priv->create_item = NULL;
+
+ priv->gtk_store_path = NULL;
+ priv->gtk_store_monitor = NULL;
+}
+
+static BookmarkAgent *
+bookmark_agent_new (BookmarkStoreType type)
+{
+ BookmarkAgent *this;
+ BookmarkAgentPrivate *priv;
+ GFile *gtk_store_file;
+
+ this = g_object_new (BOOKMARK_AGENT_TYPE, NULL);
+ priv = bookmark_agent_get_instance_private (this);
+
+ priv->type = type;
+ priv->store = g_bookmark_file_new ();
+
+ switch (type) {
+ case BOOKMARK_STORE_USER_APPS:
+ priv->store_filename = USER_APPS_STORE_FILE_NAME;
+ priv->create_item = create_app_item;
+
+ break;
+
+ case BOOKMARK_STORE_USER_DOCS:
+ priv->store_filename = USER_DOCS_STORE_FILE_NAME;
+ priv->create_item = create_doc_item;
+
+ break;
+
+ case BOOKMARK_STORE_USER_DIRS:
+ priv->store_filename = USER_DIRS_STORE_FILE_NAME;
+ priv->create_item = create_dir_item;
+
+ priv->user_modifiable = TRUE;
+ priv->reorderable = FALSE;
+
+ priv->load_store = load_places_store;
+
+ priv->gtk_store_path = g_build_filename (g_get_user_config_dir (),
+ "gtk-3.0", GTK_BOOKMARKS_FILE, NULL);
+ gtk_store_file = g_file_new_for_path (priv->gtk_store_path);
+ priv->gtk_store_monitor = g_file_monitor_file (gtk_store_file,
+ 0, NULL, NULL);
+ if (priv->gtk_store_monitor) {
+ g_signal_connect (priv->gtk_store_monitor, "changed",
+ G_CALLBACK (store_monitor_cb), this);
+ }
+
+ g_object_unref (gtk_store_file);
+
+ break;
+
+ case BOOKMARK_STORE_RECENT_APPS:
+ case BOOKMARK_STORE_RECENT_DOCS:
+ priv->user_modifiable = TRUE;
+ priv->reorderable = FALSE;
+
+ priv->store_path = g_build_filename (g_get_user_data_dir (), "recently-used.xbel", NULL);
+
+ break;
+
+ case BOOKMARK_STORE_SYSTEM:
+ priv->store_filename = SYSTEM_STORE_FILE_NAME;
+ priv->create_item = create_app_item;
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (
+ type == BOOKMARK_STORE_USER_APPS || type == BOOKMARK_STORE_USER_DOCS ||
+ type == BOOKMARK_STORE_USER_DIRS || type == BOOKMARK_STORE_SYSTEM)
+ {
+ priv->user_modifiable = TRUE;
+
+ priv->user_store_path = g_build_filename (
+ g_get_user_data_dir (), PACKAGE, priv->store_filename, NULL);
+
+ priv->update_path = update_user_spec_path;
+ }
+
+ if (type == BOOKMARK_STORE_USER_APPS || type == BOOKMARK_STORE_USER_DOCS || type == BOOKMARK_STORE_SYSTEM) {
+ priv->reorderable = TRUE;
+ priv->load_store = load_xbel_store;
+ priv->save_store = save_xbel_store;
+ }
+
+ update_agent (this);
+
+ return this;
+}
+
+BookmarkAgent *
+bookmark_agent_get_instance (BookmarkStoreType type)
+{
+ g_return_val_if_fail (0 <= type, NULL);
+ g_return_val_if_fail (type < BOOKMARK_STORE_N_TYPES, NULL);
+
+ if (! instances [type]) {
+ instances [type] = bookmark_agent_new (type);
+ g_object_weak_ref (G_OBJECT (instances [type]), weak_destroy_cb, GINT_TO_POINTER (type));
+ }
+ else
+ g_object_ref (G_OBJECT (instances [type]));
+
+ return instances [type];
+}
+
+static void
+get_property (GObject *g_obj, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ BookmarkAgent *this = BOOKMARK_AGENT (g_obj);
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ switch (prop_id) {
+ case PROP_ITEMS:
+ g_value_set_pointer (value, priv->items);
+ break;
+
+ case PROP_STATUS:
+ g_value_set_int (value, priv->status);
+ break;
+ }
+}
+
+static void
+set_property (GObject *g_obj, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ /* no writeable properties */
+}
+
+static void
+finalize (GObject *g_obj)
+{
+ BookmarkAgent *this = BOOKMARK_AGENT (g_obj);
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gint i;
+
+ for (i = 0; priv->items && priv->items [i]; ++i)
+ bookmark_item_free (priv->items [i]);
+
+ g_free (priv->items);
+ g_free (priv->store_path);
+ g_free (priv->user_store_path);
+ g_free (priv->gtk_store_path);
+
+ if (priv->store_monitor) {
+ g_signal_handlers_disconnect_by_func (priv->store_monitor, store_monitor_cb, this);
+ g_file_monitor_cancel (priv->store_monitor);
+ g_object_unref (priv->store_monitor);
+ }
+
+ if (priv->user_store_monitor) {
+ g_signal_handlers_disconnect_by_func (priv->user_store_monitor, store_monitor_cb, this);
+ g_file_monitor_cancel (priv->user_store_monitor);
+ g_object_unref (priv->user_store_monitor);
+ }
+
+ if (priv->gtk_store_monitor) {
+ g_signal_handlers_disconnect_by_func (priv->gtk_store_monitor, store_monitor_cb, this);
+ g_file_monitor_cancel (priv->gtk_store_monitor);
+ g_object_unref (priv->gtk_store_monitor);
+ }
+
+ g_bookmark_file_free (priv->store);
+
+ G_OBJECT_CLASS (bookmark_agent_parent_class)->finalize (g_obj);
+}
+
+static void
+update_agent (BookmarkAgent *this)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ if (priv->update_path)
+ priv->update_path (this);
+
+ if (priv->load_store)
+ priv->load_store (this);
+
+ update_items (this);
+}
+
+static void
+update_items (BookmarkAgent *this)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gchar **uris = NULL;
+ gchar **uris_ordered = NULL;
+ gsize n_uris = 0;
+ gint rank = -1;
+ gint rank_corr = -1;
+ gboolean needs_update = FALSE;
+ gboolean store_corrupted = FALSE;
+ gchar *new_title, *old_title;
+
+ gint i;
+
+ uris = g_bookmark_file_get_uris (priv->store, & n_uris);
+ uris_ordered = g_new0 (gchar *, n_uris + 1);
+ uris_ordered [n_uris] = NULL;
+
+ for (i = 0; uris && uris [i]; ++i) {
+ rank = get_rank (this, uris [i]);
+
+ if (rank < 0 || rank >= n_uris)
+ rank = i;
+
+ if (uris_ordered [rank]) {
+ store_corrupted = TRUE;
+ rank_corr = rank;
+
+ for (rank = 0; rank < n_uris; ++rank)
+ if (! uris_ordered [rank])
+ break;
+
+ g_warning (
+ "store corruption [%s] - multiple uris with same rank (%d): [%s] [%s], moving latter to %d",
+ priv->store_path, rank_corr, uris_ordered [rank_corr], uris [i], rank);
+ }
+
+ set_rank (this, uris [i], rank);
+
+ uris_ordered [rank] = uris [i];
+ }
+
+ if (priv->n_items != n_uris)
+ needs_update = TRUE;
+
+ for (i = 0; ! needs_update && uris_ordered && uris_ordered [i]; ++i) {
+ if (priv->type == BOOKMARK_STORE_USER_DIRS) {
+ new_title = g_bookmark_file_get_title (priv->store, uris_ordered [i], NULL);
+ old_title = priv->items [i]->title;
+ if (!new_title && !old_title) {
+ if (strcmp (priv->items [i]->uri, uris_ordered [i]))
+ needs_update = TRUE;
+ }
+ else if ((new_title && !old_title) || (!new_title && old_title))
+ needs_update = TRUE;
+ else if (strcmp (old_title, new_title))
+ needs_update = TRUE;
+ g_free (new_title);
+ }
+ else if (strcmp (priv->items [i]->uri, uris_ordered [i]))
+ needs_update = TRUE;
+ }
+
+ if (needs_update) {
+ for (i = 0; priv->items && priv->items [i]; ++i)
+ bookmark_item_free (priv->items [i]);
+
+ g_free (priv->items);
+
+ priv->n_items = n_uris;
+ priv->items = g_new0 (BookmarkItem *, priv->n_items + 1);
+
+ for (i = 0; uris_ordered && uris_ordered [i]; ++i) {
+ priv->items [i] = g_new0 (BookmarkItem, 1);
+ priv->items [i]->uri = g_strdup (uris_ordered [i]);
+ priv->items [i]->title = g_bookmark_file_get_title (priv->store, uris_ordered [i], NULL);
+ priv->items [i]->mime_type = g_bookmark_file_get_mime_type (priv->store, uris_ordered [i], NULL);
+ #if GLIB_CHECK_VERSION(2,66,0)
+ priv->items [i]->mtime = g_bookmark_file_get_modified_date_time (priv->store, uris_ordered [i], NULL);
+ #else
+ priv->items [i]->mtime = g_bookmark_file_get_modified (priv->store, uris_ordered [i], NULL);
+ #endif
+ priv->items [i]->app_name = NULL;
+ priv->items [i]->app_exec = NULL;
+
+ g_bookmark_file_get_icon (priv->store, uris_ordered [i], & priv->items [i]->icon, NULL, NULL);
+ }
+
+ /* Since the bookmark store for recently-used items is updated by the caller of BookmarkAgent,
+ * we don't emit notifications in that case. The caller will know when to update itself.
+ */
+ if (!TYPE_IS_RECENT (priv->type))
+ g_object_notify (G_OBJECT (this), BOOKMARK_AGENT_ITEMS_PROP);
+ }
+
+ if (store_corrupted)
+ save_store (this);
+
+ g_strfreev (uris);
+ g_free (uris_ordered);
+}
+
+static void
+save_store (BookmarkAgent *this)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gchar *dir;
+
+ g_return_if_fail (priv->user_modifiable);
+
+ priv->needs_sync = TRUE;
+ priv->update_path (this);
+
+ dir = g_path_get_dirname (priv->store_path);
+ g_mkdir_with_parents (dir, 0700);
+ g_free (dir);
+
+ priv->save_store (this);
+ update_items (this);
+}
+
+static gint
+get_rank (BookmarkAgent *this, const gchar *uri)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gchar **groups;
+ gint rank;
+
+ gint i;
+
+ if (! priv->reorderable)
+ return -1;
+
+ groups = g_bookmark_file_get_groups (priv->store, uri, NULL, NULL);
+ rank = -1;
+
+ for (i = 0; groups && groups [i]; ++i) {
+ if (g_str_has_prefix (groups [i], "rank-")) {
+ if (rank >= 0)
+ g_warning (
+ "store corruption - multiple ranks for same uri: [%s] [%s]",
+ priv->store_path, uri);
+
+ rank = atoi (& groups [i] [5]);
+ }
+ }
+
+ g_strfreev (groups);
+
+ return rank;
+}
+
+static void
+set_rank (BookmarkAgent *this, const gchar *uri, gint rank)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gchar **groups;
+ gchar *group;
+
+ gint i;
+
+ if (! (priv->reorderable && bookmark_agent_has_item (this, uri)))
+ return;
+
+ groups = g_bookmark_file_get_groups (priv->store, uri, NULL, NULL);
+
+ for (i = 0; groups && groups [i]; ++i)
+ if (g_str_has_prefix (groups [i], "rank-"))
+ g_bookmark_file_remove_group (priv->store, uri, groups [i], NULL);
+
+ g_strfreev (groups);
+
+ group = g_strdup_printf ("rank-%d", rank);
+ g_bookmark_file_add_group (priv->store, uri, group);
+ g_free (group);
+}
+
+static void
+load_xbel_store (BookmarkAgent *this)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gchar **uris = NULL;
+
+ GError *error = NULL;
+
+ gint i;
+ gboolean success;
+
+ if (!priv->store_path)
+ success = FALSE;
+ else {
+ success = g_bookmark_file_load_from_file (priv->store, priv->store_path, & error);
+ }
+
+ if (!success) {
+ g_bookmark_file_free (priv->store);
+ priv->store = g_bookmark_file_new ();
+
+ if (error) {
+ g_debug ("Couldn't load bookmark file [%s]: %s", priv->store_path, error->message);
+ g_error_free (error);
+ } else {
+ g_debug ("Couldn't load bookmark file [NULL]");
+ }
+ return;
+ }
+
+ uris = g_bookmark_file_get_uris (priv->store, NULL);
+
+ for (i = 0; uris && uris [i]; ++i)
+ priv->create_item (this, uris [i]);
+
+ g_strfreev (uris);
+}
+
+static void
+load_places_store (BookmarkAgent *this)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gchar **uris;
+ gchar **groups;
+ gchar **bookmarks = NULL;
+
+ gchar *buf, *label, *uri;
+
+ gint i, j, bookmark_len;
+
+ load_xbel_store (this);
+
+ uris = g_bookmark_file_get_uris (priv->store, NULL);
+
+ for (i = 0; uris && uris [i]; ++i) {
+ groups = g_bookmark_file_get_groups (priv->store, uris [i], NULL, NULL);
+
+ for (j = 0; groups && groups [j]; ++j) {
+ if (! strcmp (groups [j], "gtk-bookmarks")) {
+ g_bookmark_file_remove_item (priv->store, uris [i], NULL);
+
+ break;
+ }
+ }
+
+ g_strfreev (groups);
+ }
+
+ g_strfreev (uris);
+
+ g_file_get_contents (priv->gtk_store_path, & buf, NULL, NULL);
+
+ if (buf) {
+ bookmarks = g_strsplit (buf, "\n", -1);
+ g_free (buf);
+ }
+
+ for (i = 0; bookmarks && bookmarks [i]; ++i) {
+ bookmark_len = strlen (bookmarks [i]);
+ if (bookmark_len > 0) {
+ label = strstr (bookmarks[i], " ");
+ if (label != NULL)
+ uri = g_strndup (bookmarks [i], bookmark_len - strlen (label));
+ else
+ uri = bookmarks [i];
+ g_bookmark_file_add_group (priv->store, uri, "gtk-bookmarks");
+ priv->create_item (this, uri);
+ if (label != NULL) {
+ label++;
+ if (strlen (label) > 0)
+ g_bookmark_file_set_title (priv->store, uri, label);
+ g_free (uri);
+ }
+ }
+ }
+
+ g_strfreev (bookmarks);
+}
+
+static gchar *
+find_package_data_file (const gchar *filename)
+{
+ const gchar * const *dirs = NULL;
+ gchar *path = NULL;
+ gint i;
+
+ dirs = g_get_system_data_dirs ();
+
+ for (i = 0; ! path && dirs && dirs [i]; ++i) {
+ path = g_build_filename (dirs [i], PACKAGE, filename, NULL);
+
+ if (! g_file_test (path, G_FILE_TEST_EXISTS)) {
+ g_free (path);
+ path = NULL;
+ }
+ }
+
+ return path;
+}
+
+static void
+update_user_spec_path (BookmarkAgent *this)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gboolean use_user_path;
+ gchar *path = NULL;
+
+ BookmarkStoreStatus status;
+
+ use_user_path = priv->user_modifiable &&
+ (priv->needs_sync || g_file_test (priv->user_store_path, G_FILE_TEST_EXISTS));
+
+ if (use_user_path)
+ path = g_strdup (priv->user_store_path);
+ else
+ path = find_package_data_file (priv->store_filename);
+
+ if (use_user_path)
+ status = BOOKMARK_STORE_USER;
+ else if (path && priv->user_modifiable)
+ status = BOOKMARK_STORE_DEFAULT;
+ else if (path)
+ status = BOOKMARK_STORE_DEFAULT_ONLY;
+ else
+ status = BOOKMARK_STORE_ABSENT;
+
+ if (priv->status != status) {
+ priv->status = status;
+ g_object_notify (G_OBJECT (this), BOOKMARK_AGENT_STORE_STATUS_PROP);
+
+ if (priv->user_store_monitor) {
+ g_file_monitor_cancel (priv->user_store_monitor);
+ g_object_unref (priv->user_store_monitor);
+ priv->user_store_monitor = NULL;
+ }
+
+ if (priv->status == BOOKMARK_STORE_DEFAULT) {
+ GFile *user_store_file;
+
+ user_store_file = g_file_new_for_path (priv->user_store_path);
+ priv->user_store_monitor = g_file_monitor_file (user_store_file,
+ 0, NULL, NULL);
+ if (priv->user_store_monitor) {
+ g_signal_connect (priv->user_store_monitor, "changed",
+ G_CALLBACK (store_monitor_cb), this);
+ }
+
+ g_object_unref (user_store_file);
+ }
+ }
+
+ if (g_strcmp0 (priv->store_path, path)) {
+ g_free (priv->store_path);
+ priv->store_path = path;
+
+ if (priv->store_monitor) {
+ g_file_monitor_cancel (priv->store_monitor);
+ g_object_unref (priv->store_monitor);
+ }
+
+ if (priv->store_path) {
+ GFile *store_file;
+
+ store_file = g_file_new_for_path (priv->store_path);
+ priv->store_monitor = g_file_monitor_file (store_file,
+ 0, NULL, NULL);
+ if (priv->store_monitor) {
+ g_signal_connect (priv->store_monitor, "changed",
+ G_CALLBACK (store_monitor_cb), this);
+ }
+
+ g_object_unref (store_file);
+ }
+ }
+ else
+ g_free (path);
+}
+
+static void
+save_xbel_store (BookmarkAgent *this)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ GError *error = NULL;
+
+ if (g_bookmark_file_to_file (priv->store, priv->store_path, &error))
+ return;
+
+ if (error) {
+ g_warning ("Couldn't save bookmark file [%s]: %s", priv->store_path, error->message);
+ g_error_free (error);
+ } else {
+ g_warning ("Couldn't save bookmark file [%s]", priv->store_path);
+ }
+}
+
+static void
+create_app_item (BookmarkAgent *this, const gchar *uri)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ MateDesktopItem *ditem;
+ gchar *uri_new = NULL;
+
+ ditem = libslab_mate_desktop_item_new_from_unknown_id (uri);
+
+ if (ditem) {
+ uri_new = g_strdup (mate_desktop_item_get_location (ditem));
+ mate_desktop_item_unref (ditem);
+ }
+
+ if (! uri_new)
+ return;
+
+ if (g_strcmp0 (uri, uri_new))
+ g_bookmark_file_move_item (priv->store, uri, uri_new, NULL);
+
+ g_free (uri_new);
+}
+
+static void
+create_doc_item (BookmarkAgent *this, const gchar *uri)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gchar *uri_new = NULL;
+
+ if ((strcmp (uri, "BLANK_SPREADSHEET") == 0) || (strcmp (uri, "BLANK_DOCUMENT") == 0)) {
+ gchar *template = NULL;
+ gchar *file;
+
+ gchar *dir = g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS));
+ if (!dir)
+ dir = g_build_filename (g_get_home_dir (), "Documents", NULL);
+
+ if (strcmp (uri, "BLANK_SPREADSHEET") == 0) {
+ g_bookmark_file_set_title (priv->store, uri, "BLANK_SPREADSHEET");
+ file = g_strconcat (_("New Spreadsheet"), ".ots", NULL);
+ template = find_package_data_file (CALC_TEMPLATE_FILE_NAME);
+ } else {
+ g_bookmark_file_set_title (priv->store, uri, "BLANK_DOCUMENT");
+ file = g_strconcat (_("New Document"), ".ott", NULL);
+ template = find_package_data_file (WRITER_TEMPLATE_FILE_NAME);
+ }
+
+ gchar *path = g_build_filename (dir, file, NULL);
+ if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+ g_mkdir_with_parents (dir, 0700);
+
+ if (template != NULL) {
+ gchar *contents;
+ gsize length;
+
+ if (g_file_get_contents (template, &contents, &length, NULL))
+ g_file_set_contents (path, contents, length, NULL);
+
+ g_free (contents);
+ } else {
+ fclose (g_fopen (path, "w"));
+ }
+ }
+
+ uri_new = g_filename_to_uri (path, NULL, NULL);
+
+ g_free (dir);
+ g_free (file);
+ g_free (path);
+ g_free (template);
+ }
+
+ if (!uri_new)
+ return;
+
+ if (g_strcmp0 (uri, uri_new))
+ g_bookmark_file_move_item (priv->store, uri, uri_new, NULL);
+
+ g_free (uri_new);
+}
+
+static void
+create_dir_item (BookmarkAgent *this, const gchar *uri)
+{
+ BookmarkAgentPrivate *priv = bookmark_agent_get_instance_private (this);
+
+ gchar *uri_new = NULL;
+ gchar *path = NULL;
+ gchar *name = NULL;
+ gchar *icon = NULL;
+
+ gchar *search_string = NULL;
+
+ gboolean gotta_free_name = FALSE;
+
+ if (strcmp (uri, "HOME") == 0) {
+ uri_new = g_filename_to_uri (g_get_home_dir (), NULL, NULL);
+ name = g_strdup (C_("Home folder", "Home"));
+ gotta_free_name = TRUE;
+ icon = "user-home";
+ } else if (strcmp (uri, "DOCUMENTS") == 0) {
+ path = g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS));
+ if (!path)
+ path = g_build_filename (g_get_home_dir (), "Documents", NULL);
+ name = _("Documents");
+ uri_new = g_filename_to_uri (path, NULL, NULL);
+ } else if (strcmp (uri, "DESKTOP") == 0) {
+ path = g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP));
+ if (!path)
+ path = g_build_filename (g_get_home_dir (), "Desktop", NULL);
+ name = _("Desktop");
+ uri_new = g_filename_to_uri (path, NULL, NULL);
+ icon = "user-desktop";
+ } else if (strcmp (uri, "file:///") == 0) {
+ icon = "drive-harddisk";
+ name = _("File System");
+ } else if (strcmp (uri, "network:") == 0) {
+ icon = "network-workgroup";
+ name = _("Network Servers");
+ } else if (g_str_has_prefix (uri, "x-caja-search")) {
+ icon = "system-search";
+
+ path = g_build_filename (g_get_user_data_dir (), "caja", "searches", & uri [21], NULL);
+
+ if (g_file_test (path, G_FILE_TEST_EXISTS)) {
+ gchar *buf = NULL;
+ g_file_get_contents (path, &buf, NULL, NULL);
+
+ gchar *tag_open_ptr = NULL;
+ gchar *tag_close_ptr = NULL;
+
+ if (buf) {
+ tag_open_ptr = strstr (buf, "<text>");
+ tag_close_ptr = strstr (buf, "</text>");
+ }
+
+ if (tag_open_ptr && tag_close_ptr) {
+ tag_close_ptr [0] = '\0';
+ tag_close_ptr [0] = 'a';
+ search_string = g_strdup_printf ("\"%s\"", &tag_open_ptr[6]);
+ }
+
+ g_free (buf);
+ }
+
+ if (search_string) {
+ name = search_string;
+ gotta_free_name = TRUE;
+ } else {
+ name = _("Search");
+ }
+ }
+
+ if (icon)
+ g_bookmark_file_set_icon (priv->store, uri, icon, "image/png");
+
+ if (name)
+ g_bookmark_file_set_title (priv->store, uri, name);
+
+ if (uri_new && g_strcmp0 (uri, uri_new))
+ g_bookmark_file_move_item (priv->store, uri, uri_new, NULL);
+
+ if (gotta_free_name) {
+ g_free (name);
+ }
+
+ g_free (path);
+ g_free (uri_new);
+}
+
+static void
+store_monitor_cb (GFileMonitor *mon, GFile *f1, GFile *f2,
+ GFileMonitorEvent event_type, gpointer user_data)
+{
+ update_agent (BOOKMARK_AGENT (user_data));
+}
+
+static void
+weak_destroy_cb (gpointer data, GObject *g_obj)
+{
+ instances [GPOINTER_TO_INT (data)] = NULL;
+}
diff --git a/shell/bookmark-agent.h b/shell/bookmark-agent.h
new file mode 100644
index 00000000..f54415bf
--- /dev/null
+++ b/shell/bookmark-agent.h
@@ -0,0 +1,98 @@
+/*
+ * This file is part of the Main Menu.
+ *
+ * Copyright (c) 2007 Novell, Inc.
+ *
+ * The Main Menu is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * The Main Menu is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * the Main Menu; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __BOOKMARK_AGENT_H__
+#define __BOOKMARK_AGENT_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#if !GLIB_CHECK_VERSION(2,66,0)
+#include <time.h>
+#endif
+
+G_BEGIN_DECLS
+
+#define BOOKMARK_AGENT_TYPE (bookmark_agent_get_type ())
+#define BOOKMARK_AGENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), BOOKMARK_AGENT_TYPE, BookmarkAgent))
+#define BOOKMARK_AGENT_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), BOOKMARK_AGENT_TYPE, BookmarkAgentClass))
+#define IS_BOOKMARK_AGENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), BOOKMARK_AGENT_TYPE))
+#define IS_BOOKMARK_AGENT_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), BOOKMARK_AGENT_TYPE))
+#define BOOKMARK_AGENT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), BOOKMARK_AGENT_TYPE, BookmarkAgentClass))
+
+#define BOOKMARK_AGENT_STORE_STATUS_PROP "store-status"
+#define BOOKMARK_AGENT_ITEMS_PROP "items"
+
+typedef struct {
+ gchar *uri;
+ gchar *title;
+ gchar *mime_type;
+#if GLIB_CHECK_VERSION(2,66,0)
+ GDateTime *mtime;
+#else
+ time_t mtime;
+#endif
+ gchar *icon;
+ gchar *app_name;
+ gchar *app_exec;
+} BookmarkItem;
+
+typedef enum {
+ BOOKMARK_STORE_DEFAULT_ONLY,
+ BOOKMARK_STORE_DEFAULT,
+ BOOKMARK_STORE_USER,
+ BOOKMARK_STORE_ABSENT
+} BookmarkStoreStatus;
+
+typedef enum {
+ BOOKMARK_STORE_USER_APPS = 0,
+ BOOKMARK_STORE_USER_DOCS = 1,
+ BOOKMARK_STORE_USER_DIRS = 2,
+ BOOKMARK_STORE_RECENT_APPS = 3,
+ BOOKMARK_STORE_RECENT_DOCS = 4,
+ BOOKMARK_STORE_SYSTEM = 5,
+ BOOKMARK_STORE_N_TYPES = 6
+} BookmarkStoreType;
+
+typedef struct {
+ GObject g_object;
+} BookmarkAgent;
+
+typedef struct {
+ GObjectClass g_object_class;
+} BookmarkAgentClass;
+
+GType bookmark_agent_get_type (void);
+
+BookmarkAgent *bookmark_agent_get_instance (BookmarkStoreType type);
+gboolean bookmark_agent_has_item (BookmarkAgent *this, const gchar *uri);
+void bookmark_agent_add_item (BookmarkAgent *this, const BookmarkItem *item);
+void bookmark_agent_move_item (BookmarkAgent *this, const gchar *uri, const gchar *uri_new);
+void bookmark_agent_remove_item (BookmarkAgent *this, const gchar *uri);
+void bookmark_agent_reorder_items (BookmarkAgent *this, const gchar **uris);
+
+void bookmark_agent_update_from_bookmark_file (BookmarkAgent *this, GBookmarkFile *store);
+void bookmark_agent_purge_items (BookmarkAgent *this);
+
+void bookmark_item_free (BookmarkItem *item);
+
+G_END_DECLS
+
+#endif /* __BOOKMARK_AGENT_H__ */
diff --git a/shell/control-center.c b/shell/control-center.c
index 2bdc741c..f0f391c4 100644
--- a/shell/control-center.c
+++ b/shell/control-center.c
@@ -23,7 +23,7 @@
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <gio/gio.h>
-#include <libslab/slab.h>
+#include "slab.h"
void handle_static_action_clicked(Tile* tile, TileEvent* event, gpointer data);
static GSList* get_actions_list(void);
diff --git a/shell/double-click-detector.c b/shell/double-click-detector.c
new file mode 100644
index 00000000..e6874d11
--- /dev/null
+++ b/shell/double-click-detector.c
@@ -0,0 +1,87 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006, 2007 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "double-click-detector.h"
+
+#include <gtk/gtk.h>
+
+#include "libslab-utils.h"
+
+G_DEFINE_TYPE (DoubleClickDetector, double_click_detector, G_TYPE_OBJECT);
+
+void double_click_detector_update_click_time (DoubleClickDetector * detector, guint32 event_time);
+
+static void
+double_click_detector_class_init (DoubleClickDetectorClass * detector_class)
+{
+}
+
+static void
+double_click_detector_init (DoubleClickDetector * detector)
+{
+ GtkSettings *settings;
+ gint click_interval;
+
+ settings = gtk_settings_get_default ();
+
+ g_object_get (G_OBJECT (settings), "gtk-double-click-time", &click_interval, NULL);
+
+ detector->double_click_time = (gint32) click_interval;
+ detector->last_click_time = 0;
+}
+
+DoubleClickDetector *
+double_click_detector_new ()
+{
+ return g_object_new (DOUBLE_CLICK_DETECTOR_TYPE, NULL);
+}
+
+gboolean
+double_click_detector_is_double_click (DoubleClickDetector *this, guint32 event_time,
+ gboolean auto_update)
+{
+ gint32 delta;
+
+ if (event_time == 0)
+ event_time = (guint32) (g_get_monotonic_time () / 1000); /* milliseconds */
+
+ if (this->last_click_time == 0) {
+ if (auto_update)
+ double_click_detector_update_click_time (this, event_time);
+
+ return FALSE;
+ }
+
+ delta = (gint32) event_time - (gint32) this->last_click_time;
+
+ if (auto_update)
+ double_click_detector_update_click_time (this, event_time);
+
+ return delta < this->double_click_time;
+}
+
+void
+double_click_detector_update_click_time (DoubleClickDetector *this, guint32 event_time)
+{
+ if (event_time == 0)
+ event_time = (guint32) (g_get_monotonic_time () / 1000); /* milliseconds */
+
+ this->last_click_time = event_time;
+}
diff --git a/shell/double-click-detector.h b/shell/double-click-detector.h
new file mode 100644
index 00000000..4a745d8e
--- /dev/null
+++ b/shell/double-click-detector.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __DOUBLE_CLICK_DETECTOR_H__
+#define __DOUBLE_CLICK_DETECTOR_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define DOUBLE_CLICK_DETECTOR_TYPE (double_click_detector_get_type ())
+#define DOUBLE_CLICK_DETECTOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DOUBLE_CLICK_DETECTOR_TYPE, DoubleClickDetector))
+#define DOUBLE_CLICK_DETECTOR_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), DOUBLE_CLICK_DETECTOR_TYPE, DoubleClickDetectorClass))
+#define IS_DOUBLE_CLICK_DETECTOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DOUBLE_CLICK_DETECTOR_TYPE))
+#define IS_DOUBLE_CLICK_DETECTOR_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), DOUBLE_CLICK_DETECTOR_TYPE))
+#define DOUBLE_CLICK_DETECTOR_GET_CLASS(o) (G_TYPE_CHECK_GET_CLASS ((o), DOUBLE_CLICK_DETECTOR_TYPE, DoubleClickDetectorClass))
+
+typedef struct
+{
+ GObject parent_placeholder;
+
+ gint32 double_click_time;
+ guint32 last_click_time;
+} DoubleClickDetector;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} DoubleClickDetectorClass;
+
+GType double_click_detector_get_type (void);
+
+DoubleClickDetector *double_click_detector_new (void);
+
+gboolean double_click_detector_is_double_click (DoubleClickDetector * detector, guint32 event_time,
+ gboolean auto_update);
+
+G_END_DECLS
+
+#endif /* __DOUBLE_CLICK_DETECTOR_H__ */
diff --git a/shell/libslab-utils.c b/shell/libslab-utils.c
new file mode 100644
index 00000000..5ee4d09c
--- /dev/null
+++ b/shell/libslab-utils.c
@@ -0,0 +1,70 @@
+#include "libslab-utils.h"
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <gtk/gtk.h>
+
+MateDesktopItem *
+libslab_mate_desktop_item_new_from_unknown_id (const gchar *id)
+{
+ MateDesktopItem *item;
+ gchar *basename;
+
+ GError *error = NULL;
+
+ if (! id)
+ return NULL;
+
+ item = mate_desktop_item_new_from_uri (id, 0, & error);
+
+ if (! error)
+ return item;
+ else {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ item = mate_desktop_item_new_from_file (id, 0, & error);
+
+ if (! error)
+ return item;
+ else {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ item = mate_desktop_item_new_from_basename (id, 0, & error);
+
+ if (! error)
+ return item;
+ else {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ basename = g_strrstr (id, "/");
+
+ if (basename) {
+ basename++;
+
+ item = mate_desktop_item_new_from_basename (basename, 0, &error);
+
+ if (! error)
+ return item;
+ else {
+ g_error_free (error);
+ error = NULL;
+ }
+ }
+
+ return NULL;
+}
diff --git a/shell/libslab-utils.h b/shell/libslab-utils.h
new file mode 100644
index 00000000..069ff3c7
--- /dev/null
+++ b/shell/libslab-utils.h
@@ -0,0 +1,14 @@
+#ifndef __LIBSLAB_UTILS_H__
+#define __LIBSLAB_UTILS_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <libmate-desktop/mate-desktop-item.h>
+
+G_BEGIN_DECLS
+
+MateDesktopItem *libslab_mate_desktop_item_new_from_unknown_id (const gchar *id);
+
+G_END_DECLS
+
+#endif /* __LIBSLAB_UTILS_H__ */
diff --git a/shell/mate-utils.c b/shell/mate-utils.c
new file mode 100644
index 00000000..e42d02c4
--- /dev/null
+++ b/shell/mate-utils.c
@@ -0,0 +1,82 @@
+#include "mate-utils.h"
+
+#include <string.h>
+
+gboolean
+load_image_by_id (GtkImage *image, GtkIconSize size, const gchar *image_id)
+{
+ cairo_surface_t *surface;
+ gint width;
+ gint height;
+ gint scale_factor;
+
+ GtkIconTheme *icon_theme;
+
+ gchar *id;
+
+ gboolean icon_exists;
+
+ if (!image_id)
+ return FALSE;
+
+ id = g_strdup (image_id);
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (image));
+
+ gtk_icon_size_lookup (size, &width, &height);
+ gtk_image_set_pixel_size (image, width);
+
+ if (g_path_is_absolute (id))
+ {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_new_from_file_at_size (id, width * scale_factor, height * scale_factor, NULL);
+
+ icon_exists = (pixbuf != NULL);
+
+ if (icon_exists)
+ {
+ surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor, NULL);
+ gtk_image_set_from_surface (image, surface);
+
+ cairo_surface_destroy (surface);
+ g_object_unref (pixbuf);
+ }
+ else
+ gtk_image_set_from_icon_name (image, "image-missing", size);
+ }
+ else
+ {
+ if ( /* file extensions are not copesetic with loading by "name" */
+ g_str_has_suffix (id, ".png") ||
+ g_str_has_suffix (id, ".svg") ||
+ g_str_has_suffix (id, ".xpm")
+ )
+
+ id[strlen (id) - 4] = '\0';
+
+ if (gtk_widget_has_screen (GTK_WIDGET (image)))
+ icon_theme =
+ gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET
+ (image)));
+ else
+ icon_theme = gtk_icon_theme_get_default ();
+
+ surface = gtk_icon_theme_load_surface (icon_theme, id,
+ width, scale_factor,
+ NULL,
+ GTK_ICON_LOOKUP_FORCE_SIZE,
+ NULL);
+ icon_exists = (surface != NULL);
+ if (icon_exists) {
+ gtk_image_set_from_surface (image, surface);
+ cairo_surface_destroy (surface);
+ }
+ else
+ gtk_image_set_from_icon_name (image, "image-missing", size);
+
+ }
+
+ g_free (id);
+
+ return icon_exists;
+}
diff --git a/shell/mate-utils.h b/shell/mate-utils.h
new file mode 100644
index 00000000..0fa4cd3f
--- /dev/null
+++ b/shell/mate-utils.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __MATE_UTILS_H__
+#define __MATE_UTILS_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+gboolean load_image_by_id (GtkImage * image, GtkIconSize size,
+ const gchar * image_id);
+
+G_END_DECLS
+
+#endif /* __MATE_UTILS_H__ */
diff --git a/shell/nameplate-tile.c b/shell/nameplate-tile.c
new file mode 100644
index 00000000..7d14452c
--- /dev/null
+++ b/shell/nameplate-tile.c
@@ -0,0 +1,274 @@
+/*
+ * This file is part of libtile.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libtile is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libtile is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "nameplate-tile.h"
+
+static void nameplate_tile_get_property (GObject *, guint, GValue *, GParamSpec *);
+static void nameplate_tile_set_property (GObject *, guint, const GValue *, GParamSpec *);
+static GObject *nameplate_tile_constructor (GType, guint, GObjectConstructParam *);
+
+static void nameplate_tile_drag_begin (GtkWidget *, GdkDragContext *);
+
+static void nameplate_tile_setup (NameplateTile *);
+
+typedef struct
+{
+ GtkContainer *image_ctnr;
+ GtkContainer *header_ctnr;
+ GtkContainer *subheader_ctnr;
+} NameplateTilePrivate;
+
+enum
+{
+ PROP_0,
+ PROP_NAMEPLATE_IMAGE,
+ PROP_NAMEPLATE_HEADER,
+ PROP_NAMEPLATE_SUBHEADER,
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (NameplateTile, nameplate_tile, TILE_TYPE)
+
+GtkWidget *nameplate_tile_new (const gchar * uri, GtkWidget * image, GtkWidget * header,
+ GtkWidget * subheader)
+{
+ return GTK_WIDGET (
+ g_object_new (NAMEPLATE_TILE_TYPE,
+ "tile-uri", uri,
+ "nameplate-image", image,
+ "nameplate-header", header,
+ "nameplate-subheader", subheader,
+ NULL));
+}
+
+static void
+nameplate_tile_class_init (NameplateTileClass * this_class)
+{
+ GObjectClass *g_obj_class = G_OBJECT_CLASS (this_class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (this_class);
+
+ g_obj_class->constructor = nameplate_tile_constructor;
+ g_obj_class->get_property = nameplate_tile_get_property;
+ g_obj_class->set_property = nameplate_tile_set_property;
+
+ widget_class->drag_begin = nameplate_tile_drag_begin;
+
+ g_object_class_install_property (g_obj_class, PROP_NAMEPLATE_IMAGE,
+ g_param_spec_object ("nameplate-image", "nameplate-image", "nameplate image",
+ GTK_TYPE_WIDGET, G_PARAM_READWRITE));
+
+ g_object_class_install_property (g_obj_class, PROP_NAMEPLATE_HEADER,
+ g_param_spec_object ("nameplate-header", "nameplate-header", "nameplate header",
+ GTK_TYPE_WIDGET, G_PARAM_READWRITE));
+
+ g_object_class_install_property (g_obj_class, PROP_NAMEPLATE_SUBHEADER,
+ g_param_spec_object ("nameplate-subheader", "nameplate-subheader",
+ "nameplate subheader", GTK_TYPE_WIDGET, G_PARAM_READWRITE));
+}
+
+static void
+nameplate_tile_init (NameplateTile * this)
+{
+}
+
+static GObject *
+nameplate_tile_constructor (GType type, guint n_param, GObjectConstructParam * param)
+{
+ GObject *g_obj =
+ (*G_OBJECT_CLASS (nameplate_tile_parent_class)->constructor) (type, n_param, param);
+
+ nameplate_tile_setup (NAMEPLATE_TILE (g_obj));
+
+ return g_obj;
+}
+
+static void
+nameplate_tile_get_property (GObject * g_object, guint prop_id, GValue * value,
+ GParamSpec * param_spec)
+{
+ NameplateTile *np_tile = NAMEPLATE_TILE (g_object);
+
+ switch (prop_id)
+ {
+ case PROP_NAMEPLATE_IMAGE:
+ g_value_set_object (value, np_tile->image);
+ break;
+
+ case PROP_NAMEPLATE_HEADER:
+ g_value_set_object (value, np_tile->header);
+ break;
+
+ case PROP_NAMEPLATE_SUBHEADER:
+ g_value_set_object (value, np_tile->subheader);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+nameplate_tile_set_property (GObject * g_object, guint prop_id, const GValue * value,
+ GParamSpec * param_spec)
+{
+ NameplateTile *this = NAMEPLATE_TILE (g_object);
+ NameplateTilePrivate *priv = nameplate_tile_get_instance_private (this);
+
+ GObject *widget_obj = NULL;
+
+ switch (prop_id) {
+ case PROP_NAMEPLATE_IMAGE:
+ case PROP_NAMEPLATE_HEADER:
+ case PROP_NAMEPLATE_SUBHEADER:
+ widget_obj = g_value_get_object (value);
+ break;
+ default:
+ break;
+ }
+
+ switch (prop_id)
+ {
+ case PROP_NAMEPLATE_IMAGE:
+ if (GTK_IS_WIDGET (widget_obj))
+ {
+ if (GTK_IS_WIDGET (this->image))
+ gtk_widget_destroy (this->image);
+
+ this->image = GTK_WIDGET (widget_obj);
+
+ gtk_container_add (priv->image_ctnr, this->image);
+
+ gtk_widget_show_all (this->image);
+ }
+ else if (GTK_IS_WIDGET (this->image))
+ gtk_widget_destroy (this->image);
+
+ break;
+
+ case PROP_NAMEPLATE_HEADER:
+ if (GTK_IS_WIDGET (widget_obj))
+ {
+ if (GTK_IS_WIDGET (this->header))
+ gtk_widget_destroy (this->header);
+
+ this->header = GTK_WIDGET (widget_obj);
+
+ gtk_container_add (priv->header_ctnr, this->header);
+
+ gtk_widget_show_all (this->header);
+ }
+ else if (GTK_IS_WIDGET (this->header))
+ gtk_widget_destroy (this->header);
+
+ break;
+
+ case PROP_NAMEPLATE_SUBHEADER:
+ if (GTK_IS_WIDGET (widget_obj))
+ {
+ if (GTK_IS_WIDGET (this->subheader))
+ gtk_widget_destroy (this->subheader);
+
+ this->subheader = GTK_WIDGET (widget_obj);
+
+ gtk_container_add (priv->subheader_ctnr, this->subheader);
+
+ gtk_widget_show_all (this->subheader);
+ }
+ else if (GTK_IS_WIDGET (this->subheader))
+ gtk_widget_destroy (this->subheader);
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+nameplate_tile_setup (NameplateTile *this)
+{
+ NameplateTilePrivate *priv = nameplate_tile_get_instance_private (this);
+
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+
+ priv->image_ctnr = GTK_CONTAINER (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+ gtk_widget_set_valign (GTK_WIDGET (priv->image_ctnr), GTK_ALIGN_CENTER);
+
+ priv->header_ctnr = GTK_CONTAINER (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+
+ priv->subheader_ctnr = GTK_CONTAINER (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+ gtk_widget_set_halign (GTK_WIDGET (priv->subheader_ctnr), GTK_ALIGN_START);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_set_halign (vbox, GTK_ALIGN_FILL);
+ gtk_widget_set_valign (vbox, GTK_ALIGN_CENTER);
+
+ gtk_container_add (GTK_CONTAINER (this), hbox);
+ gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (priv->image_ctnr), FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (priv->header_ctnr), FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (priv->subheader_ctnr), FALSE, FALSE, 0);
+
+ if (GTK_IS_WIDGET (this->image))
+ gtk_container_add (priv->image_ctnr, this->image);
+
+ if (GTK_IS_WIDGET (this->header))
+ gtk_container_add (priv->header_ctnr, this->header);
+
+ if (GTK_IS_WIDGET (this->subheader))
+ gtk_container_add (priv->subheader_ctnr, this->subheader);
+
+ gtk_widget_set_focus_on_click (GTK_WIDGET (this), FALSE);
+}
+
+static void
+nameplate_tile_drag_begin (GtkWidget * widget, GdkDragContext * context)
+{
+ NameplateTile *this = NAMEPLATE_TILE (widget);
+ GtkImage *image;
+ const gchar *name;
+
+ (*GTK_WIDGET_CLASS (nameplate_tile_parent_class)->drag_begin) (widget, context);
+
+ if (!this->image || !GTK_IS_IMAGE (this->image))
+ return;
+
+ image = GTK_IMAGE (this->image);
+
+ switch (gtk_image_get_storage_type (image))
+ {
+ case GTK_IMAGE_PIXBUF:
+ if (gtk_image_get_pixbuf (image))
+ gtk_drag_set_icon_pixbuf (context, gtk_image_get_pixbuf (image), 0, 0);
+
+ break;
+
+ case GTK_IMAGE_ICON_NAME:
+ gtk_image_get_icon_name (image, &name, NULL);
+ if (name)
+ gtk_drag_set_icon_name (context, name, 0, 0);
+
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/shell/nameplate-tile.h b/shell/nameplate-tile.h
new file mode 100644
index 00000000..d51ff40f
--- /dev/null
+++ b/shell/nameplate-tile.h
@@ -0,0 +1,57 @@
+/*
+ * This file is part of libtile.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libtile is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libtile is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __NAMEPLATE_TILE_H__
+#define __NAMEPLATE_TILE_H__
+
+#include "tile.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAMEPLATE_TILE_TYPE (nameplate_tile_get_type ())
+#define NAMEPLATE_TILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), NAMEPLATE_TILE_TYPE, NameplateTile))
+#define NAMEPLATE_TILE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), NAMEPLATE_TILE_TYPE, NameplateTileClass))
+#define IS_NAMEPLATE_TILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), NAMEPLATE_TILE_TYPE))
+#define IS_NAMEPLATE_TILE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), NAMEPLATE_TILE_TYPE))
+#define NAMEPLATE_TILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), NAMEPLATE_TILE_TYPE, NameplateTileClass))
+
+typedef struct {
+ Tile tile;
+
+ GtkWidget *image;
+ GtkWidget *header;
+ GtkWidget *subheader;
+} NameplateTile;
+
+typedef struct {
+ TileClass tile_class;
+} NameplateTileClass;
+
+GType nameplate_tile_get_type (void);
+
+GtkWidget *nameplate_tile_new (const gchar * uri, GtkWidget * image, GtkWidget * header,
+ GtkWidget * subheader);
+
+G_END_DECLS
+
+#endif /* __NAMEPLATE_TILE_H__ */
diff --git a/shell/nld-marshal.list b/shell/nld-marshal.list
new file mode 100644
index 00000000..30ba5d8d
--- /dev/null
+++ b/shell/nld-marshal.list
@@ -0,0 +1 @@
+VOID:STRING
diff --git a/shell/search-bar.c b/shell/search-bar.c
new file mode 100644
index 00000000..f560f649
--- /dev/null
+++ b/shell/search-bar.c
@@ -0,0 +1,238 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "search-bar.h"
+#include "config.h"
+
+#include "nld-marshal.h"
+
+#include <glib/gi18n-lib.h>
+
+typedef struct
+{
+ GtkWidget *hbox;
+ GtkEntry *entry;
+ GtkWidget *button;
+
+ int search_timeout;
+ guint timeout_id;
+
+ gboolean block_signal;
+} NldSearchBarPrivate;
+
+static void nld_search_bar_finalize (GObject *);
+
+static gboolean nld_search_bar_focus (GtkWidget *, GtkDirectionType);
+static void nld_search_bar_grab_focus (GtkWidget *);
+
+enum
+{
+ SEARCH,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE_WITH_PRIVATE (NldSearchBar, nld_search_bar, GTK_TYPE_BOX)
+
+static void emit_search (NldSearchBar * search_bar);
+static void emit_search_callback (GtkWidget * widget, gpointer search_bar);
+
+static void nld_search_bar_class_init (NldSearchBarClass * nld_search_bar_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (nld_search_bar_class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (nld_search_bar_class);
+
+ object_class->finalize = nld_search_bar_finalize;
+ widget_class->focus = nld_search_bar_focus;
+ widget_class->grab_focus = nld_search_bar_grab_focus;
+
+ signals[SEARCH] =
+ g_signal_new ("search", G_TYPE_FROM_CLASS (nld_search_bar_class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (NldSearchBarClass, search),
+ NULL, NULL, nld_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void
+nld_search_bar_init (NldSearchBar * search_bar)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+ GtkWidget *entry;
+
+ gtk_widget_set_can_focus (GTK_WIDGET (search_bar), TRUE);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (search_bar), GTK_ORIENTATION_VERTICAL);
+
+ priv->hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
+ gtk_box_pack_start (GTK_BOX (search_bar), priv->hbox, TRUE, FALSE, 0);
+
+ entry = gtk_search_entry_new ();
+ gtk_widget_set_halign (entry, GTK_ALIGN_START);
+ gtk_widget_set_valign (entry, GTK_ALIGN_CENTER);
+ priv->entry = GTK_ENTRY (entry);
+ gtk_widget_show (entry);
+ gtk_box_pack_start (GTK_BOX (priv->hbox), entry, TRUE, TRUE, 0);
+
+ g_signal_connect (entry, "activate", G_CALLBACK (emit_search_callback), search_bar);
+
+ priv->search_timeout = -1;
+}
+
+static void
+nld_search_bar_finalize (GObject * object)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (NLD_SEARCH_BAR(object));
+
+ if (priv->timeout_id)
+ g_source_remove (priv->timeout_id);
+
+ G_OBJECT_CLASS (nld_search_bar_parent_class)->finalize (object);
+}
+
+static gboolean
+nld_search_bar_focus (GtkWidget * widget, GtkDirectionType dir)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (NLD_SEARCH_BAR(widget));
+
+ return gtk_widget_child_focus (priv->hbox, dir);
+}
+
+gboolean
+nld_search_bar_has_focus (NldSearchBar * search_bar)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+
+ return gtk_widget_has_focus (GTK_WIDGET (priv->entry));
+}
+
+static void
+nld_search_bar_grab_focus (GtkWidget * widget)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (NLD_SEARCH_BAR(widget));
+
+ gtk_widget_grab_focus (GTK_WIDGET (priv->entry));
+}
+
+GtkWidget *
+nld_search_bar_new (void)
+{
+ return g_object_new (NLD_TYPE_SEARCH_BAR, NULL);
+}
+
+void
+nld_search_bar_clear (NldSearchBar * search_bar)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+
+ priv->block_signal = TRUE;
+ gtk_entry_set_text (priv->entry, "");
+ priv->block_signal = FALSE;
+}
+
+static void
+emit_search (NldSearchBar * search_bar)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+
+ if (priv->block_signal)
+ return;
+
+ if (priv->timeout_id)
+ {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+
+ g_signal_emit (search_bar, signals[SEARCH], 0,
+ nld_search_bar_get_text (search_bar));
+}
+
+static void
+emit_search_callback (GtkWidget * widget, gpointer search_bar)
+{
+ emit_search (search_bar);
+}
+
+static gboolean
+search_timeout (gpointer search_bar)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+
+ priv->timeout_id = 0;
+ emit_search (search_bar);
+ return FALSE;
+}
+
+static void
+entry_changed (GtkWidget * entry, gpointer search_bar)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+
+ if (priv->search_timeout == 0)
+ emit_search (search_bar);
+ else if (priv->search_timeout > 0)
+ {
+ if (priv->timeout_id != 0)
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id =
+ g_timeout_add (priv->search_timeout * 1000, search_timeout, search_bar);
+ }
+}
+
+int
+nld_search_bar_get_search_timeout (NldSearchBar * search_bar)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+
+ return priv->search_timeout;
+}
+
+void
+nld_search_bar_set_search_timeout (NldSearchBar * search_bar, int search_timeout)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+
+ if (priv->search_timeout != -1 && search_timeout == -1)
+ g_signal_handlers_disconnect_by_func (priv->entry, entry_changed, search_bar);
+ else if (search_timeout != -1)
+ {
+ g_signal_connect (priv->entry, "changed", G_CALLBACK (entry_changed), search_bar);
+ }
+
+ priv->search_timeout = search_timeout;
+}
+
+const char *
+nld_search_bar_get_text (NldSearchBar * search_bar)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+
+ return gtk_entry_get_text (priv->entry);
+}
+
+void
+nld_search_bar_set_text (NldSearchBar * search_bar, const char *text, gboolean activate)
+{
+ NldSearchBarPrivate *priv = nld_search_bar_get_instance_private (search_bar);
+
+ gtk_entry_set_text (priv->entry, text);
+ if (activate)
+ emit_search (search_bar);
+}
+
diff --git a/shell/search-bar.h b/shell/search-bar.h
new file mode 100644
index 00000000..fca5216d
--- /dev/null
+++ b/shell/search-bar.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __NLD_SEARCH_BAR_H__
+#define __NLD_SEARCH_BAR_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NLD_TYPE_SEARCH_BAR (nld_search_bar_get_type ())
+#define NLD_SEARCH_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NLD_TYPE_SEARCH_BAR, NldSearchBar))
+#define NLD_SEARCH_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NLD_TYPE_SEARCH_BAR, NldSearchBarClass))
+#define NLD_IS_SEARCH_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NLD_TYPE_SEARCH_BAR))
+#define NLD_IS_SEARCH_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NLD_TYPE_SEARCH_BAR))
+#define NLD_SEARCH_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NLD_TYPE_SEARCH_BAR, NldSearchBarClass))
+
+typedef struct
+{
+ GtkBox parent;
+} NldSearchBar;
+
+typedef struct
+{
+ GtkBoxClass parent_class;
+
+ void (*search) (NldSearchBar *, const char *text);
+} NldSearchBarClass;
+
+GType nld_search_bar_get_type (void);
+
+GtkWidget *nld_search_bar_new (void);
+
+void nld_search_bar_clear (NldSearchBar * search_bar);
+gboolean nld_search_bar_has_focus (NldSearchBar * search_bar);
+
+gboolean nld_search_bar_get_show_button (NldSearchBar * search_bar);
+void nld_search_bar_set_show_button (NldSearchBar * search_bar, gboolean show_button);
+
+int nld_search_bar_get_search_timeout (NldSearchBar * search_bar);
+void nld_search_bar_set_search_timeout (NldSearchBar * search_bar, int search_timeout);
+
+const char *nld_search_bar_get_text (NldSearchBar * search_bar);
+void nld_search_bar_set_text (NldSearchBar * search_bar, const char *text, gboolean activate);
+
+G_END_DECLS
+
+#endif /* __NLD_SEARCH_BAR_H__ */
diff --git a/shell/shell-window.c b/shell/shell-window.c
new file mode 100644
index 00000000..19f3c5e8
--- /dev/null
+++ b/shell/shell-window.c
@@ -0,0 +1,83 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "shell-window.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+#include "app-resizer.h"
+
+G_DEFINE_TYPE (ShellWindow, shell_window, GTK_TYPE_FRAME);
+
+static void
+shell_window_class_init (ShellWindowClass * klass)
+{
+}
+
+static void
+shell_window_init (ShellWindow * window)
+{
+ window->_hbox = NULL;
+ window->_left_pane = NULL;
+ window->_right_pane = NULL;
+}
+
+GtkWidget *
+shell_window_new (AppShellData * app_data)
+{
+ ShellWindow *window = g_object_new (SHELL_WINDOW_TYPE, NULL);
+
+ gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE);
+ gtk_frame_set_shadow_type(GTK_FRAME(window), GTK_SHADOW_NONE);
+
+ window->_hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (window->_hbox));
+
+ return GTK_WIDGET (window);
+}
+
+void
+shell_window_clear_resize_handler (ShellWindow * win)
+{
+ if (win->resize_handler_id)
+ {
+ g_signal_handler_disconnect (win, win->resize_handler_id);
+ win->resize_handler_id = 0;
+ }
+}
+
+void
+shell_window_set_contents (ShellWindow * shell, GtkWidget * left_pane, GtkWidget * right_pane)
+{
+ shell->_left_pane = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_set_margin_top (GTK_WIDGET (shell->_left_pane), 15);
+ gtk_widget_set_margin_bottom (GTK_WIDGET (shell->_left_pane), 15);
+ gtk_widget_set_margin_start (GTK_WIDGET (shell->_left_pane), 15);
+ gtk_widget_set_margin_end (GTK_WIDGET (shell->_left_pane), 15);
+
+ shell->_right_pane = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+
+ gtk_box_pack_start (shell->_hbox, shell->_left_pane, FALSE, FALSE, 0);
+ gtk_box_pack_start (shell->_hbox, shell->_right_pane, TRUE, TRUE, 0); /* this one takes any extra space */
+
+ gtk_container_add (GTK_CONTAINER (shell->_left_pane), left_pane);
+ gtk_container_add (GTK_CONTAINER (shell->_right_pane), right_pane);
+}
diff --git a/shell/shell-window.h b/shell/shell-window.h
new file mode 100644
index 00000000..2aa51e3f
--- /dev/null
+++ b/shell/shell-window.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SHELL_WINDOW_H__
+#define __SHELL_WINDOW_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "app-shell.h"
+
+G_BEGIN_DECLS
+
+#define SHELL_WINDOW_TYPE (shell_window_get_type ())
+#define SHELL_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_WINDOW_TYPE, ShellWindow))
+#define SHELL_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_WINDOW_TYPE, ShellWindowClass))
+#define IS_SHELL_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_WINDOW_TYPE))
+#define IS_SHELL_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_WINDOW_TYPE))
+#define SHELL_WINDOW_GET_CLASS(obj) (G_TYPE_CHECK_GET_CLASS ((obj), SHELL_WINDOW_TYPE, ShellWindowClass))
+
+typedef struct _ShellWindow ShellWindow;
+typedef struct _ShellWindowClass ShellWindowClass;
+
+struct _ShellWindow
+{
+ GtkFrame frame;
+
+ GtkBox *_hbox;
+ GtkWidget *_left_pane;
+ GtkWidget *_right_pane;
+
+ gulong resize_handler_id;
+};
+
+struct _ShellWindowClass
+{
+ GtkFrameClass parent_class;
+};
+
+GType shell_window_get_type (void);
+GtkWidget *shell_window_new (AppShellData * app_data);
+void shell_window_set_contents (ShellWindow * window, GtkWidget * left_pane,
+ GtkWidget * right_pane);
+void shell_window_clear_resize_handler (ShellWindow * win);
+
+G_END_DECLS
+
+#endif /* __SHELL_WINDOW_H__ */
diff --git a/shell/slab-mate-util.c b/shell/slab-mate-util.c
new file mode 100644
index 00000000..b036de18
--- /dev/null
+++ b/shell/slab-mate-util.c
@@ -0,0 +1,160 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "slab-mate-util.h"
+#include "libslab-utils.h"
+
+#include <gio/gio.h>
+#include <string.h>
+
+MateDesktopItem *
+load_desktop_item_from_unknown (const gchar *id)
+{
+ MateDesktopItem *item;
+ gchar *basename;
+
+ GError *error = NULL;
+
+ item = mate_desktop_item_new_from_uri (id, 0, &error);
+
+ if (! error)
+ return item;
+ else {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ item = mate_desktop_item_new_from_file (id, 0, &error);
+
+ if (! error)
+ return item;
+ else {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ item = mate_desktop_item_new_from_basename (id, 0, &error);
+
+ if (! error)
+ return item;
+ else {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ basename = g_strrstr (id, "/");
+
+ if (basename) {
+ basename++;
+
+ item = mate_desktop_item_new_from_basename (basename, 0, &error);
+
+ if (! error)
+ return item;
+ else {
+ g_error_free (error);
+ error = NULL;
+ }
+ }
+
+ return NULL;
+}
+
+gboolean
+open_desktop_item_exec (MateDesktopItem * desktop_item)
+{
+ GError *error = NULL;
+
+ if (!desktop_item)
+ return FALSE;
+
+ mate_desktop_item_launch (desktop_item, NULL, MATE_DESKTOP_ITEM_LAUNCH_ONLY_ONE | MATE_DESKTOP_ITEM_LAUNCH_DO_NOT_REAP_CHILD, &error);
+
+ if (error)
+ {
+ g_warning ("error launching %s [%s]\n",
+ mate_desktop_item_get_location (desktop_item), error->message);
+
+ g_error_free (error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+open_desktop_item_help (MateDesktopItem * desktop_item)
+{
+ const gchar *doc_path;
+ gchar *help_uri;
+
+ GError *error;
+
+ if (!desktop_item)
+ return FALSE;
+
+ doc_path = mate_desktop_item_get_string (desktop_item, "DocPath");
+
+ if (doc_path)
+ {
+ help_uri = g_strdup_printf ("help:%s", doc_path);
+
+ error = NULL;
+ if (!gtk_show_uri_on_window (NULL, help_uri, gtk_get_current_event_time (), &error))
+ {
+ g_warning ("error opening %s [%s]\n", help_uri, error->message);
+
+ g_free (help_uri);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_free (help_uri);
+ }
+ else
+ return FALSE;
+
+ return TRUE;
+}
+
+void
+copy_file (const gchar * src_uri, const gchar * dst_uri)
+{
+ GFile *src;
+ GFile *dst;
+ GError *error = NULL;
+ gboolean res;
+
+ src = g_file_new_for_uri (src_uri);
+ dst = g_file_new_for_uri (dst_uri);
+
+ res = g_file_copy (src, dst,
+ G_FILE_COPY_NONE,
+ NULL, NULL, NULL, &error);
+
+ if (!res)
+ {
+ g_warning ("error copying [%s] to [%s]: %s.", src_uri, dst_uri, error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (src);
+ g_object_unref (dst);
+}
diff --git a/shell/slab-mate-util.h b/shell/slab-mate-util.h
new file mode 100644
index 00000000..211e76c4
--- /dev/null
+++ b/shell/slab-mate-util.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SLAB_MATE_UTIL_H__
+#define __SLAB_MATE_UTIL_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <libmate-desktop/mate-desktop-item.h>
+
+G_BEGIN_DECLS
+
+MateDesktopItem *load_desktop_item_from_unknown (const gchar * id);
+
+gboolean open_desktop_item_exec (MateDesktopItem * desktop_item);
+gboolean open_desktop_item_help (MateDesktopItem * desktop_item);
+
+void copy_file (const gchar * src_uri, const gchar * dst_uri);
+
+G_END_DECLS
+
+#endif /* __SLAB_MATE_UTIL_H__ */
diff --git a/shell/slab-section.c b/shell/slab-section.c
new file mode 100644
index 00000000..f86820cf
--- /dev/null
+++ b/shell/slab-section.c
@@ -0,0 +1,233 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "slab-section.h"
+
+G_DEFINE_TYPE (SlabSection, slab_section, GTK_TYPE_BOX)
+
+static void slab_section_finalize (GObject *);
+
+static void slab_section_class_init (SlabSectionClass * slab_section_class)
+{
+ GObjectClass *g_obj_class = G_OBJECT_CLASS (slab_section_class);
+
+ g_obj_class->finalize = slab_section_finalize;
+}
+
+static void
+slab_section_init (SlabSection * section)
+{
+ section->title = NULL;
+ section->contents = NULL;
+}
+
+static void
+slab_section_finalize (GObject * obj)
+{
+ g_assert (IS_SLAB_SECTION (obj));
+ (*G_OBJECT_CLASS (slab_section_parent_class)->finalize) (obj);
+}
+
+static void
+set_override_color (GtkWidget *widget,
+ GdkRGBA *rgba)
+{
+ gchar *css;
+ GtkCssProvider *provider;
+
+ provider = gtk_css_provider_new ();
+
+ css = g_strdup_printf ("* { color: %s;}",
+ gdk_rgba_to_string (rgba));
+ gtk_css_provider_load_from_data (provider, css, -1, NULL);
+ g_free (css);
+
+ gtk_style_context_add_provider (gtk_widget_get_style_context (widget),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ g_object_unref (provider);
+}
+
+static void
+slab_section_set_title_color (GtkWidget * widget)
+{
+ GtkStyleContext *context;
+ GdkRGBA *rgba = NULL;
+
+ context = gtk_widget_get_style_context (widget);
+
+ switch (SLAB_SECTION (widget)->style)
+ {
+ case Style1:
+ gtk_style_context_get (context,
+ GTK_STATE_FLAG_SELECTED,
+ "background-color", &rgba,
+ NULL);
+ set_override_color (SLAB_SECTION (widget)->title, rgba);
+ break;
+ case Style2:
+ if (SLAB_SECTION (widget)->selected)
+ {
+ gtk_style_context_get (context,
+ GTK_STATE_FLAG_SELECTED,
+ "background-color", &rgba,
+ NULL);
+ set_override_color (SLAB_SECTION (widget)->title, rgba);
+ }
+ else
+ {
+ gtk_style_context_get (context,
+ GTK_STATE_FLAG_INSENSITIVE,
+ "color", &rgba,
+ NULL);
+ set_override_color (SLAB_SECTION (widget)->title, rgba);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+slab_section_style_set (GtkWidget * widget, GtkStyle * prev_style, gpointer user_data)
+{
+ static gboolean recursively_entered = FALSE;
+ if (!recursively_entered)
+ {
+ recursively_entered = TRUE;
+
+ slab_section_set_title_color (widget);
+
+ recursively_entered = FALSE;
+ }
+}
+
+/*
+gboolean
+slab_section_expose_event (GtkWidget * widget, GdkEventExpose * event, gpointer data)
+{
+ gdk_draw_rectangle (widget->window, widget->style->light_gc[GTK_STATE_SELECTED], TRUE,
+ widget->allocation.x, widget->allocation.y,
+ widget->allocation.width + 40, widget->allocation.height);
+
+ return FALSE;
+}
+*/
+
+void
+slab_section_set_selected (SlabSection * section, gboolean selected)
+{
+ if (selected == section->selected)
+ return;
+ section->selected = selected;
+
+/*
+ if(selected)
+ {
+ section->expose_handler_id =
+ g_signal_connect (section, "expose-event",
+ G_CALLBACK (slab_section_expose_event),
+ NULL);
+ }
+ else
+ {
+ g_signal_handler_disconnect(section, section->expose_handler_id);
+ }
+*/
+
+ slab_section_set_title_color (GTK_WIDGET (section));
+}
+
+GtkWidget *
+slab_section_new_with_markup (const gchar * title_markup, SlabStyle style)
+{
+ SlabSection *section;
+ gchar * widget_theming_name;
+
+ section = g_object_new (SLAB_SECTION_TYPE, NULL);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (section), GTK_ORIENTATION_VERTICAL);
+ gtk_box_set_homogeneous (GTK_BOX (section), FALSE);
+ gtk_box_set_spacing (GTK_BOX (section), 0);
+ section->style = style;
+ section->selected = FALSE;
+
+ section->childbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 10));
+ switch (style)
+ {
+ case Style1:
+ widget_theming_name = "slab_section_style1";
+ break;
+ case Style2:
+ gtk_widget_set_margin_top (GTK_WIDGET (section->childbox), SLAB_TOP_PADDING);
+ gtk_widget_set_margin_bottom (GTK_WIDGET (section->childbox), SLAB_BOTTOM_PADDING);
+ gtk_widget_set_margin_start (GTK_WIDGET (section->childbox), SLAB_LEFT_PADDING);
+ gtk_widget_set_margin_end (GTK_WIDGET (section->childbox), 0);
+ widget_theming_name = "slab_section_style2";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ gtk_box_pack_start (GTK_BOX (section), GTK_WIDGET (section->childbox), TRUE, TRUE, 0);
+
+ section->title = gtk_label_new (title_markup);
+ gtk_label_set_use_markup (GTK_LABEL (section->title), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (section->title), 0.0);
+
+ gtk_widget_set_name (GTK_WIDGET (section), widget_theming_name);
+ g_signal_connect (section, "style-set",
+ G_CALLBACK (slab_section_style_set),
+ NULL);
+
+ gtk_box_pack_start (section->childbox, section->title, FALSE, FALSE, 0);
+
+ return GTK_WIDGET (section);
+}
+
+GtkWidget *
+slab_section_new (const gchar * title, SlabStyle style)
+{
+ GtkWidget *section;
+ gchar *markup;
+
+ markup = g_strdup_printf ("<span size=\"large\" weight=\"bold\">%s</span>", title);
+ section = slab_section_new_with_markup (markup, style);
+
+ g_free (markup);
+
+ return section;
+}
+
+void
+slab_section_set_title (SlabSection * section, const gchar * title)
+{
+ gchar *markup = g_strdup_printf ("<span size=\"large\">%s</span>", title);
+
+ gtk_label_set_markup (GTK_LABEL (section->title), markup);
+
+ g_free (markup);
+}
+
+void
+slab_section_set_contents (SlabSection * section, GtkWidget * contents)
+{
+ section->contents = contents;
+
+ gtk_box_pack_start (section->childbox, contents, FALSE, FALSE, 0);
+}
diff --git a/shell/slab-section.h b/shell/slab-section.h
new file mode 100644
index 00000000..04288a87
--- /dev/null
+++ b/shell/slab-section.h
@@ -0,0 +1,72 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SLAB_SECTION_H__
+#define __SLAB_SECTION_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SLAB_SECTION_TYPE (slab_section_get_type ())
+#define SLAB_SECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SLAB_SECTION_TYPE, SlabSection))
+#define SLAB_SECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SLAB_SECTION_TYPE, SlabSectionClass))
+#define IS_SLAB_SECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SLAB_SECTION_TYPE ))
+#define IS_SLAB_SECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SLAB_SECTION_TYPE ))
+#define SLAB_SECTION_GET_CLASS(obj) (G_TYPE_CHECK_GET_CLASS ((obj), SLAB_SECTION_TYPE, SlabSectionClass))
+
+#define SLAB_TOP_PADDING 5
+#define SLAB_BOTTOM_PADDING 5
+#define SLAB_LEFT_PADDING 10
+
+typedef enum
+{
+ Style1, /* SlabSections in left pane - no padding */
+ Style2 /* SlabSections in right pane - padding, label text changes as group is selected */
+} SlabStyle;
+
+typedef struct
+{
+ GtkBox parent_vbox;
+
+ GtkWidget *title;
+ GtkWidget *contents;
+ SlabStyle style;
+ gulong expose_handler_id;
+ GtkBox *childbox;
+ gboolean selected;
+} SlabSection;
+
+typedef struct
+{
+ GtkBoxClass parent_class;
+} SlabSectionClass;
+
+GType slab_section_get_type (void);
+GtkWidget *slab_section_new (const gchar * title, SlabStyle style);
+GtkWidget *slab_section_new_with_markup (const gchar * title_markup, SlabStyle style);
+void slab_section_set_title (SlabSection * section, const gchar * title);
+void slab_section_set_contents (SlabSection * section, GtkWidget * contents);
+void slab_section_set_selected (SlabSection * section, gboolean selected);
+
+G_END_DECLS
+
+#endif /* __SLAB_SECTION_H__ */
diff --git a/shell/slab.h b/shell/slab.h
new file mode 100644
index 00000000..721943a0
--- /dev/null
+++ b/shell/slab.h
@@ -0,0 +1,40 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#ifndef __SLAB_H__
+#define __SLAB_H__
+
+#include "app-resizer.h"
+#include "app-shell.h"
+#include "application-tile.h"
+#include "bookmark-agent.h"
+#include "double-click-detector.h"
+#include "mate-utils.h"
+#include "libslab-utils.h"
+#include "nameplate-tile.h"
+#include "search-bar.h"
+#include "shell-window.h"
+#include "slab-mate-util.h"
+#include "slab-section.h"
+#include "tile.h"
+
+#endif /* __SLAB_H__ */
+
diff --git a/shell/themed-icon.c b/shell/themed-icon.c
new file mode 100644
index 00000000..aabf4b89
--- /dev/null
+++ b/shell/themed-icon.c
@@ -0,0 +1,159 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "themed-icon.h"
+
+#include "mate-utils.h"
+
+static void themed_icon_finalize (GObject *);
+static void themed_icon_get_property (GObject *, guint, GValue *, GParamSpec *);
+static void themed_icon_set_property (GObject *, guint, const GValue *, GParamSpec *);
+
+static void themed_icon_show (GtkWidget *);
+static void themed_icon_style_updated (GtkWidget *);
+
+enum
+{
+ PROP_0,
+ PROP_ICON_ID,
+ PROP_ICON_SIZE
+};
+
+typedef struct
+{
+ gboolean icon_loaded;
+} ThemedIconPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (ThemedIcon, themed_icon, GTK_TYPE_IMAGE)
+
+static void themed_icon_class_init (ThemedIconClass * themed_icon_class)
+{
+ GObjectClass *g_obj_class = G_OBJECT_CLASS (themed_icon_class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (themed_icon_class);
+
+ g_obj_class->get_property = themed_icon_get_property;
+ g_obj_class->set_property = themed_icon_set_property;
+ g_obj_class->finalize = themed_icon_finalize;
+
+ widget_class->show = themed_icon_show;
+ widget_class->style_updated = themed_icon_style_updated;
+
+ g_object_class_install_property (g_obj_class, PROP_ICON_ID, g_param_spec_string ("icon-id",
+ "icon-id", "the identifier of the icon", NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (g_obj_class, PROP_ICON_SIZE,
+ g_param_spec_enum ("icon-size", "icon-size", "the size of the icon",
+ GTK_TYPE_ICON_SIZE, GTK_ICON_SIZE_BUTTON,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+}
+
+static void
+themed_icon_init (ThemedIcon * icon)
+{
+ ThemedIconPrivate *priv = themed_icon_get_instance_private (icon);
+
+ priv->icon_loaded = FALSE;
+}
+
+GtkWidget *
+themed_icon_new (const gchar * id, GtkIconSize size)
+{
+ GtkWidget *icon = GTK_WIDGET (g_object_new (
+ THEMED_ICON_TYPE, "icon-id", id, "icon-size", size, NULL));
+
+ return icon;
+}
+
+static void
+themed_icon_finalize (GObject * object)
+{
+ ThemedIcon *icon = THEMED_ICON (object);
+ if (icon->id)
+ g_free (icon->id);
+ (*G_OBJECT_CLASS (themed_icon_parent_class)->finalize) (object);
+}
+
+static void
+themed_icon_get_property (GObject * g_obj, guint prop_id, GValue * value, GParamSpec * param_spec)
+{
+ ThemedIcon *icon = THEMED_ICON (g_obj);
+
+ switch (prop_id)
+ {
+ case PROP_ICON_ID:
+ g_value_set_string (value, icon->id);
+ break;
+
+ case PROP_ICON_SIZE:
+ g_value_set_enum (value, icon->size);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+themed_icon_set_property (GObject * g_obj, guint prop_id, const GValue * value,
+ GParamSpec * param_spec)
+{
+ ThemedIcon *icon = THEMED_ICON (g_obj);
+
+ switch (prop_id)
+ {
+ case PROP_ICON_ID:
+ icon->id = g_strdup (g_value_get_string (value));
+
+/* gtk_image_load_by_id (GTK_IMAGE (icon), icon->size, icon->id); */
+
+ break;
+
+ case PROP_ICON_SIZE:
+ icon->size = g_value_get_enum (value);
+
+/* gtk_image_load_by_id (GTK_IMAGE (icon), icon->size, icon->id); */
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+themed_icon_show (GtkWidget * widget)
+{
+ ThemedIcon *icon = THEMED_ICON (widget);
+ ThemedIconPrivate *priv = themed_icon_get_instance_private (icon);
+
+ if (!priv->icon_loaded)
+ priv->icon_loaded = load_image_by_id (GTK_IMAGE (icon), icon->size, icon->id);
+
+ (*GTK_WIDGET_CLASS (themed_icon_parent_class)->show) (widget);
+}
+
+static void
+themed_icon_style_updated (GtkWidget * widget)
+{
+ ThemedIcon *icon = THEMED_ICON (widget);
+
+ load_image_by_id (GTK_IMAGE (icon), icon->size, icon->id);
+}
diff --git a/shell/themed-icon.h b/shell/themed-icon.h
new file mode 100644
index 00000000..d4604430
--- /dev/null
+++ b/shell/themed-icon.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of libslab.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libslab is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libslab is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __THEMED_ICON_H__
+#define __THEMED_ICON_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define THEMED_ICON_TYPE (themed_icon_get_type ())
+#define THEMED_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), THEMED_ICON_TYPE, ThemedIcon))
+#define THEMED_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), THEMED_ICON_TYPE, ThemedIconClass))
+#define IS_THEMED_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), THEMED_ICON_TYPE))
+#define IS_THEMED_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), THEMED_ICON_TYPE))
+#define THEMED_ICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), THEMED_ICON_TYPE, ThemedIconClass))
+
+typedef struct
+{
+ GtkImage parent;
+
+ GtkIconSize size;
+ gchar *id;
+} ThemedIcon;
+
+typedef struct
+{
+ GtkImageClass parent_class;
+} ThemedIconClass;
+
+GType themed_icon_get_type (void);
+GtkWidget *themed_icon_new (const gchar * id, GtkIconSize size);
+
+G_END_DECLS
+
+#endif /* __THEMED_ICON_H__ */
diff --git a/shell/tile-action.c b/shell/tile-action.c
new file mode 100644
index 00000000..c1d5906c
--- /dev/null
+++ b/shell/tile-action.c
@@ -0,0 +1,109 @@
+/*
+ * This file is part of libtile.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libtile is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libtile is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tile.h"
+
+G_DEFINE_TYPE (TileAction, tile_action, G_TYPE_OBJECT)
+
+static void tile_action_finalize (GObject *);
+static void tile_action_menu_item_activate_cb (GtkMenuItem *, gpointer);
+
+static void tile_action_class_init (TileActionClass * this_class)
+{
+ GObjectClass *g_obj_class = G_OBJECT_CLASS (this_class);
+
+ g_obj_class->finalize = tile_action_finalize;
+}
+
+static void
+tile_action_init (TileAction * this)
+{
+ this->tile = NULL;
+ this->func = 0;
+ this->menu_item = NULL;
+ this->flags = 0;
+}
+
+static void
+tile_action_finalize (GObject * g_object)
+{
+ TileAction *action = TILE_ACTION (g_object);
+ if (action->menu_item)
+ gtk_widget_destroy (GTK_WIDGET (action->menu_item));
+
+ (*G_OBJECT_CLASS (tile_action_parent_class)->finalize) (g_object);
+}
+
+TileAction *
+tile_action_new (Tile * tile, TileActionFunc func, const gchar * menu_item_markup, guint32 flags)
+{
+ TileAction *this = g_object_new (TILE_ACTION_TYPE, NULL);
+
+ this->tile = tile;
+ this->func = func;
+
+ if (menu_item_markup)
+ tile_action_set_menu_item_label (this, menu_item_markup);
+ else
+ this->menu_item = NULL;
+
+ this->flags = flags;
+
+ return this;
+}
+
+void
+tile_action_set_menu_item_label (TileAction * this, const gchar * markup)
+{
+ GtkWidget *label;
+
+ if (this->menu_item)
+ {
+ label = gtk_bin_get_child (GTK_BIN (this->menu_item));
+ gtk_label_set_markup (GTK_LABEL (label), markup);
+ }
+ else
+ {
+ label = gtk_label_new (markup);
+ gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+
+ this->menu_item = GTK_MENU_ITEM (gtk_menu_item_new ());
+ gtk_container_add (GTK_CONTAINER (this->menu_item), label);
+
+ g_signal_connect (this->menu_item, "activate",
+ G_CALLBACK (tile_action_menu_item_activate_cb),
+ this);
+ }
+}
+
+GtkMenuItem *
+tile_action_get_menu_item (TileAction * this)
+{
+ return this->menu_item;
+}
+
+static void
+tile_action_menu_item_activate_cb (GtkMenuItem * menu_item, gpointer user_data)
+{
+ TileAction *this = TILE_ACTION (user_data);
+
+ tile_trigger_action (this->tile, this);
+}
diff --git a/shell/tile.c b/shell/tile.c
new file mode 100644
index 00000000..96ff2120
--- /dev/null
+++ b/shell/tile.c
@@ -0,0 +1,529 @@
+/*
+ * This file is part of libtile.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libtile is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libtile is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "tile.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <string.h>
+
+#include "double-click-detector.h"
+
+typedef struct
+{
+ DoubleClickDetector *double_click_detector;
+
+ gboolean is_dragging;
+} TilePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (Tile, tile, GTK_TYPE_BUTTON)
+
+static void tile_finalize (GObject *);
+static void tile_dispose (GObject *);
+static void tile_get_property (GObject *, guint, GValue *, GParamSpec *);
+static void tile_set_property (GObject *, guint, const GValue *, GParamSpec *);
+static GObject *tile_constructor (GType, guint, GObjectConstructParam *);
+
+static void tile_setup (Tile *);
+
+static void tile_enter (GtkButton * widget);
+static void tile_leave (GtkButton * widget);
+static void tile_clicked (GtkButton *widget);
+
+static gboolean tile_focus_in (GtkWidget *, GdkEventFocus *);
+static gboolean tile_focus_out (GtkWidget *, GdkEventFocus *);
+static gboolean tile_draw (GtkWidget *, cairo_t *);
+static gboolean tile_button_release (GtkWidget *, GdkEventButton *);
+static gboolean tile_key_release (GtkWidget *, GdkEventKey *);
+static gboolean tile_popup_menu (GtkWidget *);
+
+static void tile_drag_begin (GtkWidget *, GdkDragContext *);
+static void tile_drag_data_get (GtkWidget *, GdkDragContext *, GtkSelectionData *, guint,
+guint);
+
+static void tile_tile_action_triggered (Tile *, TileEvent *, TileAction *);
+static void tile_action_triggered_event_marshal (GClosure *, GValue *, guint, const GValue *,
+gpointer, gpointer);
+
+typedef void (*marshal_func_VOID__POINTER_POINTER) (gpointer, gpointer, gpointer, gpointer);
+
+enum
+{
+ TILE_ACTIVATED_SIGNAL,
+ TILE_ACTION_TRIGGERED_SIGNAL,
+ LAST_SIGNAL
+};
+
+static guint tile_signals[LAST_SIGNAL] = { 0 };
+
+enum
+{
+ PROP_0,
+ PROP_TILE_URI,
+ PROP_TILE_CONTEXT_MENU,
+ PROP_TILE_ACTIONS
+};
+
+static void
+tile_class_init (TileClass * this_class)
+{
+ GObjectClass *g_obj_class = G_OBJECT_CLASS (this_class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (this_class);
+ GtkButtonClass *button_class = GTK_BUTTON_CLASS (this_class);
+
+ g_obj_class->constructor = tile_constructor;
+ g_obj_class->get_property = tile_get_property;
+ g_obj_class->set_property = tile_set_property;
+ g_obj_class->finalize = tile_finalize;
+ g_obj_class->dispose = tile_dispose;
+
+ widget_class->focus_in_event = tile_focus_in;
+ widget_class->focus_out_event = tile_focus_out;
+ widget_class->draw = tile_draw;
+ widget_class->button_release_event = tile_button_release;
+ widget_class->key_release_event = tile_key_release;
+ widget_class->drag_begin = tile_drag_begin;
+ widget_class->drag_data_get = tile_drag_data_get;
+ widget_class->popup_menu = tile_popup_menu;
+
+ button_class->enter = tile_enter;
+ button_class->leave = tile_leave;
+ button_class->clicked = tile_clicked;
+
+ this_class->tile_activated = NULL;
+ this_class->tile_action_triggered = tile_tile_action_triggered;
+
+ g_object_class_install_property (g_obj_class, PROP_TILE_URI,
+ g_param_spec_string ("tile-uri", "tile-uri", "the uri of the tile", NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (g_obj_class, PROP_TILE_CONTEXT_MENU,
+ g_param_spec_object ("context-menu", "context-menu",
+ "the context menu for the tile", GTK_TYPE_MENU, G_PARAM_READWRITE));
+
+ tile_signals[TILE_ACTIVATED_SIGNAL] = g_signal_new ("tile-activated",
+ G_TYPE_FROM_CLASS (this_class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (TileClass, tile_activated),
+ NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ tile_signals[TILE_ACTION_TRIGGERED_SIGNAL] = g_signal_new ("tile-action-triggered",
+ G_TYPE_FROM_CLASS (this_class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (TileClass, tile_action_triggered),
+ NULL, NULL, tile_action_triggered_event_marshal, G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
+}
+
+static GObject *
+tile_constructor (GType type, guint n_param, GObjectConstructParam * param)
+{
+ GObject *g_obj;
+ TilePrivate *priv;
+
+ g_obj = (*G_OBJECT_CLASS (tile_parent_class)->constructor) (type, n_param, param);
+
+ priv = tile_get_instance_private (TILE(g_obj));
+ priv->double_click_detector = double_click_detector_new ();
+
+ tile_setup (TILE (g_obj));
+
+ return g_obj;
+}
+
+static void
+tile_init (Tile * tile)
+{
+ TilePrivate *priv = tile_get_instance_private (tile);
+
+ tile->uri = NULL;
+ tile->context_menu = NULL;
+ tile->entered = FALSE;
+ tile->enabled = TRUE;
+
+ tile->actions = NULL;
+ tile->n_actions = 0;
+
+ tile->default_action = NULL;
+
+ priv->double_click_detector = NULL;
+ priv->is_dragging = FALSE;
+}
+
+static void
+tile_finalize (GObject * g_object)
+{
+ Tile *tile = TILE (g_object);
+ TilePrivate *priv = tile_get_instance_private (TILE(tile));
+
+ if (tile->n_actions) /* this will also free "default_action" entry */
+ {
+ g_free (tile->actions);
+ }
+
+ if (tile->uri)
+ g_free (tile->uri);
+
+ g_object_unref (priv->double_click_detector);
+
+ (*G_OBJECT_CLASS (tile_parent_class)->finalize) (g_object);
+}
+
+static void
+tile_dispose (GObject * g_object)
+{
+ Tile *tile = TILE (g_object);
+
+ /* free the TileAction object */
+ if (tile->n_actions)
+ {
+ gint x;
+ for (x = 0; x < tile->n_actions; x++)
+ {
+ if (tile->actions[x] != NULL) {
+ g_object_unref (tile->actions[x]);
+ tile->actions[x] = NULL;
+ }
+ }
+ }
+
+ /* free the GtkMenu object */
+ if (tile->context_menu != NULL) {
+ gtk_widget_destroy (GTK_WIDGET (tile->context_menu));
+ tile->context_menu = NULL;
+ }
+
+ (*G_OBJECT_CLASS (tile_parent_class)->dispose) (g_object);
+}
+
+static void
+tile_get_property (GObject * g_obj, guint prop_id, GValue * value, GParamSpec * param_spec)
+{
+ if (!IS_TILE (g_obj))
+ return;
+
+ switch (prop_id)
+ {
+ case PROP_TILE_URI:
+ g_value_set_string (value, TILE (g_obj)->uri);
+ break;
+
+ case PROP_TILE_CONTEXT_MENU:
+ g_value_set_object (value, TILE (g_obj)->context_menu);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+tile_set_property (GObject * g_obj, guint prop_id, const GValue * value, GParamSpec * param_spec)
+{
+ Tile *tile;
+ GtkMenu *menu;
+
+ if (!IS_TILE (g_obj))
+ return;
+
+ tile = TILE (g_obj);
+
+ switch (prop_id)
+ {
+ case PROP_TILE_URI:
+ tile->uri = g_strdup (g_value_get_string (value));
+ break;
+
+ case PROP_TILE_CONTEXT_MENU:
+ menu = g_value_get_object (value);
+
+ if (menu == tile->context_menu)
+ break;
+
+ if (tile->context_menu)
+ gtk_menu_detach (tile->context_menu);
+
+ tile->context_menu = menu;
+
+ if (tile->context_menu)
+ gtk_menu_attach_to_widget (tile->context_menu, GTK_WIDGET (tile), NULL);
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+tile_setup (Tile * tile)
+{
+ gtk_button_set_relief (GTK_BUTTON (tile), GTK_RELIEF_NONE);
+
+ if (tile->uri)
+ {
+ gtk_drag_source_set (GTK_WIDGET (tile), GDK_BUTTON1_MASK, NULL, 0,
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ gtk_drag_source_add_uri_targets (GTK_WIDGET (tile));
+ }
+}
+
+static void
+tile_enter (GtkButton * widget)
+{
+ gtk_widget_set_state_flags (GTK_WIDGET (widget), TILE_STATE_ENTERED, TRUE);
+
+ TILE (widget)->entered = TRUE;
+}
+
+static void
+tile_leave (GtkButton * widget)
+{
+ if (gtk_widget_has_focus (GTK_WIDGET (widget)))
+ gtk_widget_set_state_flags (GTK_WIDGET (widget), TILE_STATE_FOCUSED, TRUE);
+ else
+ gtk_widget_set_state_flags (GTK_WIDGET (widget), GTK_STATE_FLAG_NORMAL, TRUE);
+
+ TILE (widget)->entered = FALSE;
+}
+
+static void
+tile_clicked (GtkButton * widget)
+{
+ TileEvent *tile_event;
+ GdkEvent *event;
+ gboolean handled;
+
+ tile_event = g_new0 (TileEvent, 1);
+ tile_event->type = TILE_EVENT_ACTIVATED_DOUBLE_CLICK;
+ tile_event->time = gtk_get_current_event_time ();
+
+ g_signal_emit (widget, tile_signals[TILE_ACTIVATED_SIGNAL], 0, tile_event);
+ g_signal_emit_by_name (widget, "button-release-event", &event, &handled);
+
+ g_free (tile_event);
+}
+
+static gboolean
+tile_focus_in (GtkWidget * widget, GdkEventFocus * event)
+{
+ gtk_widget_set_state_flags (widget, TILE_STATE_FOCUSED, TRUE);
+
+ return FALSE;
+}
+
+static gboolean
+tile_focus_out (GtkWidget * widget, GdkEventFocus * event)
+{
+ if (TILE (widget)->entered)
+ gtk_widget_set_state_flags (widget, TILE_STATE_ENTERED, TRUE);
+ else
+ gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_NORMAL, TRUE);
+
+ return FALSE;
+}
+
+static gboolean
+tile_draw (GtkWidget * widget, cairo_t * cr)
+{
+ /* FIXME: there ought to be a better way to prevent the focus from being rendered. */
+
+ gboolean has_focus;
+ gboolean retval;
+
+ if ((has_focus = gtk_widget_has_focus (widget)))
+ gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_FOCUSED);
+
+ retval = (*GTK_WIDGET_CLASS (tile_parent_class)->draw) (widget, cr);
+
+ if (has_focus)
+ gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_FOCUSED, TRUE);
+
+ return retval;
+}
+
+static gboolean
+tile_button_release (GtkWidget * widget, GdkEventButton * event)
+{
+ Tile *tile = TILE (widget);
+ TilePrivate *priv = tile_get_instance_private (tile);
+
+ TileEvent *tile_event;
+ gboolean handled;
+
+ if (priv->is_dragging)
+ {
+ priv->is_dragging = FALSE;
+
+ return TRUE;
+ }
+
+ switch (event->button)
+ {
+ case 1:
+ tile_event = g_new0 (TileEvent, 1);
+ tile_event->time = event->time;
+
+ if (double_click_detector_is_double_click (priv->double_click_detector, event->time,
+ TRUE))
+ tile_event->type = TILE_EVENT_ACTIVATED_DOUBLE_CLICK;
+ else
+ tile_event->type = TILE_EVENT_ACTIVATED_SINGLE_CLICK;
+
+ g_signal_emit (tile, tile_signals[TILE_ACTIVATED_SIGNAL], 0, tile_event);
+ g_signal_emit_by_name (GTK_BUTTON (widget), "button-release-event", &event, &handled);
+
+ g_free (tile_event);
+
+ break;
+
+ case 3:
+ if (GTK_IS_MENU (tile->context_menu))
+ gtk_menu_popup_at_widget (GTK_MENU (tile->context_menu),
+ widget,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ (const GdkEvent*) event);
+
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+tile_key_release (GtkWidget * widget, GdkEventKey * event)
+{
+ TileEvent *tile_event;
+
+ if (event->keyval == GDK_KEY_Return)
+ {
+ tile_event = g_new0 (TileEvent, 1);
+ tile_event->type = TILE_EVENT_ACTIVATED_KEYBOARD;
+ tile_event->time = event->time;
+
+ g_signal_emit (widget, tile_signals[TILE_ACTIVATED_SIGNAL], 0, tile_event);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+tile_popup_menu (GtkWidget * widget)
+{
+ Tile *tile = TILE (widget);
+
+ if (GTK_IS_MENU (tile->context_menu))
+ {
+ gtk_menu_popup_at_widget (GTK_MENU (tile->context_menu),
+ widget,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_NORTH_WEST,
+ NULL);
+
+ return TRUE;
+ }
+
+ else
+ return FALSE;
+}
+
+static void
+tile_drag_begin (GtkWidget * widget, GdkDragContext * context)
+{
+ TilePrivate *priv;
+
+ priv = tile_get_instance_private (TILE(widget));
+ priv->is_dragging = TRUE;
+}
+
+static void
+tile_drag_data_get (GtkWidget * widget, GdkDragContext * context, GtkSelectionData * data,
+ guint info, guint time)
+{
+ gchar *uris[2];
+
+ if (TILE (widget)->uri)
+ {
+ uris[0] = TILE (widget)->uri;
+ uris[1] = NULL;
+
+ gtk_selection_data_set_uris (data, uris);
+ }
+}
+
+static void
+tile_tile_action_triggered (Tile * tile, TileEvent * event, TileAction * action)
+{
+ if (action && action->func)
+ (*action->func) (tile, event, action);
+}
+
+void
+tile_trigger_action (Tile * tile, TileAction * action)
+{
+ tile_trigger_action_with_time (tile, action, GDK_CURRENT_TIME);
+}
+
+void
+tile_trigger_action_with_time (Tile * tile, TileAction * action, guint32 time)
+{
+ TileEvent *event = g_new0 (TileEvent, 1);
+
+ event->type = TILE_EVENT_ACTION_TRIGGERED;
+ event->time = time;
+
+ g_signal_emit (tile, tile_signals[TILE_ACTION_TRIGGERED_SIGNAL], 0, event, action);
+ g_free (event);
+}
+
+static void
+tile_action_triggered_event_marshal (GClosure * closure, GValue * retval, guint n_param,
+ const GValue * param, gpointer invocation_hint, gpointer marshal_data)
+{
+ marshal_func_VOID__POINTER_POINTER callback;
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data_0, data_1;
+
+ g_return_if_fail (n_param == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data_0 = closure->data;
+ data_1 = g_value_peek_pointer (param);
+ }
+ else
+ {
+ data_0 = g_value_peek_pointer (param);
+ data_1 = closure->data;
+ }
+
+ if (marshal_data)
+ callback = (marshal_func_VOID__POINTER_POINTER) marshal_data;
+ else
+ callback = (marshal_func_VOID__POINTER_POINTER) cc->callback;
+
+ callback (data_0, g_value_peek_pointer (param + 1), g_value_peek_pointer (param + 2),
+ data_1);
+}
diff --git a/shell/tile.h b/shell/tile.h
new file mode 100644
index 00000000..9c8d3f2a
--- /dev/null
+++ b/shell/tile.h
@@ -0,0 +1,127 @@
+/*
+ * This file is part of libtile.
+ *
+ * Copyright (c) 2006 Novell, Inc.
+ *
+ * Libtile is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * Libtile is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libslab; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TILE_H__
+#define __TILE_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TILE_TYPE (tile_get_type ())
+#define TILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TILE_TYPE, Tile))
+#define TILE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), TILE_TYPE, TileClass))
+#define IS_TILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TILE_TYPE))
+#define IS_TILE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), TILE_TYPE))
+#define TILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TILE_TYPE, TileClass))
+#define TILE_ACTION_TYPE (tile_action_get_type ())
+#define TILE_ACTION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TILE_ACTION_TYPE, TileAction))
+#define TILE_ACTION_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), TILE_ACTION_TYPE, TileActionClass))
+#define IS_TILE_ACTION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TILE_ACTION_TYPE))
+#define IS_TILE_ACTION_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), TILE_ACTION_TYPE))
+#define TILE_ACTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TILE_ACTION_TYPE, TileActionClass))
+#define TILE_ACTION_CHECK_FLAG(action,flag) ((TILE_ACTION (action)->flags & (flag)) != 0)
+#define TILE_STATE_ENTERED GTK_STATE_FLAG_PRELIGHT
+#define TILE_STATE_FOCUSED GTK_STATE_FLAG_PRELIGHT
+
+typedef struct _Tile Tile;
+typedef struct _TileClass TileClass;
+typedef struct _TileAction TileAction;
+typedef struct _TileActionClass TileActionClass;
+typedef struct _TileEvent TileEvent;
+
+typedef void (*TileActionFunc) (Tile *, TileEvent *, TileAction *);
+
+typedef enum
+{
+ TILE_EVENT_ACTIVATED_SINGLE_CLICK,
+ TILE_EVENT_ACTIVATED_DOUBLE_CLICK,
+ TILE_EVENT_ACTIVATED_KEYBOARD,
+ TILE_EVENT_ACTION_TRIGGERED
+} TileEventType;
+
+typedef enum
+{
+ TILE_ACTION_OPENS_NEW_WINDOW = 1 << 0,
+ TILE_ACTION_OPENS_HELP = 1 << 1
+} TileActionFlags;
+
+struct _Tile
+{
+ GtkButton gtk_button;
+
+ gchar *uri;
+ GtkMenu *context_menu;
+ gboolean entered;
+ gboolean enabled;
+
+ TileAction **actions;
+ gint n_actions;
+
+ TileAction *default_action;
+};
+
+struct _TileClass
+{
+ GtkButtonClass gtk_button_class;
+
+ void (*tile_activated) (Tile *, TileEvent *);
+ void (*tile_action_triggered) (Tile *, TileEvent *, TileAction *);
+};
+
+struct _TileAction
+{
+ GObject parent;
+
+ Tile *tile;
+
+ TileActionFunc func;
+ GtkMenuItem *menu_item;
+
+ guint32 flags;
+};
+
+struct _TileActionClass
+{
+ GObjectClass parent_class;
+};
+
+struct _TileEvent
+{
+ TileEventType type;
+ guint32 time;
+};
+
+GType tile_get_type (void);
+GType tile_action_get_type (void);
+
+void tile_trigger_action (Tile * tile, TileAction * action);
+void tile_trigger_action_with_time (Tile * tile, TileAction * action, guint32 time);
+
+TileAction *tile_action_new (Tile * tile, TileActionFunc func, const gchar * menu_item_markup,
+ guint32 flags);
+
+void tile_action_set_menu_item_label (TileAction * action, const gchar * markup);
+GtkMenuItem *tile_action_get_menu_item (TileAction * action);
+
+G_END_DECLS
+
+#endif /* __TILE_H__ */