midori/midori/sokoke.c
Christian Dywan 0b066778a5 Refactor main.c and move _eval_script to sokoke
The diagnostic dialog actually performs better now
because we do the extension loading and session setup
in idle callbacks, aside from the code being more
easily readable in smaller units.
2008-12-25 00:41:11 +01:00

710 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.
*/
#include "sokoke.h"
#if HAVE_CONFIG_H
#include <config.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <glib/gprintf.h>
static gchar*
sokoke_js_string_utf8 (JSStringRef js_string)
{
size_t size_utf8;
gchar* string_utf8;
g_return_val_if_fail (js_string, NULL);
size_utf8 = JSStringGetMaximumUTF8CStringSize (js_string);
string_utf8 = g_new (gchar, size_utf8);
JSStringGetUTF8CString (js_string, string_utf8, size_utf8);
return string_utf8;
}
JSValueRef
sokoke_js_script_eval (JSContextRef js_context,
const gchar* script,
gchar** exception)
{
g_return_val_if_fail (js_context, FALSE);
g_return_val_if_fail (script, FALSE);
JSStringRef js_script = JSStringCreateWithUTF8CString (script);
JSValueRef js_exception = NULL;
JSValueRef js_value = JSEvaluateScript (js_context, js_script,
JSContextGetGlobalObject (js_context), NULL, 0, &js_exception);
if (!js_value && exception)
{
JSStringRef js_message = JSValueToStringCopy (js_context,
js_exception, NULL);
*exception = sokoke_js_string_utf8 (js_message);
JSStringRelease (js_message);
js_value = JSValueMakeNull (js_context);
}
JSStringRelease (js_script);
return js_value;
}
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, "%s", 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;
g_return_val_if_fail (command != NULL, FALSE);
g_return_val_if_fail (argument != NULL, FALSE);
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** parts;
gchar* search;
const gchar* search_uri;
KatzeItem* item;
g_return_val_if_fail (uri, NULL);
g_return_val_if_fail (!search_engines ||
katze_array_is_a (search_engines, KATZE_TYPE_ITEM), NULL);
/* Just return if it's a javascript: uri */
if (g_str_has_prefix (uri, "javascript:"))
return g_strdup (uri);
/* 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? */
search = strchr (uri, ':');
if (search && search[0] && !g_ascii_isalpha (search[1]))
if (!strchr (search, '.'))
return g_strconcat ("http://", uri, NULL);
if (!strcmp (uri, "localhost") || g_str_has_prefix (uri, "localhost/"))
return g_strconcat ("http://", uri, NULL);
parts = g_strsplit (uri, ".", 0);
if (!search && parts[0] && parts[1])
{
search = NULL;
if (!(parts[1][1] == '\0' && !g_ascii_isalpha (parts[1][0])))
if (!strchr (parts[0], ' ') && !strchr (parts[1], ' '))
search = g_strconcat ("http://", uri, NULL);
g_free (parts);
if (search)
return search;
}
/* 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_combo_box_add_strings (GtkComboBox* combobox,
const gchar* label_first, ...)
{
const gchar* label;
/* 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);
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_OSX,
SOKOKE_DESKTOP_UNKNOWN
} SokokeDesktop;
static SokokeDesktop
sokoke_get_desktop (void)
{
#ifdef HAVE_OSX
return SOKOKE_DESKTOP_OSX;
#else
static SokokeDesktop desktop = SOKOKE_DESKTOP_UNTESTED;
if (G_UNLIKELY (desktop == SOKOKE_DESKTOP_UNTESTED))
{
/* Are we running in Xfce? */
gint result;
gchar *out = NULL;
gboolean success = g_spawn_command_line_sync ("xprop -root _DT_SAVE_MODE",
&out, NULL, &result, NULL);
if (success && ! result && out != NULL && strstr (out, "xfce4") != NULL)
desktop = SOKOKE_DESKTOP_XFCE;
else
desktop = SOKOKE_DESKTOP_UNKNOWN;
g_free (out);
}
return desktop;
#endif
}
/**
* sokoke_xfce_header_new:
* @icon: an icon name
* @title: the title of the header
*
* Creates an Xfce style header *if* Xfce is running.
*
* Return value: A #GtkWidget or %NULL
*
* Since 0.1.2 @icon may be NULL, and a default is used.
**/
GtkWidget*
sokoke_xfce_header_new (const gchar* icon,
const gchar* title)
{
g_return_val_if_fail (title, NULL);
/* 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;
gchar* markup;
GtkWidget* xfce_heading;
GtkWidget* hbox;
GtkWidget* image;
GtkWidget* label;
xfce_heading = gtk_event_box_new ();
entry = gtk_entry_new ();
gtk_widget_modify_bg (xfce_heading, GTK_STATE_NORMAL,
&entry->style->base[GTK_STATE_NORMAL]);
hbox = gtk_hbox_new (FALSE, 12);
gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
if (icon)
image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_DIALOG);
else
image = gtk_image_new_from_stock (GTK_STOCK_PREFERENCES,
GTK_ICON_SIZE_DIALOG);
gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
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);
gtk_widget_destroy (entry);
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;
GtkWidget* label;
hbox = gtk_event_box_new ();
gtk_widget_modify_bg (hbox, GTK_STATE_NORMAL,
&hbox->style->bg[GTK_STATE_SELECTED]);
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;
FILE* fp;
data = g_key_file_to_data (key_file, NULL, error);
if (!data)
return FALSE;
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);
}
/**
* 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;
}