Logo Search packages:      
Sourcecode: matchbox-desktop version File versions  Download package

main.c

/* 
 * Copyright (C) 2007 OpenedHand Ltd
 *
 * This program 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.
 *
 * This program 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
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <config.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <ctype.h>
#include "taku-table.h"
#include "taku-icon-tile.h"
#include "taku-launcher-tile.h"
#include "xutil.h"

typedef struct {
  char *match;
  char *name;
} Category;

static GList *categories;
static GList *current_category;

static TakuTable *table;

static GtkLabel *switcher_label;

static gboolean
popup_menu (GtkButton *button, gpointer user_data);

/* Changes the current category: Updates the switcher label and table filter */
static void
set_category (GList *category_list_item)
{
  Category *category = category_list_item->data;

  gtk_label_set_text (switcher_label, category->name);
  taku_table_set_filter (table, category->match);

  current_category = category_list_item;
}

static void
prev_category (void)
{
  if (current_category->prev == NULL)
    current_category = g_list_last (categories);
  else
    current_category = current_category->prev;

  set_category (current_category);
}

static void
next_category (void)
{
  if (current_category->next == NULL)
    current_category = categories;
  else
    current_category = current_category->next;

  set_category (current_category);
}

/* Handle failed focus events by switching between categories */
static gboolean
focus_cb (GtkWidget *widget, GtkDirectionType direction, gpointer user_data)
{
  if (direction == GTK_DIR_LEFT) {
    prev_category ();

    gtk_widget_grab_focus (GTK_WIDGET (table));

    return TRUE;
  } else if (direction == GTK_DIR_RIGHT) {
    next_category ();

    gtk_widget_grab_focus (GTK_WIDGET (table));

    return TRUE;
  }

  return FALSE;
}

static void
position_menu (GtkMenu *menu,
               int *x, int *y, gboolean *push_in, gpointer user_data)
{
  GtkWidget *widget = GTK_WIDGET (user_data);

  gdk_window_get_origin (widget->window, x, y);

  *x += widget->allocation.x;
  *y += widget->allocation.y + widget->allocation.height;

  *push_in = TRUE;
}

static void
popdown_menu (GtkMenuShell *menu_shell, gpointer user_data)
{
  GtkToggleButton *button = GTK_TOGGLE_BUTTON (user_data);

  /* Button up */
  g_signal_handlers_block_by_func (button, popup_menu, NULL);
  gtk_toggle_button_set_active (button, FALSE);
  g_signal_handlers_unblock_by_func (button, popup_menu, NULL);

  /* Destroy menu */
  gtk_widget_destroy (GTK_WIDGET (menu_shell));
}

static gboolean
popup_menu (GtkButton *button, gpointer user_data)
{
  GtkWidget *menu;
  GList *l;

  /* Button down */
  g_signal_handlers_block_by_func (button, popup_menu, NULL);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
  g_signal_handlers_unblock_by_func (button, popup_menu, NULL);

  /* Create menu */
  menu = gtk_menu_new ();
  gtk_widget_set_size_request (menu, GTK_WIDGET (button)->allocation.width, -1);

  g_signal_connect (menu, "selection-done", G_CALLBACK (popdown_menu), button);

  for (l = categories; l; l = l->next) {
    Category *category = l->data;
    GtkWidget *menu_item, *label;

    menu_item = gtk_menu_item_new ();
    g_signal_connect_swapped (menu_item, "activate",
                              G_CALLBACK (set_category), l);
    gtk_widget_show (menu_item);
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);

    label = gtk_label_new (category->name);
    gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.0);
    gtk_widget_show (label);
    gtk_container_add (GTK_CONTAINER (menu_item), label);
  }

  /* Popup menu */
  gtk_menu_popup (GTK_MENU (menu),
                  NULL, NULL,
                  position_menu,
                  button,
                  1,
                  GDK_CURRENT_TIME);

  return TRUE;
}

