midori/midori/sokoke.c
2008-10-24 01:35:34 +02:00

711 lines
22 KiB
C

/*
Copyright (C) 2007-2008 Christian Dywan <christian@twotoasts.de>
This library 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.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif
#include "sokoke.h"
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <glib/gprintf.h>
static void
error_dialog (const gchar* short_message,
const gchar* detailed_message)
{
GtkWidget* dialog = gtk_message_dialog_new (
NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, short_message);
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
"%s", detailed_message);
gtk_widget_show (dialog);
g_signal_connect_swapped (dialog, "response",
G_CALLBACK (gtk_widget_destroy), dialog);
}
gboolean
sokoke_spawn_program (const gchar* command,
const gchar* argument)
{
gchar* argument_escaped;
gchar* command_ready;
gchar** argv;
GError* error;
argument_escaped = g_shell_quote (argument);
if (strstr (command, "%s"))
command_ready = g_strdup_printf (command, argument_escaped);
else
command_ready = g_strconcat (command, " ", argument_escaped, NULL);
error = NULL;
if (!g_shell_parse_argv (command_ready, NULL, &argv, &error))
{
error_dialog (_("Could not run external program."), error->message);
g_error_free (error);
g_free (command_ready);
g_free (argument_escaped);
return FALSE;
}
error = NULL;
if (!g_spawn_async (NULL, argv, NULL,
(GSpawnFlags)G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
NULL, NULL, NULL, &error))
{
error_dialog (_("Could not run external program."), error->message);
g_error_free (error);
}
g_strfreev (argv);
g_free (command_ready);
g_free (argument_escaped);
return TRUE;
}
gchar*
sokoke_magic_uri (const gchar* uri,
KatzeArray* search_engines)
{
gchar* current_dir;
gchar* result;
gchar* search;
const gchar* search_uri;
gchar** parts;
KatzeItem* item;
g_return_val_if_fail (uri, NULL);
if (search_engines)
g_return_val_if_fail (katze_array_is_a (search_engines, KATZE_TYPE_ITEM), NULL);
/* Add file:// if we have a local path */
if (g_path_is_absolute (uri))
return g_strconcat ("file://", uri, NULL);
/* Construct an absolute path if the file is relative */
if (g_file_test (uri, G_FILE_TEST_EXISTS)
&& g_file_test (uri, G_FILE_TEST_IS_REGULAR))
{
current_dir = g_get_current_dir ();
result = g_strconcat ("file://", current_dir,
G_DIR_SEPARATOR_S, uri, NULL);
g_free (current_dir);
return result;
}
/* Do we need to add a protocol? */
if (!strstr (uri, "://"))
{
/* Do we have a domain, ip address or localhost? */
if (strchr (uri, '.') != NULL || strchr (uri, ':')
|| !strcmp (uri, "localhost"))
return g_strconcat ("http://", uri, NULL);
/* We don't want to search? So return early. */
if (!search_engines)
return g_strdup (uri);
search = NULL;
search_uri = NULL;
/* Do we have a keyword and a string? */
parts = g_strsplit (uri, " ", 2);
if (parts[0] && parts[1])
{
item = katze_array_find_token (search_engines, parts[0]);
if (item)
search_uri = katze_item_get_uri (item);
}
g_free (parts);
if (search_uri)
search = g_strdup_printf (search_uri, parts[1]);
return search;
}
return g_strdup (uri);
}
void
sokoke_entry_setup_completion (GtkEntry* entry)
{
/* TODO: The current behavior works only with the beginning of strings
But we want to match "localhost" with "loc" and "hos" */
GtkEntryCompletion* completion = gtk_entry_completion_new ();
gtk_entry_completion_set_model (completion,
GTK_TREE_MODEL (gtk_list_store_new (1, G_TYPE_STRING)));
gtk_entry_completion_set_text_column (completion, 0);
gtk_entry_completion_set_minimum_key_length (completion, 3);
gtk_entry_set_completion (entry, completion);
/* FIXME: Completion doesn't work well, so it's disabled */
gtk_entry_completion_set_popup_completion (completion, FALSE);
}
void
sokoke_entry_append_completion (GtkEntry* entry, const gchar* text)
{
GtkEntryCompletion* completion = gtk_entry_get_completion (entry);
GtkTreeModel* completion_store = gtk_entry_completion_get_model (completion);
GtkTreeIter iter;
gtk_list_store_insert (GTK_LIST_STORE (completion_store), &iter, 0);
gtk_list_store_set (GTK_LIST_STORE (completion_store), &iter, 0, text, -1);
}
void
sokoke_combo_box_add_strings (GtkComboBox* combobox,
const gchar* label_first, ...)
{
/* Add a number of strings to a combobox, terminated with NULL
This works only for text comboboxes */
va_list args;
va_start (args, label_first);
const gchar* label;
for (label = label_first; label; label = va_arg (args, const gchar*))
gtk_combo_box_append_text (combobox, label);
va_end (args);
}
void sokoke_widget_set_visible (GtkWidget* widget, gboolean visible)
{
/* Show or hide the widget */
if (visible)
gtk_widget_show (widget);
else
gtk_widget_hide (widget);
}
void
sokoke_container_show_children (GtkContainer* container)
{
/* Show every child but not the container itself */
gtk_container_foreach (container, (GtkCallback)(gtk_widget_show_all), NULL);
}
void
sokoke_widget_popup (GtkWidget* widget,
GtkMenu* menu,
GdkEventButton* event,
SokokeMenuPos pos)
{
katze_widget_popup (widget, menu, event, (KatzeMenuPos)pos);
}
typedef enum
{
SOKOKE_DESKTOP_UNTESTED,
SOKOKE_DESKTOP_XFCE,
SOKOKE_DESKTOP_UNKNOWN
} SokokeDesktop;
static SokokeDesktop
sokoke_get_desktop (void)
{
#ifdef GDK_WINDOWING_X11
static SokokeDesktop desktop = SOKOKE_DESKTOP_UNTESTED;
if (G_UNLIKELY (desktop == SOKOKE_DESKTOP_UNTESTED))
{
/* Are we running in Xfce? */
gint result; gchar* out; gchar* err;
gboolean success = g_spawn_command_line_sync (
"xprop -root _DT_SAVE_MODE | grep -q xfce4",
&out, &err, &result, NULL);
if (success && !result)
desktop = SOKOKE_DESKTOP_XFCE;
else
desktop = SOKOKE_DESKTOP_UNKNOWN;
}
return desktop;
#else
return SOKOKE_DESKTOP_UNKNOWN;
#endif
}
GtkWidget*
sokoke_xfce_header_new (const gchar* icon,
const gchar* title)
{
/* Create an xfce header with icon and title
This returns NULL if the desktop is not Xfce */
if (sokoke_get_desktop () == SOKOKE_DESKTOP_XFCE)
{
GtkWidget* entry = gtk_entry_new ();
gchar* markup;
GtkWidget* xfce_heading = gtk_event_box_new ();
gtk_widget_modify_bg (xfce_heading, GTK_STATE_NORMAL,
&entry->style->base[GTK_STATE_NORMAL]);
GtkWidget* hbox = gtk_hbox_new (FALSE, 12);
gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
GtkWidget* image = gtk_image_new_from_icon_name (icon,
GTK_ICON_SIZE_DIALOG);
gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
GtkWidget* label = gtk_label_new (NULL);
gtk_widget_modify_fg (label, GTK_STATE_NORMAL
, &entry->style->text[GTK_STATE_NORMAL]);
markup = g_strdup_printf ("<span size='large' weight='bold'>%s</span>",
title);
gtk_label_set_markup (GTK_LABEL (label), markup);
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_container_add (GTK_CONTAINER (xfce_heading), hbox);
g_free (markup);
return xfce_heading;
}
return NULL;
}
GtkWidget*
sokoke_superuser_warning_new (void)
{
/* Create a horizontal bar with a security warning
This returns NULL if the user is no superuser */
#if HAVE_UNISTD_H
if (G_UNLIKELY (!geteuid ())) /* effective superuser? */
{
GtkWidget* hbox = gtk_event_box_new ();
gtk_widget_modify_bg (hbox, GTK_STATE_NORMAL,
&hbox->style->bg[GTK_STATE_SELECTED]);
GtkWidget* label = gtk_label_new (
_("Warning: You are using a superuser account!"));
gtk_misc_set_padding (GTK_MISC (label), 0, 2);
gtk_widget_modify_fg (GTK_WIDGET (label), GTK_STATE_NORMAL,
&GTK_WIDGET (label)->style->fg[GTK_STATE_SELECTED]);
gtk_widget_show (label);
gtk_container_add (GTK_CONTAINER (hbox), GTK_WIDGET (label));
gtk_widget_show (hbox);
return hbox;
}
#endif
return NULL;
}
GtkWidget*
sokoke_hig_frame_new (const gchar* title)
{
/* Create a frame with no actual frame but a bold label and indentation */
GtkWidget* frame = gtk_frame_new (NULL);
gchar* title_bold = g_strdup_printf ("<b>%s</b>", title);
GtkWidget* label = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (label), title_bold);
g_free (title_bold);
gtk_frame_set_label_widget (GTK_FRAME (frame), label);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
return frame;
}
void
sokoke_widget_set_pango_font_style (GtkWidget* widget,
PangoStyle style)
{
/* Conveniently change the pango font style
For some reason we need to reset if we actually want the normal style */
if (style == PANGO_STYLE_NORMAL)
gtk_widget_modify_font (widget, NULL);
else
{
PangoFontDescription* font_description = pango_font_description_new ();
pango_font_description_set_style (font_description, PANGO_STYLE_ITALIC);
gtk_widget_modify_font (widget, font_description);
pango_font_description_free (font_description);
}
}
static gboolean
sokoke_on_entry_focus_in_event (GtkEntry* entry,
GdkEventFocus* event,
gpointer userdata)
{
gint has_default = GPOINTER_TO_INT (
g_object_get_data (G_OBJECT (entry), "sokoke_has_default"));
if (has_default)
{
gtk_entry_set_text (entry, "");
g_object_set_data (G_OBJECT (entry), "sokoke_has_default",
GINT_TO_POINTER (0));
sokoke_widget_set_pango_font_style (GTK_WIDGET (entry),
PANGO_STYLE_NORMAL);
}
return FALSE;
}
static gboolean
sokoke_on_entry_focus_out_event (GtkEntry* entry,
GdkEventFocus* event,
gpointer userdata)
{
const gchar* text = gtk_entry_get_text (entry);
if (text && !*text)
{
const gchar* default_text = (const gchar*)g_object_get_data (
G_OBJECT (entry), "sokoke_default_text");
gtk_entry_set_text (entry, default_text);
g_object_set_data (G_OBJECT (entry),
"sokoke_has_default", GINT_TO_POINTER (1));
sokoke_widget_set_pango_font_style (GTK_WIDGET (entry),
PANGO_STYLE_ITALIC);
}
return FALSE;
}
void
sokoke_entry_set_default_text (GtkEntry* entry,
const gchar* default_text)
{
/* Note: The default text initially overwrites any previous text */
gchar* old_value = g_object_get_data (G_OBJECT (entry),
"sokoke_default_text");
if (!old_value)
{
g_object_set_data (G_OBJECT (entry), "sokoke_has_default",
GINT_TO_POINTER (1));
sokoke_widget_set_pango_font_style (GTK_WIDGET (entry),
PANGO_STYLE_ITALIC);
gtk_entry_set_text (entry, default_text);
}
else if (!GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (entry)))
{
gint has_default = GPOINTER_TO_INT (
g_object_get_data (G_OBJECT (entry), "sokoke_has_default"));
if (has_default)
{
gtk_entry_set_text (entry, default_text);
sokoke_widget_set_pango_font_style (GTK_WIDGET (entry),
PANGO_STYLE_ITALIC);
}
}
g_object_set_data (G_OBJECT (entry), "sokoke_default_text",
(gpointer)default_text);
g_signal_connect (entry, "focus-in-event",
G_CALLBACK (sokoke_on_entry_focus_in_event), NULL);
g_signal_connect (entry, "focus-out-event",
G_CALLBACK (sokoke_on_entry_focus_out_event), NULL);
}
gchar*
sokoke_key_file_get_string_default (GKeyFile* key_file,
const gchar* group,
const gchar* key,
const gchar* default_value,
GError** error)
{
gchar* value = g_key_file_get_string (key_file, group, key, error);
return value == NULL ? g_strdup (default_value) : value;
}
gint
sokoke_key_file_get_integer_default (GKeyFile* key_file,
const gchar* group,
const gchar* key,
const gint default_value,
GError** error)
{
if (!g_key_file_has_key (key_file, group, key, NULL))
return default_value;
return g_key_file_get_integer (key_file, group, key, error);
}
gdouble
sokoke_key_file_get_double_default (GKeyFile* key_file,
const gchar* group,
const gchar* key,
const gdouble default_value,
GError** error)
{
if (!g_key_file_has_key (key_file, group, key, NULL))
return default_value;
return g_key_file_get_double (key_file, group, key, error);
}
gboolean
sokoke_key_file_get_boolean_default (GKeyFile* key_file,
const gchar* group,
const gchar* key,
const gboolean default_value,
GError** error)
{
if (!g_key_file_has_key (key_file, group, key, NULL))
return default_value;
return g_key_file_get_boolean (key_file, group, key, error);
}
gboolean
sokoke_key_file_save_to_file (GKeyFile* key_file,
const gchar* filename,
GError** error)
{
gchar* data = g_key_file_to_data (key_file, NULL, error);
if (!data)
return FALSE;
FILE* fp;
if (!(fp = fopen (filename, "w")))
{
*error = g_error_new (G_FILE_ERROR, G_FILE_ERROR_ACCES,
_("Writing failed."));
return FALSE;
}
fputs (data, fp);
fclose (fp);
g_free (data);
return TRUE;
}
void
sokoke_widget_get_text_size (GtkWidget* widget,
const gchar* text,
gint* width,
gint* height)
{
PangoLayout* layout = gtk_widget_create_pango_layout (widget, text);
pango_layout_get_pixel_size (layout, width, height);
g_object_unref (layout);
}
GdkPixbuf*
sokoke_web_icon (const gchar* icon,
GtkIconSize size,
GtkWidget* widget)
{
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
GdkPixbuf* pixbuf = NULL;
if (icon && *icon)
{
/* TODO: We want to allow http as well, maybe also base64? */
const gchar* icon_ready = g_str_has_prefix (icon, "file://")
? &icon[7] : icon;
GtkStockItem stock_id;
if (gtk_stock_lookup (icon, &stock_id))
pixbuf = gtk_widget_render_icon (widget, icon_ready, size, NULL);
else
{
gint width, height;
if (gtk_widget_has_screen (widget))
{
GdkScreen* screen = gtk_widget_get_screen (widget);
gtk_icon_size_lookup_for_settings (
gtk_settings_get_for_screen (screen),
size, &width, &height);
pixbuf = gtk_icon_theme_load_icon (
gtk_icon_theme_get_for_screen (screen), icon,
MAX (width, height), GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
}
}
if (!pixbuf)
pixbuf = gdk_pixbuf_new_from_file_at_size (icon_ready, 16, 16, NULL);
}
if (!pixbuf)
pixbuf = gtk_widget_render_icon (widget, GTK_STOCK_FIND, size, NULL);
return pixbuf;
}
gint
sokoke_object_get_int (gpointer object,
const gchar* property)
{
gint value = 0;
g_return_val_if_fail (object != NULL, FALSE);
g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
/* FIXME: Check value type */
g_object_get (object, property, &value, NULL);
return value;
}
gboolean
sokoke_object_get_boolean (gpointer object,
const gchar* property)
{
gboolean value = FALSE;
g_return_val_if_fail (object != NULL, FALSE);
g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
/* FIXME: Check value type */
g_object_get (object, property, &value, NULL);
return value;
}
/**
* sokoke_action_create_popup_menu_item:
* @action: a #GtkAction
*
* Creates a menu item from an action, just like
* gtk_action_create_menu_item(), but it won't
* display an accelerator.
*
* Note: This menu item is not a proxy and will
* not reflect any changes to the action.
*
* Return value: a new #GtkMenuItem
**/
GtkWidget*
sokoke_action_create_popup_menu_item (GtkAction* action)
{
GtkWidget* menuitem;
GtkWidget* icon;
gchar* label;
gchar* stock_id;
gchar* icon_name;
gboolean sensitive;
gboolean visible;
g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
g_object_get (action,
"label", &label,
"stock-id", &stock_id,
"icon-name", &icon_name,
"sensitive", &sensitive,
"visible", &visible,
NULL);
if (GTK_IS_TOGGLE_ACTION (action))
{
menuitem = gtk_check_menu_item_new_with_mnemonic (label);
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menuitem),
gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
}
else if (stock_id)
{
if (label)
{
menuitem = gtk_image_menu_item_new_with_mnemonic (label);
icon = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), icon);
}
else
menuitem = gtk_image_menu_item_new_from_stock (stock_id, NULL);
}
else
{
menuitem = gtk_image_menu_item_new_with_mnemonic (label);
if (icon_name)
{
icon = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), icon);
}
}
gtk_widget_set_sensitive (menuitem, sensitive);
sokoke_widget_set_visible (menuitem, visible);
g_signal_connect_swapped (menuitem, "activate",
G_CALLBACK (gtk_action_activate), action);
return menuitem;
}
/**
* sokoke_image_menu_item_new_ellipsized:
* @label: the text of the menu item
*
* Creates a new #GtkImageMenuItem containing an ellipsized label.
*
* Return value: a new #GtkImageMenuItem
**/
GtkWidget*
sokoke_image_menu_item_new_ellipsized (const gchar* label)
{
return katze_image_menu_item_new_ellipsized (label);
}
/**
* sokoke_tree_view_get_selected_iter:
* @tree_view: a #GtkTreeView
* @model: a pointer to store the model, or %NULL
* @iter: a pointer to store the iter, or %NULL
*
* Determines whether there is a selection in the tree view
* and sets the @iter to the current selection.
*
* If there is a selection and @model is not %NULL, it is
* set to the model, mainly for convenience.
*
* Either @model or @iter or both can be %NULL in which case
* no value will be assigned in any case.
*
* Return value: %TRUE if there is a selection
**/
gboolean
sokoke_tree_view_get_selected_iter (GtkTreeView* tree_view,
GtkTreeModel** model,
GtkTreeIter* iter)
{
GtkTreeSelection* selection;
g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE);
selection = gtk_tree_view_get_selection (tree_view);
if (selection)
if (gtk_tree_selection_get_selected (selection, model, iter))
return TRUE;
return FALSE;
}
/**
* sokoke_same_day:
* @day1: a time_t timestamp value
* @day2: a time_t timestamp value
*
* Compares two timestamps to see if their values are on the
* same day.
*
* Return value: %TRUE if the day of the timestamps match.
**/
gboolean
sokoke_same_day (const time_t* day1,
const time_t* day2)
{
struct tm* tm1;
struct tm* tm2;
gboolean same;
tm2 = localtime (day1);
tm1 = (struct tm*) g_memdup (tm2, sizeof (struct tm));
tm2 = localtime (day2);
same = (tm1->tm_year == tm2->tm_year &&
tm1->tm_mon == tm2->tm_mon &&
tm1->tm_mday == tm2->tm_mday) ? TRUE : FALSE;
g_free (tm1);
return same;
}
/**
* sokoke_days_between:
* @day1: a time_t timestamp value
* @day2: a time_t timestamp value
*
* Calculates the number of days between two timestamps.
*
* Return value: an integer.
**/
gint
sokoke_days_between (const time_t* day1,
const time_t* day2)
{
GDate* date1;
GDate* date2;
gint age;
date1 = g_date_new ();
date2 = g_date_new ();
g_date_set_time_t (date1, *day1);
g_date_set_time_t (date2, *day2);
age = g_date_days_between (date1, date2);
g_date_free (date1);
g_date_free (date2);
return age;
}