summaryrefslogtreecommitdiff
path: root/shell/launcher.c
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2014-03-04 20:31:45 +0100
committerJonny Lamb <jonny.lamb@collabora.co.uk>2014-03-04 20:35:34 +0100
commit816ffed24b033da36923e9e387d540e5e876a578 (patch)
treef930527256f2e5a0a5e5856e202f5e4ba707bdfa /shell/launcher.c
parentf49d4231e5681fe28378c4f3dcf445814eeaabc6 (diff)
downloadgraynard-816ffed24b033da36923e9e387d540e5e876a578.tar.gz
graynard-816ffed24b033da36923e9e387d540e5e876a578.zip
launcher: add new app launcher window and replace old grid with it
Diffstat (limited to 'shell/launcher.c')
-rw-r--r--shell/launcher.c420
1 files changed, 420 insertions, 0 deletions
diff --git a/shell/launcher.c b/shell/launcher.c
new file mode 100644
index 0000000..679b40a
--- /dev/null
+++ b/shell/launcher.c
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2014 Collabora Ltd.
+ *
+ * Author: Jonny Lamb <jonny.lamb@collabora.co.uk>
+ */
+
+#include "config.h"
+
+#include "launcher.h"
+
+#include "clock.h"
+#include "panel.h"
+#include "shell-app-system.h"
+
+enum {
+ PROP_0,
+ PROP_BACKGROUND,
+};
+
+enum {
+ APP_LAUNCHED,
+ N_SIGNALS
+};
+static guint signals[N_SIGNALS] = { 0 };
+
+struct WestonGtkLauncherPrivate {
+ /* background widget so we know the output size */
+ GtkWidget *background;
+ ShellAppSystem *app_system;
+ GtkWidget *scrolled_window;
+ GtkWidget *grid;
+};
+
+G_DEFINE_TYPE(WestonGtkLauncher, weston_gtk_launcher, GTK_TYPE_WINDOW)
+
+/* each grid item is 114x114 */
+#define GRID_ITEM_WIDTH 114
+
+static void
+weston_gtk_launcher_init (WestonGtkLauncher *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ WESTON_GTK_LAUNCHER_TYPE,
+ WestonGtkLauncherPrivate);
+}
+
+static gint
+sort_apps (gconstpointer a,
+ gconstpointer b)
+{
+ GAppInfo *info1 = G_APP_INFO (a);
+ GAppInfo *info2 = G_APP_INFO (b);
+ gchar *s1, *s2;
+ gint ret;
+
+ s1 = g_utf8_casefold (g_app_info_get_display_name (info1), -1);
+ s2 = g_utf8_casefold (g_app_info_get_display_name (info2), -1);
+
+ ret = g_strcmp0 (s1, s2);
+
+ g_free (s1);
+ g_free (s2);
+
+ return ret;
+}
+
+static gboolean
+get_child_position_cb (GtkOverlay *overlay,
+ GtkWidget *widget,
+ GdkRectangle *allocation,
+ gpointer user_data)
+{
+ GtkOverlayClass *klass = GTK_OVERLAY_GET_CLASS (overlay);
+
+ klass->get_child_position (overlay, widget, allocation);
+
+ /* use the same valign and halign properties, but given we have a
+ * border of 1px, respect it and don't draw the overlay widget over
+ * the border. */
+ allocation->x += 1;
+ allocation->y -= 1;
+ allocation->width -= 2;
+
+ return TRUE;
+}
+
+static gboolean
+app_launched_idle_cb (gpointer data)
+{
+ WestonGtkLauncher *self = data;
+ GtkAdjustment *adjustment;
+
+ /* make the scrolled window go back to the top */
+
+ adjustment = gtk_scrolled_window_get_vadjustment (
+ GTK_SCROLLED_WINDOW (self->priv->scrolled_window));
+
+ gtk_adjustment_set_value (adjustment, 0.0);
+
+ return FALSE;
+}
+
+static void
+clicked_cb (GtkWidget *widget,
+ GDesktopAppInfo *info)
+{
+ WestonGtkLauncher *self;
+
+ g_app_info_launch (G_APP_INFO (info), NULL, NULL, NULL);
+
+ self = g_object_get_data (G_OBJECT (widget), "launcher");
+ g_assert (self);
+ g_signal_emit (self, signals[APP_LAUNCHED], 0);
+
+ /* do this in an idle so it's not done so obviously onscreen */
+ g_idle_add (app_launched_idle_cb, self);
+}
+
+static gboolean
+app_enter_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GtkWidget *revealer)
+{
+ gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), TRUE);
+ return FALSE;
+}
+
+static gboolean
+app_leave_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GtkWidget *revealer)
+{
+ gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), FALSE);
+ return FALSE;
+}
+
+static GtkWidget *
+app_launcher_new_from_desktop_info (WestonGtkLauncher *self,
+ GDesktopAppInfo *info)
+{
+ GIcon *icon;
+ GtkWidget *alignment;
+ GtkWidget *image;
+ GtkWidget *button;
+ GtkWidget *overlay;
+ GtkWidget *revealer;
+ GtkWidget *label;
+ GtkWidget *ebox;
+
+ /* we need an ebox to catch enter and leave events */
+ ebox = gtk_event_box_new ();
+ gtk_style_context_add_class (gtk_widget_get_style_context (ebox),
+ "wgs-grid-item");
+
+ /* we use an overlay so we can have the app icon showing but use a
+ * GtkRevealer to show a label of the app's name. */
+ overlay = gtk_overlay_new ();
+ gtk_container_add (GTK_CONTAINER (ebox), overlay);
+
+ /* ...but each item has a border of 1px and we don't want the
+ * revealer to paint into the border, so overload this function to
+ * know where to put it. */
+ g_signal_connect (overlay, "get-child-position",
+ G_CALLBACK (get_child_position_cb), NULL);
+
+ revealer = gtk_revealer_new ();
+ g_object_set (revealer,
+ "halign", GTK_ALIGN_FILL, /* all the width */
+ "valign", GTK_ALIGN_END, /* only at the bottom */
+ NULL);
+ gtk_revealer_set_transition_type (GTK_REVEALER (revealer),
+ GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
+ gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), FALSE);
+ gtk_overlay_add_overlay (GTK_OVERLAY (overlay), revealer);
+
+ /* app name */
+ label = gtk_label_new (g_app_info_get_display_name (G_APP_INFO (info)));
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_style_context_add_class (gtk_widget_get_style_context (label), "wgs-grid-label");
+ gtk_container_add (GTK_CONTAINER (revealer), label);
+
+ /* icon button to load the app */
+ alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
+ gtk_container_add (GTK_CONTAINER (overlay), alignment);
+
+ icon = g_app_info_get_icon (G_APP_INFO (info));
+ image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
+ button = gtk_button_new ();
+ gtk_style_context_remove_class (
+ gtk_widget_get_style_context (button),
+ "button");
+ gtk_style_context_remove_class (
+ gtk_widget_get_style_context (button),
+ "image-button");
+ gtk_button_set_image (GTK_BUTTON (button), image);
+ g_object_set (image,
+ "margin", 30,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (alignment), button);
+
+ /* TODO: a bit ugly */
+ g_object_set_data (G_OBJECT (button), "launcher", self);
+ g_signal_connect (button, "clicked", G_CALLBACK (clicked_cb), info);
+
+ /* now we have set everything up, we can refernce the ebox and the
+ * revealer. enter will show the label and leave will hide the label. */
+ g_signal_connect (ebox, "enter-notify-event", G_CALLBACK (app_enter_cb), revealer);
+ g_signal_connect (ebox, "leave-notify-event", G_CALLBACK (app_leave_cb), revealer);
+
+ return ebox;
+}
+
+static void
+installed_changed_cb (ShellAppSystem *app_system,
+ WestonGtkLauncher *self)
+{
+ GHashTable *entries = shell_app_system_get_entries (app_system);
+ GList *l, *values;
+
+ gint output_width, output_height;
+ guint cols;
+ guint left, top;
+
+ /* remove all children first */
+ gtk_container_foreach (GTK_CONTAINER (self->priv->grid),
+ (GtkCallback) gtk_widget_destroy, NULL);
+
+ values = g_hash_table_get_values (entries);
+ values = g_list_sort (values, sort_apps);
+
+ weston_gtk_launcher_calculate (self, NULL, NULL, &cols);
+ cols--; /* because we start from zero here */
+
+ left = top = 0;
+ for (l = values; l; l = l->next)
+ {
+ GDesktopAppInfo *info = G_DESKTOP_APP_INFO (l->data);
+ GtkWidget *app = app_launcher_new_from_desktop_info (self, info);
+
+ gtk_grid_attach (GTK_GRID (self->priv->grid), app, left++, top, 1, 1);
+
+ if (left > cols)
+ {
+ left = 0;
+ top++;
+ }
+ }
+
+ g_list_free (values);
+
+ gtk_widget_show_all (self->priv->grid);
+}
+
+static void
+background_size_allocate_cb (GtkWidget *widget,
+ GdkRectangle *allocation,
+ WestonGtkLauncher *self)
+{
+ installed_changed_cb (self->priv->app_system, self);
+}
+
+static void
+weston_gtk_launcher_constructed (GObject *object)
+{
+ WestonGtkLauncher *self = WESTON_GTK_LAUNCHER (object);
+
+ G_OBJECT_CLASS (weston_gtk_launcher_parent_class)->constructed (object);
+
+ /* window properties */
+ gtk_window_set_title (GTK_WINDOW (self), "gtk shell");
+ gtk_window_set_decorated (GTK_WINDOW (self), FALSE);
+ gtk_widget_realize (GTK_WIDGET (self));
+
+ /* make it black and slightly alpha */
+ gtk_style_context_add_class (
+ gtk_widget_get_style_context (GTK_WIDGET (self)),
+ "wgs-grid");
+
+ /* scroll it */
+ self->priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (self), self->priv->scrolled_window);
+
+ /* main grid for apps */
+ self->priv->grid = gtk_grid_new ();
+ gtk_container_add (GTK_CONTAINER (self->priv->scrolled_window),
+ self->priv->grid);
+
+ /* fill the grid with apps */
+ self->priv->app_system = shell_app_system_get_default ();
+ g_signal_connect (self->priv->app_system, "installed-changed",
+ G_CALLBACK (installed_changed_cb), self);
+
+ /* refill the grid if the background is changed */
+ g_assert (self->priv->background != NULL);
+ g_signal_connect (self->priv->background, "size-allocate",
+ G_CALLBACK (background_size_allocate_cb), self);
+
+ /* now actually fill the grid */
+ installed_changed_cb (self->priv->app_system, self);
+}
+
+static void
+weston_gtk_launcher_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WestonGtkLauncher *self = WESTON_GTK_LAUNCHER (object);
+
+ switch (param_id)
+ {
+ case PROP_BACKGROUND:
+ g_value_set_object (value, self->priv->background);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+weston_gtk_launcher_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WestonGtkLauncher *self = WESTON_GTK_LAUNCHER (object);
+
+ switch (param_id)
+ {
+ case PROP_BACKGROUND:
+ self->priv->background = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+weston_gtk_launcher_class_init (WestonGtkLauncherClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *)klass;
+
+ object_class->constructed = weston_gtk_launcher_constructed;
+ object_class->get_property = weston_gtk_launcher_get_property;
+ object_class->set_property = weston_gtk_launcher_set_property;
+
+ g_object_class_install_property (object_class, PROP_BACKGROUND,
+ g_param_spec_object ("background",
+ "background",
+ "The #GtkWidget of the background of the output",
+ GTK_TYPE_WIDGET,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ signals[APP_LAUNCHED] = g_signal_new ("app-launched",
+ G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ NULL, G_TYPE_NONE, 0);
+
+ g_type_class_add_private (object_class, sizeof (WestonGtkLauncherPrivate));
+}
+
+GtkWidget *
+weston_gtk_launcher_new (GtkWidget *background_widget)
+{
+ return g_object_new (WESTON_GTK_LAUNCHER_TYPE,
+ "background", background_widget,
+ NULL);
+}
+
+void
+weston_gtk_launcher_calculate (WestonGtkLauncher *self,
+ gint *grid_window_width,
+ gint *grid_window_height,
+ gint *grid_cols)
+{
+ gint output_width, output_height;
+ gint panel_height;
+ gint usable_width, usable_height;
+ guint cols, rows;
+
+ gtk_widget_get_size_request (self->priv->background,
+ &output_width, &output_height);
+
+ panel_height = output_height * WESTON_GTK_PANEL_HEIGHT_RATIO;
+
+ /* the scrollbar is 13 pixels wide */
+ usable_width = output_width - WESTON_GTK_PANEL_WIDTH - 13;
+
+ /* we want the height from under the clock which means we need to
+ * work out where the clock is, then add the clock height */
+ usable_height = output_height - (((output_height - panel_height) / 2) + WESTON_GTK_CLOCK_HEIGHT);
+
+ cols = (guint) (usable_width / GRID_ITEM_WIDTH);
+ rows = (guint) (usable_height / GRID_ITEM_WIDTH);
+
+ /* we don't want any blank space at the bottom of the grid so make
+ * sure it's full and resize if necessary */
+ /* TODO: this might not actually be a great idea. */
+ if (cols > 0)
+ {
+ guint num_apps;
+
+ num_apps = g_hash_table_size (
+ shell_app_system_get_entries (self->priv->app_system));
+
+ /* worst case cols = 1 */
+ while ((num_apps % cols) > 0)
+ cols--;
+ }
+
+ if (grid_window_width)
+ *grid_window_width = (cols * GRID_ITEM_WIDTH) + 13; /* add back the 13 for the scrollbar */
+
+ if (grid_window_height)
+ *grid_window_height = (rows * GRID_ITEM_WIDTH);
+
+ if (grid_cols)
+ *grid_cols = cols;
+}