/*
 * Load all .directory files from @vfolderdir, and add them as tables.
 */
static void
load_vfolder_dir (const char *vfolderdir)
{
  GError *error = NULL;
  FILE *fp;
  char name[NAME_MAX], *filename;
  Category *category;

  category = g_slice_new (Category);
  category->match = NULL;
  category->name  = g_strdup (_("All"));

  categories = NULL;
  categories = g_list_prepend (categories, category);

  filename = g_build_filename (vfolderdir, "Root.order", NULL);
  fp = fopen (filename, "r");
  if (fp == NULL) {
    g_warning ("Cannot read %s", filename);
    g_free (filename);
    return;
  }
  g_free (filename);

  while (fgets (name, NAME_MAX - 9, fp) != NULL) {
    char *match = NULL, *local_name = NULL;
    GKeyFile *key_file;

    if (name[0] == '#' || isspace (name[0]))
      continue;

    strcpy (name + strlen (name) - 1, ".directory");
  
    filename = g_build_filename (vfolderdir, name, NULL);

    key_file = g_key_file_new ();
    g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &error);
    if (error) {
      g_warning ("Cannot read %s: %s", filename, error->message);
      g_error_free (error);
      error = NULL;

      goto done;
    }

    match = g_key_file_get_string (key_file, "Desktop Entry", "Match", NULL);
    if (match == NULL)
      goto done;

    local_name = g_key_file_get_locale_string (key_file, "Desktop Entry",
                                               "Name", NULL, NULL);
    if (local_name == NULL) {
      g_warning ("Directory file %s does not contain a \"Name\" field",
                 filename);
      g_free (match);
      goto done;
    }

    category = g_slice_new (Category);
    category->match = match;
    category->name  = local_name;

    categories = g_list_append (categories, category);

  done:
    g_key_file_free (key_file);
    g_free (filename);
  }

  fclose (fp);
}

/*
 * Load all .desktop files in @datadir/applications/, and add them to @table.
 */
static void
load_data_dir (const char *datadir)
{
  GError *error = NULL;
  GDir *dir;
  char *directory;
  const char *name;

  g_assert (datadir);

  directory = g_build_filename (datadir, "applications", NULL);

  /* Check if the directory exists */
  if (! g_file_test (directory, G_FILE_TEST_IS_DIR)) {
    g_free (directory);
    return;
  }

  dir = g_dir_open (directory, 0, &error);
  if (error) {
    g_warning ("Cannot read %s: %s", directory, error->message);
    g_error_free (error);
    g_free (directory);
    return;
  }

  while ((name = g_dir_read_name (dir)) != NULL) {
    char *filename;
    GtkWidget *tile;
  
    if (! g_str_has_suffix (name, ".desktop"))
      continue;

    filename = g_build_filename (directory, name, NULL);

    /* TODO: load launcher data, probe that, and then create a tile */

    tile = taku_launcher_tile_for_desktop_file (filename);
    if (!tile)
      goto done;

    gtk_container_add (GTK_CONTAINER (table), tile);

  done:
    g_free (filename);
  }

  g_free (directory);
  g_dir_close (dir);
}

int
main (int argc, char **argv)
{
  GtkWidget *window, *box, *hbox, *button, *arrow, *scrolled, *viewport;
  const char * const *dirs;
  char *vfolder_dir;
#ifndef STANDALONE
  /* Sane defaults in case something terrible happens in x_get_workarea() */
  int x = 0, y = 0;
#endif
  int w = 640, h = 480;

  gtk_init (&argc, &argv);
  g_set_application_name (_("Desktop"));
  
  /* Register the magic TakuIcon size so that it can be controlled from the
     theme. */
  gtk_icon_size_register ("TakuIcon", 64, 64);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  g_signal_connect (window, "delete-event", G_CALLBACK (gtk_main_quit), NULL);
  gtk_widget_set_name (window, "TakuWindow");
  gtk_window_set_title (GTK_WINDOW (window), _("Desktop"));

#ifndef STANDALONE
  gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DESKTOP);
  gtk_window_set_skip_taskbar_hint (GTK_WINDOW (window), TRUE);
  if (x_get_workarea (&x, &y, &w, &h)) {
    gtk_window_set_default_size (GTK_WINDOW (window), w, h);
    gtk_window_move (GTK_WINDOW (window), x, y);
  }
#else
  gtk_window_set_default_size (GTK_WINDOW (window), w, h);
#endif

  gtk_widget_show (window);

  /* Main VBox */
  box = gtk_vbox_new (FALSE, 0);
  gtk_widget_show (box);
  gtk_container_add (GTK_CONTAINER (window), box);

  /* Navigation bar */
  hbox = gtk_hbox_new (FALSE, 0);
  gtk_widget_show (hbox);
  gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, TRUE, 0);

  button = gtk_button_new ();
  gtk_widget_set_name (button, "MatchboxDesktopPrevButton");
  gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
  g_signal_connect (button, "clicked", G_CALLBACK (prev_category), NULL);
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);

  arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
  gtk_widget_show (arrow);
  gtk_container_add (GTK_CONTAINER (button), arrow);

  button = gtk_toggle_button_new ();
  gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
  g_signal_connect (button, "clicked", G_CALLBACK (popup_menu), NULL);
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);

  switcher_label = GTK_LABEL (gtk_label_new (NULL));
  gtk_widget_show (GTK_WIDGET (switcher_label));
  gtk_container_add (GTK_CONTAINER (button), GTK_WIDGET (switcher_label));

  button = gtk_button_new ();
  gtk_widget_set_name (button, "MatchboxDesktopNextButton");
  gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
  g_signal_connect (button, "clicked", G_CALLBACK (next_category), NULL);
  gtk_widget_show (button);
  gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, TRUE, 0);

  arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
  gtk_widget_show (arrow);
  gtk_container_add (GTK_CONTAINER (button), arrow);

  /* Table area */
  scrolled = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
                                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  gtk_widget_show (scrolled);
  gtk_box_pack_start (GTK_BOX (box), scrolled, TRUE, TRUE, 0);

  viewport = gtk_viewport_new (NULL, NULL);
  gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport),
                                GTK_SHADOW_NONE);
  gtk_widget_show (viewport);
  gtk_container_add (GTK_CONTAINER (scrolled), viewport);

  table = TAKU_TABLE (taku_table_new ());
  g_signal_connect_after (table, "focus", G_CALLBACK (focus_cb), NULL);
  gtk_container_add (GTK_CONTAINER (viewport), GTK_WIDGET (table));

  /* Load matchbox vfolders */
  vfolder_dir = g_build_filename (g_get_home_dir (),
                                  ".matchbox", "vfolders", NULL);
  if (g_file_test (vfolder_dir, G_FILE_TEST_EXISTS))
    load_vfolder_dir (vfolder_dir);
  else
    load_vfolder_dir (PKGDATADIR "/vfolders");
  g_free (vfolder_dir);

  /* Show the 'All' category */
  set_category (categories);

  /* Load all desktop files in the system data directories, and the user data
     directory. TODO: would it be best to do this in an idle handler and
     populate the desktop incrementally? */
  for (dirs = g_get_system_data_dirs (); *dirs; dirs++) {
    load_data_dir (*dirs);
  }
  load_data_dir (g_get_user_data_dir ());

  /* Go! */
  gtk_widget_show (GTK_WIDGET (table));

  gtk_main ();

  /* Cleanup */
  gtk_widget_destroy (window);

  while (categories) {
    Category *category = categories->data;

    g_free (category->match);
    g_free (category->name);

    g_slice_free (Category, category);

    categories = g_list_delete_link (categories, categories);
  }

  return 0;
}

Generated by  Doxygen 1.6.0   Back to index