mirror of
https://gitlab.gnome.org/GNOME/glade.git
synced 2025-11-20 00:03:16 -05:00
* src/glade-utils.c: Remove redundant call to `gtk_window_set_position()'
in `glade_util_file_dialog_new()'. Fixes #408879.
svn path=/trunk/; revision=1118
1854 lines
46 KiB
C
1854 lines
46 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/*
|
|
* Copyright (C) 2001 Ximian, Inc.
|
|
*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Authors:
|
|
* Chema Celorio <chema@celorio.com>
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <gtk/gtktooltips.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <gmodule.h>
|
|
#include <glib/gi18n-lib.h>
|
|
#include <glib/gstdio.h>
|
|
#include <errno.h>
|
|
|
|
#include "glade.h"
|
|
#include "glade-project.h"
|
|
#include "glade-command.h"
|
|
#include "glade-debug.h"
|
|
#include "glade-placeholder.h"
|
|
#include "glade-widget.h"
|
|
#include "glade-widget-adaptor.h"
|
|
#include "glade-property.h"
|
|
#include "glade-property-class.h"
|
|
#include "glade-clipboard.h"
|
|
#include "glade-fixed.h"
|
|
|
|
#ifdef G_OS_WIN32
|
|
long __stdcall
|
|
ShellExecuteA (long hwnd,
|
|
const char* lpOperation,
|
|
const char* lpFile,
|
|
const char* lpParameters,
|
|
const char* lpDirectory,
|
|
int nShowCmd);
|
|
#endif
|
|
|
|
#define GLADE_UTIL_SELECTION_NODE_SIZE 7
|
|
#define GLADE_UTIL_COPY_BUFFSIZE 1024
|
|
|
|
#define GLADE_DEVHELP_ICON_NAME "devhelp"
|
|
#define GLADE_DEVHELP_FALLBACK_ICON_FILE "devhelp.png"
|
|
|
|
/* List of widgets that have selection
|
|
*/
|
|
static GList *glade_util_selection = NULL;
|
|
|
|
static GtkTooltips *glade_tooltips = NULL;
|
|
|
|
/**
|
|
* glade_util_widget_set_tooltip:
|
|
* @widget: a #GtkWidget
|
|
* @str: a string
|
|
*
|
|
* Creates a new tooltip from @str and sets @widget to use it.
|
|
*/
|
|
void
|
|
glade_util_widget_set_tooltip (GtkWidget *widget, const gchar *str)
|
|
{
|
|
g_return_if_fail (widget != NULL);
|
|
if (str == NULL)
|
|
return;
|
|
|
|
if (glade_tooltips == NULL)
|
|
{
|
|
glade_tooltips = gtk_tooltips_new ();
|
|
|
|
g_object_ref (G_OBJECT (glade_tooltips));
|
|
gtk_object_sink (GTK_OBJECT (glade_tooltips));
|
|
}
|
|
|
|
gtk_tooltips_set_tip (glade_tooltips, widget, str, NULL);
|
|
}
|
|
|
|
/**
|
|
* glade_util_compose_get_type_func:
|
|
* @name:
|
|
*
|
|
* TODO: write me
|
|
*
|
|
* Returns:
|
|
*/
|
|
static gchar *
|
|
glade_util_compose_get_type_func (const gchar *name)
|
|
{
|
|
gchar *retval;
|
|
GString *tmp;
|
|
gint i = 1, j;
|
|
|
|
tmp = g_string_new (name);
|
|
|
|
while (tmp->str[i])
|
|
{
|
|
if (g_ascii_isupper (tmp->str[i]))
|
|
{
|
|
tmp = g_string_insert_c (tmp, i++, '_');
|
|
|
|
j = 0;
|
|
while (g_ascii_isupper (tmp->str[i++]))
|
|
j++;
|
|
|
|
if (j > 2)
|
|
g_string_insert_c (tmp, i-2, '_');
|
|
|
|
continue;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
tmp = g_string_append (tmp, "_get_type");
|
|
retval = g_ascii_strdown (tmp->str, tmp->len);
|
|
g_string_free (tmp, TRUE);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* glade_util_get_type_from_name:
|
|
* @name: the name of the #GType - like 'GtkWidget' or a "get-type" function.
|
|
* @have_func: function-name flag -- true if the name is a "get-type" function.
|
|
*
|
|
* Returns the type using the "get type" function name based on @name.
|
|
* If the @have_func flag is true,@name is used directly, otherwise the get-type
|
|
* function is contrived from @name then used.
|
|
*
|
|
* Returns: the new #GType
|
|
*/
|
|
GType
|
|
glade_util_get_type_from_name (const gchar *name, gboolean have_func)
|
|
{
|
|
static GModule *allsymbols = NULL;
|
|
GType (*get_type) ();
|
|
GType type = 0;
|
|
gchar *func_name = (gchar*)name;
|
|
|
|
if ((type = g_type_from_name (name)) == 0 &&
|
|
(have_func || (func_name = glade_util_compose_get_type_func (name)) != NULL))
|
|
{
|
|
|
|
if (!allsymbols)
|
|
allsymbols = g_module_open (NULL, 0);
|
|
|
|
if (g_module_symbol (allsymbols, func_name,
|
|
(gpointer) &get_type))
|
|
{
|
|
g_assert (get_type);
|
|
type = get_type ();
|
|
}
|
|
else
|
|
{
|
|
g_warning (_("We could not find the symbol \"%s\""),
|
|
func_name);
|
|
}
|
|
g_free (func_name);
|
|
}
|
|
|
|
if (type == 0)
|
|
g_warning(_("Could not get the type from \"%s\""), name);
|
|
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* glade_utils_get_pspec_from_funcname:
|
|
* @funcname: the symbol name of a function to generate a #GParamSpec
|
|
*
|
|
* Returns: A #GParamSpec created by the delagate function
|
|
* specified by @funcname
|
|
*/
|
|
GParamSpec *
|
|
glade_utils_get_pspec_from_funcname (const gchar *funcname)
|
|
{
|
|
static GModule *allsymbols = NULL;
|
|
GParamSpec *pspec = NULL;
|
|
GParamSpec *(*get_pspec)() = NULL;
|
|
|
|
if (!allsymbols)
|
|
allsymbols = g_module_open (NULL, 0);
|
|
|
|
if (!g_module_symbol (allsymbols, funcname,
|
|
(gpointer) &get_pspec)) {
|
|
g_warning (_("We could not find the symbol \"%s\""),
|
|
funcname);
|
|
return FALSE;
|
|
}
|
|
|
|
g_assert (get_pspec);
|
|
pspec = get_pspec ();
|
|
|
|
return pspec;
|
|
}
|
|
|
|
/**
|
|
* glade_util_ui_message:
|
|
* @parent: a #GtkWindow cast as a #GtkWidget
|
|
* @format: a printf style format string
|
|
* @type: a #GladeUIMessageType
|
|
* @...: args for the format.
|
|
*
|
|
* Creates a new warning dialog window as a child of @parent containing
|
|
* the text of @format, runs it, then destroys it on close. Depending
|
|
* on @type, a cancel button may apear or the icon may change.
|
|
*
|
|
* Returns: True if the @type was GLADE_UI_ARE_YOU_SURE and the user
|
|
* selected "OK", True if the @type was GLADE_UI_YES_OR_NO and
|
|
* the user selected "YES"; False otherwise.
|
|
*/
|
|
gboolean
|
|
glade_util_ui_message (GtkWidget *parent,
|
|
GladeUIMessageType type,
|
|
const gchar *format,
|
|
...)
|
|
{
|
|
GtkWidget *dialog;
|
|
GtkMessageType message_type = GTK_MESSAGE_INFO;
|
|
GtkButtonsType buttons_type = GTK_BUTTONS_OK;
|
|
va_list args;
|
|
gchar *string;
|
|
gint response;
|
|
|
|
va_start (args, format);
|
|
string = g_strdup_vprintf (format, args);
|
|
va_end (args);
|
|
|
|
/* Get message_type */
|
|
switch (type)
|
|
{
|
|
case GLADE_UI_INFO: message_type = GTK_MESSAGE_INFO; break;
|
|
case GLADE_UI_WARN:
|
|
case GLADE_UI_ARE_YOU_SURE:
|
|
message_type = GTK_MESSAGE_WARNING;
|
|
break;
|
|
case GLADE_UI_ERROR: message_type = GTK_MESSAGE_ERROR; break;
|
|
case GLADE_UI_YES_OR_NO: message_type = GTK_MESSAGE_QUESTION; break;
|
|
break;
|
|
default:
|
|
g_critical ("Bad arg for glade_util_ui_message");
|
|
break;
|
|
}
|
|
|
|
|
|
/* Get buttons_type */
|
|
switch (type)
|
|
{
|
|
case GLADE_UI_INFO:
|
|
case GLADE_UI_WARN:
|
|
case GLADE_UI_ERROR:
|
|
buttons_type = GTK_BUTTONS_OK;
|
|
break;
|
|
case GLADE_UI_ARE_YOU_SURE: buttons_type = GTK_BUTTONS_OK_CANCEL; break;
|
|
case GLADE_UI_YES_OR_NO: buttons_type = GTK_BUTTONS_YES_NO; break;
|
|
break;
|
|
default:
|
|
g_critical ("Bad arg for glade_util_ui_message");
|
|
break;
|
|
}
|
|
|
|
dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
message_type,
|
|
buttons_type,
|
|
string);
|
|
|
|
response = gtk_dialog_run (GTK_DIALOG (dialog));
|
|
|
|
gtk_widget_destroy (dialog);
|
|
g_free (string);
|
|
|
|
return (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_YES);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
GtkStatusbar *statusbar;
|
|
guint context_id;
|
|
guint message_id;
|
|
} FlashInfo;
|
|
|
|
static const guint32 flash_length = 3000;
|
|
|
|
static gboolean
|
|
remove_message_timeout (FlashInfo * fi)
|
|
{
|
|
gtk_statusbar_remove (fi->statusbar, fi->context_id, fi->message_id);
|
|
g_free (fi);
|
|
|
|
/* remove the timeout */
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* glade_utils_flash_message:
|
|
* @statusbar: The statusbar
|
|
* @context_id: The message context_id
|
|
* @format: The message to flash on the statusbar
|
|
*
|
|
* Flash a temporary message on the statusbar.
|
|
*/
|
|
void
|
|
glade_util_flash_message (GtkWidget *statusbar, guint context_id, gchar *format, ...)
|
|
{
|
|
va_list args;
|
|
FlashInfo *fi;
|
|
gchar *message;
|
|
|
|
g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
|
|
g_return_if_fail (format != NULL);
|
|
|
|
va_start (args, format);
|
|
message = g_strdup_vprintf (format, args);
|
|
va_end (args);
|
|
|
|
fi = g_new (FlashInfo, 1);
|
|
fi->statusbar = GTK_STATUSBAR (statusbar);
|
|
fi->context_id = context_id;
|
|
fi->message_id = gtk_statusbar_push (fi->statusbar, fi->context_id, message);
|
|
|
|
gtk_timeout_add (flash_length, (GtkFunction) remove_message_timeout, fi);
|
|
|
|
g_free (message);
|
|
}
|
|
|
|
static gint
|
|
glade_util_compare_uline_labels (const gchar *labela, const gchar *labelb)
|
|
{
|
|
for (;;) {
|
|
gunichar c1, c2;
|
|
|
|
if (*labela == '\0')
|
|
return (*labelb == '\0') ? 0 : -1;
|
|
if (*labelb == '\0')
|
|
return 1;
|
|
|
|
c1 = g_utf8_get_char (labela);
|
|
if (c1 == '_') {
|
|
labela = g_utf8_next_char (labela);
|
|
c1 = g_utf8_get_char (labela);
|
|
}
|
|
|
|
c2 = g_utf8_get_char (labelb);
|
|
if (c2 == '_') {
|
|
labelb = g_utf8_next_char (labelb);
|
|
c2 = g_utf8_get_char (labelb);
|
|
}
|
|
|
|
if (c1 < c2)
|
|
return -1;
|
|
if (c1 > c2)
|
|
return 1;
|
|
|
|
labela = g_utf8_next_char (labela);
|
|
labelb = g_utf8_next_char (labelb);
|
|
}
|
|
|
|
/* Shouldn't be reached. */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* glade_util_compare_stock_labels:
|
|
* @a: a #gconstpointer to a #GtkStockItem
|
|
* @b: a #gconstpointer to a #GtkStockItem
|
|
*
|
|
* This is a #GCompareFunc that compares the labels of two stock items,
|
|
* ignoring any '_' characters. It isn't particularly efficient.
|
|
*
|
|
* Returns: negative value if @a < @b; zero if @a = @b;
|
|
* positive value if @a > @b
|
|
*/
|
|
gint
|
|
glade_util_compare_stock_labels (gconstpointer a, gconstpointer b)
|
|
{
|
|
const gchar *stock_ida = a, *stock_idb = b;
|
|
GtkStockItem itema, itemb;
|
|
gboolean founda, foundb;
|
|
gint retval;
|
|
|
|
founda = gtk_stock_lookup (stock_ida, &itema);
|
|
foundb = gtk_stock_lookup (stock_idb, &itemb);
|
|
|
|
if (founda)
|
|
{
|
|
if (!foundb)
|
|
retval = -1;
|
|
else
|
|
/* FIXME: Not ideal for UTF-8. */
|
|
retval = glade_util_compare_uline_labels (itema.label, itemb.label);
|
|
}
|
|
else
|
|
{
|
|
if (!foundb)
|
|
retval = 0;
|
|
else
|
|
retval = 1;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* glade_util_gtk_combo_func:
|
|
* @data:
|
|
*
|
|
* TODO: write me
|
|
*
|
|
* Returns:
|
|
*/
|
|
gchar *
|
|
glade_util_gtk_combo_func (gpointer data)
|
|
{
|
|
GtkListItem * listitem = data;
|
|
|
|
/* I needed to pinch this as well - Damon. */
|
|
static const gchar *gtk_combo_string_key = "gtk-combo-string-value";
|
|
|
|
GtkWidget *label;
|
|
gchar *ltext = NULL;
|
|
|
|
ltext = (gchar *) gtk_object_get_data (GTK_OBJECT (listitem),
|
|
gtk_combo_string_key);
|
|
if (!ltext) {
|
|
label = GTK_BIN (listitem)->child;
|
|
if (!label || !GTK_IS_LABEL (label))
|
|
return NULL;
|
|
ltext = (gchar*) gtk_label_get_text (GTK_LABEL (label));
|
|
}
|
|
|
|
return ltext;
|
|
}
|
|
|
|
/* These are pinched from gtkcombo.c */
|
|
/**
|
|
* glade_util_gtk_combo_find:
|
|
* @combo:
|
|
*
|
|
* TODO: write me
|
|
*
|
|
* Returns:
|
|
*/
|
|
gpointer /* GtkListItem * */
|
|
glade_util_gtk_combo_find (GtkCombo * combo)
|
|
{
|
|
gchar *text;
|
|
gchar *ltext;
|
|
GList *clist;
|
|
int (*string_compare) (const char *, const char *);
|
|
|
|
if (combo->case_sensitive)
|
|
string_compare = strcmp;
|
|
else
|
|
string_compare = g_strcasecmp;
|
|
|
|
text = (gchar*) gtk_entry_get_text (GTK_ENTRY (combo->entry));
|
|
clist = GTK_LIST (combo->list)->children;
|
|
|
|
while (clist && clist->data) {
|
|
ltext = glade_util_gtk_combo_func (GTK_LIST_ITEM (clist->data));
|
|
if (!ltext)
|
|
continue;
|
|
if (!(*string_compare) (ltext, text))
|
|
return (GtkListItem *) clist->data;
|
|
clist = clist->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* glade_util_hide_window:
|
|
* @window: a #GtkWindow
|
|
*
|
|
* If you use this function to handle the delete_event of a window, when it
|
|
* will be shown again it will appear in the position where it was before
|
|
* beeing hidden.
|
|
*/
|
|
void
|
|
glade_util_hide_window (GtkWindow *window)
|
|
{
|
|
gint x, y;
|
|
|
|
g_return_if_fail (GTK_IS_WINDOW (window));
|
|
|
|
/* remember position of window for when it is used again */
|
|
gtk_window_get_position (window, &x, &y);
|
|
|
|
gtk_widget_hide (GTK_WIDGET (window));
|
|
|
|
gtk_window_move(window, x, y);
|
|
}
|
|
|
|
/**
|
|
* glade_util_file_dialog_new:
|
|
* @title: dialog title
|
|
* @parent: the parent #GtkWindow for the dialog
|
|
* @action: a #GladeUtilFileDialogType to say if the dialog will open or save
|
|
*
|
|
* Returns: a "glade file" file chooser dialog. The caller is responsible
|
|
* for showing the dialog
|
|
*/
|
|
GtkWidget *
|
|
glade_util_file_dialog_new (const gchar *title, GtkWindow *parent,
|
|
GladeUtilFileDialogType action)
|
|
{
|
|
GtkWidget *file_dialog;
|
|
GtkFileFilter *file_filter;
|
|
|
|
g_return_val_if_fail ((action == GLADE_FILE_DIALOG_ACTION_OPEN ||
|
|
action == GLADE_FILE_DIALOG_ACTION_SAVE), NULL);
|
|
|
|
file_dialog = gtk_file_chooser_dialog_new (title, parent, action,
|
|
GTK_STOCK_CANCEL,
|
|
GTK_RESPONSE_CANCEL,
|
|
action == GLADE_FILE_DIALOG_ACTION_OPEN ?
|
|
GTK_STOCK_OPEN : GTK_STOCK_SAVE,
|
|
GTK_RESPONSE_OK,
|
|
NULL);
|
|
|
|
file_filter = gtk_file_filter_new ();
|
|
gtk_file_filter_add_pattern (file_filter, "*");
|
|
gtk_file_filter_set_name (file_filter, _("All Files"));
|
|
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
|
|
|
|
file_filter = gtk_file_filter_new ();
|
|
gtk_file_filter_add_pattern (file_filter, "*.glade");
|
|
gtk_file_filter_set_name (file_filter, _("Glade Files"));
|
|
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
|
|
|
|
gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
|
|
|
|
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (file_dialog), TRUE);
|
|
gtk_dialog_set_default_response (GTK_DIALOG (file_dialog), GTK_RESPONSE_OK);
|
|
|
|
return file_dialog;
|
|
}
|
|
|
|
/**
|
|
* glade_util_replace:
|
|
* @str: a string
|
|
* @a: a #gchar
|
|
* @b: a #gchar
|
|
*
|
|
* Replaces each occurance of the character @a in @str to @b.
|
|
*/
|
|
void
|
|
glade_util_replace (gchar *str, gchar a, gchar b)
|
|
{
|
|
g_return_if_fail (str != NULL);
|
|
|
|
while (*str != 0)
|
|
{
|
|
if (*str == a)
|
|
*str = b;
|
|
|
|
str = g_utf8_next_char (str);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* glade_util_read_prop_name:
|
|
* @str: a string
|
|
*
|
|
* Return a usable version of a property identifier as found
|
|
* in a freshly parserd #GladeInterface
|
|
*/
|
|
gchar *
|
|
glade_util_read_prop_name (const gchar *str)
|
|
{
|
|
gchar *id;
|
|
|
|
g_return_val_if_fail (str != NULL, NULL);
|
|
|
|
id = g_strdup (str);
|
|
|
|
glade_util_replace (id, '_', '-');
|
|
|
|
if (strstr (id, "::"))
|
|
{
|
|
/* Extract the second half of "AtkObject::accessible_name"
|
|
*/
|
|
gchar **split = g_strsplit (id, "::", 0);
|
|
g_free (id);
|
|
id = g_strdup (split[1]);
|
|
g_strfreev (split);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
|
|
/**
|
|
* glade_util_duplicate_underscores:
|
|
* @name: a string
|
|
*
|
|
* Duplicates @name, but the copy has two underscores in place of any single
|
|
* underscore in the original.
|
|
*
|
|
* Returns: a newly allocated string
|
|
*/
|
|
gchar *
|
|
glade_util_duplicate_underscores (const gchar *name)
|
|
{
|
|
const gchar *tmp;
|
|
const gchar *last_tmp = name;
|
|
gchar *underscored_name = g_malloc (strlen (name) * 2 + 1);
|
|
gchar *tmp_underscored = underscored_name;
|
|
|
|
for (tmp = last_tmp; *tmp; tmp = g_utf8_next_char (tmp))
|
|
{
|
|
if (*tmp == '_')
|
|
{
|
|
memcpy (tmp_underscored, last_tmp, tmp - last_tmp + 1);
|
|
tmp_underscored += tmp - last_tmp + 1;
|
|
last_tmp = tmp + 1;
|
|
*tmp_underscored++ = '_';
|
|
}
|
|
}
|
|
|
|
memcpy (tmp_underscored, last_tmp, tmp - last_tmp + 1);
|
|
|
|
return underscored_name;
|
|
}
|
|
|
|
/* This returns the window that the given widget's position is relative to.
|
|
Usually this is the widget's parent's window. But if the widget is a
|
|
toplevel, we use its own window, as it doesn't have a parent.
|
|
Some widgets also lay out widgets in different ways. */
|
|
static GdkWindow*
|
|
glade_util_get_window_positioned_in (GtkWidget *widget)
|
|
{
|
|
GtkWidget *parent;
|
|
|
|
parent = widget->parent;
|
|
|
|
#ifdef USE_GNOME
|
|
/* BonoboDockItem widgets use a different window when floating.
|
|
FIXME: I've left this here so we remember to add it when we add
|
|
GNOME support. */
|
|
if (BONOBO_IS_DOCK_ITEM (widget)
|
|
&& BONOBO_DOCK_ITEM (widget)->is_floating) {
|
|
return BONOBO_DOCK_ITEM (widget)->float_window;
|
|
}
|
|
|
|
if (parent && BONOBO_IS_DOCK_ITEM (parent)
|
|
&& BONOBO_DOCK_ITEM (parent)->is_floating) {
|
|
return BONOBO_DOCK_ITEM (parent)->float_window;
|
|
}
|
|
#endif
|
|
|
|
if (parent)
|
|
return parent->window;
|
|
|
|
return widget->window;
|
|
}
|
|
|
|
static void
|
|
glade_util_draw_nodes (GdkWindow *window, GdkGC *gc,
|
|
gint x, gint y,
|
|
gint width, gint height)
|
|
{
|
|
if (width > GLADE_UTIL_SELECTION_NODE_SIZE && height > GLADE_UTIL_SELECTION_NODE_SIZE) {
|
|
gdk_draw_rectangle (window, gc, TRUE,
|
|
x, y,
|
|
GLADE_UTIL_SELECTION_NODE_SIZE,
|
|
GLADE_UTIL_SELECTION_NODE_SIZE);
|
|
gdk_draw_rectangle (window, gc, TRUE,
|
|
x, y + height - GLADE_UTIL_SELECTION_NODE_SIZE,
|
|
GLADE_UTIL_SELECTION_NODE_SIZE,
|
|
GLADE_UTIL_SELECTION_NODE_SIZE);
|
|
gdk_draw_rectangle (window, gc, TRUE,
|
|
x + width - GLADE_UTIL_SELECTION_NODE_SIZE, y,
|
|
GLADE_UTIL_SELECTION_NODE_SIZE,
|
|
GLADE_UTIL_SELECTION_NODE_SIZE);
|
|
gdk_draw_rectangle (window, gc, TRUE,
|
|
x + width - GLADE_UTIL_SELECTION_NODE_SIZE,
|
|
y + height - GLADE_UTIL_SELECTION_NODE_SIZE,
|
|
GLADE_UTIL_SELECTION_NODE_SIZE,
|
|
GLADE_UTIL_SELECTION_NODE_SIZE);
|
|
}
|
|
|
|
gdk_draw_rectangle (window, gc, FALSE, x, y, width - 1, height - 1);
|
|
}
|
|
|
|
/* This calculates the offset of the given window within its toplevel.
|
|
It also returns the toplevel. */
|
|
static void
|
|
glade_util_calculate_window_offset (GdkWindow *window,
|
|
gint *x, gint *y,
|
|
GdkWindow **toplevel)
|
|
{
|
|
gint tmp_x, tmp_y;
|
|
|
|
/* Calculate the offset of the window within its toplevel. */
|
|
*x = 0;
|
|
*y = 0;
|
|
|
|
for (;;) {
|
|
if (gdk_window_get_window_type (window) != GDK_WINDOW_CHILD)
|
|
break;
|
|
gdk_window_get_position (window, &tmp_x, &tmp_y);
|
|
*x += tmp_x;
|
|
*y += tmp_y;
|
|
window = gdk_window_get_parent (window);
|
|
}
|
|
|
|
*toplevel = window;
|
|
}
|
|
|
|
/* This returns TRUE if it is OK to draw the selection nodes for the given
|
|
selected widget inside the given window that has received an expose event.
|
|
For most widgets it returns TRUE, but if a selected widget is inside a
|
|
widget like a viewport, that uses its own coordinate system, then it only
|
|
returns TRUE if the expose window is inside the viewport as well. */
|
|
static gboolean
|
|
glade_util_can_draw_nodes (GtkWidget *sel_widget, GdkWindow *sel_win,
|
|
GdkWindow *expose_win)
|
|
{
|
|
GtkWidget *widget, *viewport = NULL;
|
|
GdkWindow *viewport_win = NULL;
|
|
|
|
/* Check if the selected widget is inside a viewport. */
|
|
for (widget = sel_widget->parent; widget; widget = widget->parent) {
|
|
if (GTK_IS_VIEWPORT (widget)) {
|
|
viewport = widget;
|
|
viewport_win = GTK_VIEWPORT (widget)->bin_window;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If there is no viewport-type widget above the selected widget,
|
|
it is OK to draw the selection anywhere. */
|
|
if (!viewport)
|
|
return TRUE;
|
|
|
|
/* If we have a viewport-type widget, check if the expose_win is
|
|
beneath the viewport. If it is, we can draw in it. If not, we
|
|
can't.*/
|
|
for (;;) {
|
|
if (expose_win == sel_win)
|
|
return TRUE;
|
|
if (expose_win == viewport_win)
|
|
return FALSE;
|
|
if (gdk_window_get_window_type (expose_win) != GDK_WINDOW_CHILD)
|
|
break;
|
|
expose_win = gdk_window_get_parent (expose_win);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* glade_util_draw_nodes_idle:
|
|
* @expose_win: a #GdkWindow
|
|
*
|
|
* Redraws any selection nodes that intersect @expose_win. Steps through all
|
|
* selected widgets, finds their coordinates, and calls glade_util_draw_nodes()
|
|
* if appropriate.
|
|
*
|
|
* Returns: %FALSE
|
|
*/
|
|
gboolean
|
|
glade_util_draw_nodes_idle (GdkWindow *expose_win)
|
|
{
|
|
GtkWidget *expose_widget;
|
|
gint expose_win_x, expose_win_y;
|
|
gint expose_win_w, expose_win_h;
|
|
GdkWindow *expose_toplevel;
|
|
GdkGC *gc;
|
|
GList *elem;
|
|
|
|
/* Check that the window is still alive. */
|
|
if (!gdk_window_is_viewable (expose_win))
|
|
goto out;
|
|
|
|
/* Find the corresponding GtkWidget */
|
|
gdk_window_get_user_data (expose_win, (gpointer)&expose_widget);
|
|
|
|
gc = expose_widget->style->black_gc;
|
|
|
|
/* Calculate the offset of the expose window within its toplevel. */
|
|
glade_util_calculate_window_offset (expose_win,
|
|
&expose_win_x,
|
|
&expose_win_y,
|
|
&expose_toplevel);
|
|
|
|
gdk_drawable_get_size (expose_win,
|
|
&expose_win_w, &expose_win_h);
|
|
|
|
/* Step through all the selected widgets. */
|
|
for (elem = glade_util_selection; elem; elem = elem->next) {
|
|
|
|
GtkWidget *sel_widget;
|
|
GdkWindow *sel_win, *sel_toplevel;
|
|
gint sel_x, sel_y, x, y, w, h;
|
|
|
|
sel_widget = elem->data;
|
|
|
|
if ((sel_win = glade_util_get_window_positioned_in (sel_widget)) == NULL)
|
|
continue;
|
|
|
|
/* Calculate the offset of the selected widget's window
|
|
within its toplevel. */
|
|
glade_util_calculate_window_offset (sel_win, &sel_x, &sel_y,
|
|
&sel_toplevel);
|
|
|
|
/* We only draw the nodes if the window that got the expose
|
|
event is in the same toplevel as the selected widget. */
|
|
if (expose_toplevel == sel_toplevel
|
|
&& glade_util_can_draw_nodes (sel_widget, sel_win,
|
|
expose_win)) {
|
|
x = sel_x + sel_widget->allocation.x - expose_win_x;
|
|
y = sel_y + sel_widget->allocation.y - expose_win_y;
|
|
w = sel_widget->allocation.width;
|
|
h = sel_widget->allocation.height;
|
|
|
|
/* Draw the selection nodes if they intersect the
|
|
expose window bounds. */
|
|
if (x < expose_win_w && x + w >= 0
|
|
&& y < expose_win_h && y + h >= 0) {
|
|
glade_util_draw_nodes (expose_win, gc,
|
|
x, y, w, h);
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
/* Remove the reference added in glade_util_queue_draw_nodes(). */
|
|
g_object_unref (G_OBJECT (expose_win));
|
|
|
|
/* Return FALSE so the idle handler isn't called again. */
|
|
return FALSE;
|
|
}
|
|
|
|
#define GLADE_DRAW_NODES_IDLE_PRIORITY GTK_PRIORITY_DEFAULT + 10
|
|
|
|
/**
|
|
* glade_util_queue_draw_nodes:
|
|
* @window: A #GdkWindow
|
|
*
|
|
* This function should be called whenever a widget in the interface receives
|
|
* an expose event. It sets up an idle function which will redraw any selection
|
|
* nodes that intersect the the exposed window.
|
|
*/
|
|
void
|
|
glade_util_queue_draw_nodes (GdkWindow *window)
|
|
{
|
|
g_return_if_fail (GDK_IS_WINDOW (window));
|
|
|
|
g_idle_add_full (GLADE_DRAW_NODES_IDLE_PRIORITY,
|
|
(GSourceFunc)glade_util_draw_nodes_idle,
|
|
window, NULL);
|
|
|
|
g_object_ref (G_OBJECT (window));
|
|
}
|
|
|
|
|
|
/**
|
|
* glade_util_add_selection:
|
|
* @widget: a #GtkWidget
|
|
*
|
|
* Add visual selection to this GtkWidget
|
|
*/
|
|
void
|
|
glade_util_add_selection (GtkWidget *widget)
|
|
{
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
if (glade_util_has_selection (widget))
|
|
return;
|
|
|
|
glade_util_selection =
|
|
g_list_prepend (glade_util_selection, widget);
|
|
gtk_widget_queue_draw (widget);
|
|
}
|
|
|
|
/**
|
|
* glade_util_remove_selection:
|
|
* @widget: a #GtkWidget
|
|
*
|
|
* Remove visual selection from this GtkWidget
|
|
*/
|
|
void
|
|
glade_util_remove_selection (GtkWidget *widget)
|
|
{
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
if (!glade_util_has_selection (widget))
|
|
return;
|
|
|
|
glade_util_selection =
|
|
g_list_remove (glade_util_selection, widget);
|
|
|
|
/* We redraw the parent, since the selection rectangle may not be
|
|
cleared if we just redraw the widget itself. */
|
|
gtk_widget_queue_draw (widget->parent ?
|
|
widget->parent : widget);
|
|
}
|
|
|
|
/**
|
|
* glade_util_clear_selection:
|
|
*
|
|
* Clear all visual selections
|
|
*/
|
|
void
|
|
glade_util_clear_selection (void)
|
|
{
|
|
GtkWidget *widget;
|
|
GList *list;
|
|
|
|
for (list = glade_util_selection;
|
|
list && list->data;
|
|
list = list->next)
|
|
{
|
|
widget = list->data;
|
|
gtk_widget_queue_draw (widget->parent ?
|
|
widget->parent : widget);
|
|
}
|
|
glade_util_selection =
|
|
(g_list_free (glade_util_selection), NULL);
|
|
}
|
|
|
|
/**
|
|
* glade_util_has_selection:
|
|
* @widget: a #GtkWidget
|
|
*
|
|
* Returns: %TRUE if @widget has visual selection, %FALSE otherwise
|
|
*/
|
|
gboolean
|
|
glade_util_has_selection (GtkWidget *widget)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
|
|
return g_list_find (glade_util_selection, widget) != NULL;
|
|
}
|
|
|
|
/**
|
|
* glade_util_get_selectoin:
|
|
*
|
|
* Returns: The list of selected #GtkWidgets
|
|
*/
|
|
GList *
|
|
glade_util_get_selection ()
|
|
{
|
|
return glade_util_selection;
|
|
}
|
|
|
|
/*
|
|
* taken from gtk... maybe someday we can convince them to
|
|
* expose gtk_container_get_all_children
|
|
*/
|
|
static void
|
|
gtk_container_children_callback (GtkWidget *widget,
|
|
gpointer client_data)
|
|
{
|
|
GList **children;
|
|
|
|
children = (GList**) client_data;
|
|
*children = g_list_prepend (*children, widget);
|
|
}
|
|
|
|
/**
|
|
* glade_util_container_get_all_children:
|
|
* @container: a #GtkContainer
|
|
*
|
|
* Use this to itterate over all children in a GtkContainer,
|
|
* as it used _forall() instead of _foreach() (and the GTK+ version
|
|
* of this function is simply not exposed).
|
|
*
|
|
* Note that glade_widget_class_get_children() is the high-level
|
|
* abstraction and will usually end up calling this function.
|
|
*
|
|
* Returns: a #GList giving the contents of @container
|
|
*/
|
|
GList *
|
|
glade_util_container_get_all_children (GtkContainer *container)
|
|
{
|
|
GList *children = NULL;
|
|
|
|
g_return_val_if_fail (GTK_IS_CONTAINER (container), NULL);
|
|
|
|
gtk_container_forall (container,
|
|
gtk_container_children_callback,
|
|
&children);
|
|
|
|
/* Preserve the natural order by reversing the list */
|
|
return g_list_reverse (children);
|
|
}
|
|
|
|
/**
|
|
* glade_util_count_placeholders:
|
|
* @parent: a #GladeWidget
|
|
*
|
|
* Returns the amount of #GladePlaceholders parented by @parent
|
|
*/
|
|
gint
|
|
glade_util_count_placeholders (GladeWidget *parent)
|
|
{
|
|
gint placeholders = 0;
|
|
GList *list, *children;
|
|
|
|
/* count placeholders */
|
|
if ((children = glade_widget_adaptor_get_children
|
|
(parent->adaptor, parent->object)) != NULL)
|
|
{
|
|
for (list = children; list && list->data; list = list->next)
|
|
{
|
|
if (GLADE_IS_PLACEHOLDER (list->data))
|
|
placeholders++;
|
|
}
|
|
g_list_free (children);
|
|
}
|
|
|
|
return placeholders;
|
|
}
|
|
|
|
static GtkTreeIter *
|
|
glade_util_find_iter (GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
GladeWidget *findme,
|
|
gint column)
|
|
{
|
|
GtkTreeIter *retval = NULL;
|
|
GladeWidget *widget;
|
|
GtkTreeIter *next;
|
|
|
|
g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
|
|
g_return_val_if_fail (iter != NULL, NULL);
|
|
|
|
next = gtk_tree_iter_copy (iter);
|
|
g_return_val_if_fail (next != NULL, NULL);
|
|
|
|
while (retval == NULL)
|
|
{
|
|
gtk_tree_model_get (model, next, column, &widget, -1);
|
|
if (widget == NULL) {
|
|
g_warning ("Could not get the glade widget from the model");
|
|
break;
|
|
}
|
|
else if (widget == findme)
|
|
{
|
|
retval = gtk_tree_iter_copy (next);
|
|
break;
|
|
}
|
|
else if (gtk_tree_model_iter_has_child (model, next))
|
|
{
|
|
GtkTreeIter child;
|
|
gtk_tree_model_iter_children (model, &child, next);
|
|
if ((retval = glade_util_find_iter
|
|
(model, &child, findme, column)) != NULL)
|
|
break;
|
|
}
|
|
|
|
if (!gtk_tree_model_iter_next (model, next))
|
|
break;
|
|
}
|
|
gtk_tree_iter_free (next);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* glade_util_find_iter_by_widget:
|
|
* @model: a #GtkTreeModel
|
|
* @findme: a #GladeWidget
|
|
* @column: a #gint
|
|
*
|
|
* Looks through @model for the #GtkTreeIter corresponding to
|
|
* @findme under @column.
|
|
*
|
|
* Returns: a newly allocated #GtkTreeIter from @model corresponding
|
|
* to @findme which should be freed with gtk_tree_iter_free()
|
|
*
|
|
*/
|
|
GtkTreeIter *
|
|
glade_util_find_iter_by_widget (GtkTreeModel *model,
|
|
GladeWidget *findme,
|
|
gint column)
|
|
{
|
|
GtkTreeIter iter;
|
|
if (gtk_tree_model_get_iter_first (model, &iter))
|
|
{
|
|
return glade_util_find_iter (model, &iter, findme, column);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
glade_util_basenames_match (const gchar *path1,
|
|
const gchar *path2)
|
|
{
|
|
gboolean match = FALSE;
|
|
gchar *bname1;
|
|
gchar *bname2;
|
|
|
|
if (path1 && path2)
|
|
{
|
|
bname1 = g_path_get_basename (path1);
|
|
bname2 = g_path_get_basename (path2);
|
|
|
|
match = !strcmp (bname1, bname2);
|
|
|
|
g_free (bname1);
|
|
g_free (bname2);
|
|
}
|
|
return match;
|
|
}
|
|
|
|
/**
|
|
* glade_util_purify_list:
|
|
* @list: A #GList
|
|
*
|
|
* Returns: A newly allocated version of @list with no
|
|
* duplicate data entries
|
|
*/
|
|
GList *
|
|
glade_util_purify_list (GList *list)
|
|
{
|
|
GList *l, *newlist = NULL;
|
|
|
|
for (l = list; l; l = l->next)
|
|
if (!g_list_find (newlist, l->data))
|
|
newlist = g_list_prepend (newlist, l->data);
|
|
|
|
g_list_free (list);
|
|
|
|
return g_list_reverse (newlist);
|
|
}
|
|
|
|
/**
|
|
* glade_util_added_in_list:
|
|
* @old: the old #GList
|
|
* @new: the new #GList
|
|
*
|
|
* Returns: A newly allocated #GList of elements that
|
|
* are in @new but not in @old
|
|
*
|
|
*/
|
|
GList *
|
|
glade_util_added_in_list (GList *old_list,
|
|
GList *new_list)
|
|
{
|
|
GList *added = NULL, *list;
|
|
|
|
for (list = new_list; list; list = list->next)
|
|
{
|
|
if (!g_list_find (old_list, list->data))
|
|
added = g_list_prepend (added, list->data);
|
|
}
|
|
|
|
return g_list_reverse (added);
|
|
}
|
|
|
|
/**
|
|
* glade_util_removed_from_list:
|
|
* @old: the old #GList
|
|
* @new: the new #GList
|
|
*
|
|
* Returns: A newly allocated #GList of elements that
|
|
* are in @old no longer in @new
|
|
*
|
|
*/
|
|
GList *
|
|
glade_util_removed_from_list (GList *old_list,
|
|
GList *new_list)
|
|
{
|
|
GList *added = NULL, *list;
|
|
|
|
for (list = old_list; list; list = list->next)
|
|
{
|
|
if (!g_list_find (new_list, list->data))
|
|
added = g_list_prepend (added, list->data);
|
|
}
|
|
|
|
return g_list_reverse (added);
|
|
}
|
|
|
|
|
|
/**
|
|
* glade_util_canonical_path:
|
|
* @path: any path that may contain ".." or "." components
|
|
*
|
|
* Returns: an absolute path to the specified file or directory
|
|
* that contains no ".." or "." components (this does
|
|
* not call readlink like realpath() does).
|
|
*
|
|
* Note: on some systems; I think its possible that we dont have
|
|
* permission to execute in the directory in which the glade
|
|
* file resides; I decided finally to do it this way anyway
|
|
* since libc's realpath() does exactly the same.
|
|
*/
|
|
gchar *
|
|
glade_util_canonical_path (const gchar *path)
|
|
{
|
|
gchar *orig_dir, *dirname, *basename, *direct_dir, *direct_name = NULL;
|
|
|
|
g_return_val_if_fail (path != NULL, NULL);
|
|
|
|
basename = g_path_get_basename (path);
|
|
|
|
if ((orig_dir = g_get_current_dir ()) != NULL)
|
|
{
|
|
if ((dirname = g_path_get_dirname (path)) != NULL)
|
|
{
|
|
if (g_chdir (dirname) == 0)
|
|
{
|
|
if ((direct_dir = g_get_current_dir ()) != NULL)
|
|
direct_name =
|
|
g_build_filename (direct_dir, basename, NULL);
|
|
else
|
|
g_warning ("g_path");
|
|
|
|
if (g_chdir (orig_dir) != 0)
|
|
g_warning ("Unable to chdir back to %s directory (%s)",
|
|
orig_dir, g_strerror (errno));
|
|
|
|
} else g_warning ("Unable to chdir to %s directory (%s)",
|
|
dirname, g_strerror (errno));
|
|
|
|
g_free (dirname);
|
|
} else g_warning ("Unable to get directory component of %s\n", path);
|
|
g_free (orig_dir);
|
|
}
|
|
|
|
if (basename) g_free (basename);
|
|
|
|
return direct_name;
|
|
}
|
|
|
|
static gboolean
|
|
glade_util_canonical_match (const gchar *src_path,
|
|
const gchar *dest_path)
|
|
{
|
|
gchar *canonical_src, *canonical_dest;
|
|
gboolean match;
|
|
canonical_src = glade_util_canonical_path (src_path);
|
|
canonical_dest = glade_util_canonical_path (dest_path);
|
|
|
|
match = (strcmp (canonical_src, canonical_dest) == 0);
|
|
|
|
g_free (canonical_src);
|
|
g_free (canonical_dest);
|
|
|
|
return match;
|
|
}
|
|
|
|
/**
|
|
* glade_util_copy_file:
|
|
* @src_path: the path to the source file
|
|
* @dest_path: the path to the destination file to create or overwrite.
|
|
*
|
|
* Copies a file from @src to @dest, queries the user
|
|
* if it involves overwriting the target and displays an
|
|
* error message upon failure.
|
|
*
|
|
* Returns: True if the copy was successfull.
|
|
*/
|
|
gboolean
|
|
glade_util_copy_file (const gchar *src_path,
|
|
const gchar *dest_path)
|
|
{
|
|
GIOChannel *src, *dest;
|
|
GError *error = NULL;
|
|
GIOStatus read_status, write_status = G_IO_STATUS_ERROR;
|
|
gchar buffer[GLADE_UTIL_COPY_BUFFSIZE];
|
|
gsize bytes_read, bytes_written, written;
|
|
gboolean success = FALSE;
|
|
|
|
/* FIXME: This may break if src_path & dest_path are actually
|
|
* the same file, right now the canonical comparison is the
|
|
* best check I have.
|
|
*/
|
|
if (glade_util_canonical_match (src_path, dest_path))
|
|
return FALSE;
|
|
|
|
if (g_file_test (dest_path, G_FILE_TEST_IS_REGULAR) != FALSE)
|
|
if (glade_util_ui_message
|
|
(glade_app_get_window(), GLADE_UI_YES_OR_NO,
|
|
_("%s exists.\nDo you want to replace it?"), dest_path) == FALSE)
|
|
return FALSE;
|
|
|
|
|
|
if ((src = g_io_channel_new_file (src_path, "r", &error)) != NULL)
|
|
{
|
|
g_io_channel_set_encoding (src, NULL, NULL);
|
|
|
|
if ((dest = g_io_channel_new_file (dest_path, "w", &error)) != NULL)
|
|
{
|
|
g_io_channel_set_encoding (dest, NULL, NULL);
|
|
|
|
while ((read_status = g_io_channel_read_chars
|
|
(src, buffer, GLADE_UTIL_COPY_BUFFSIZE,
|
|
&bytes_read, &error)) != G_IO_STATUS_ERROR)
|
|
{
|
|
bytes_written = 0;
|
|
while ((write_status = g_io_channel_write_chars
|
|
(dest, buffer + bytes_written,
|
|
bytes_read - bytes_written,
|
|
&written, &error)) != G_IO_STATUS_ERROR &&
|
|
(bytes_written + written) < bytes_read)
|
|
bytes_written += written;
|
|
|
|
if (write_status == G_IO_STATUS_ERROR)
|
|
{
|
|
glade_util_ui_message (glade_app_get_window(),
|
|
GLADE_UI_ERROR,
|
|
_("Error writing to %s: %s"),
|
|
dest_path, error->message);
|
|
error = (g_error_free (error), NULL);
|
|
break;
|
|
}
|
|
|
|
/* Break on EOF & ERROR but not AGAIN and not NORMAL */
|
|
if (read_status == G_IO_STATUS_EOF) break;
|
|
}
|
|
|
|
if (read_status == G_IO_STATUS_ERROR)
|
|
{
|
|
glade_util_ui_message (glade_app_get_window(),
|
|
GLADE_UI_ERROR,
|
|
_("Error reading %s: %s"),
|
|
src_path, error->message);
|
|
error = (g_error_free (error), NULL);
|
|
}
|
|
|
|
|
|
/* From here on, unless we have problems shutting down, succuss ! */
|
|
success = (read_status == G_IO_STATUS_EOF &&
|
|
write_status == G_IO_STATUS_NORMAL);
|
|
|
|
if (g_io_channel_shutdown (dest, TRUE, &error) != G_IO_STATUS_NORMAL)
|
|
{
|
|
glade_util_ui_message
|
|
(glade_app_get_window(),
|
|
GLADE_UI_ERROR,
|
|
_("Error shutting down I/O channel %s: %s"),
|
|
dest_path, error->message);
|
|
error = (g_error_free (error), NULL);
|
|
success = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glade_util_ui_message (glade_app_get_window(),
|
|
GLADE_UI_ERROR,
|
|
_("Failed to open %s for writing: %s"),
|
|
dest_path, error->message);
|
|
error = (g_error_free (error), NULL);
|
|
|
|
}
|
|
|
|
|
|
if (g_io_channel_shutdown (src, TRUE, &error) != G_IO_STATUS_NORMAL)
|
|
{
|
|
glade_util_ui_message (glade_app_get_window(),
|
|
GLADE_UI_ERROR,
|
|
_("Error shutting down io channel %s: %s"),
|
|
src_path, error->message);
|
|
success = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glade_util_ui_message (glade_app_get_window(),
|
|
GLADE_UI_ERROR,
|
|
_("Failed to open %s for reading: %s"),
|
|
src_path, error->message);
|
|
error = (g_error_free (error), NULL);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* glade_util_class_implements_interface:
|
|
* @class_type: A #GType
|
|
* @iface_type: A #GType
|
|
*
|
|
* Returns: whether @class_type implements the @iface_type interface
|
|
*/
|
|
gboolean
|
|
glade_util_class_implements_interface (GType class_type,
|
|
GType iface_type)
|
|
{
|
|
GType *ifaces;
|
|
guint n_ifaces, i;
|
|
gboolean implemented = FALSE;
|
|
|
|
if ((ifaces = g_type_interfaces (class_type, &n_ifaces)) != NULL)
|
|
{
|
|
for (i = 0; i < n_ifaces; i++)
|
|
if (ifaces[i] == iface_type)
|
|
{
|
|
implemented = TRUE;
|
|
break;
|
|
}
|
|
g_free (ifaces);
|
|
}
|
|
return implemented;
|
|
}
|
|
|
|
|
|
/**
|
|
* glade_util_load_library:
|
|
* @library_name: name of the library
|
|
*
|
|
* Loads the named library from the Glade modules directory, or failing that
|
|
* from the standard platform specific directories.
|
|
*
|
|
* The @library_name should not include any platform specifix prefix or suffix,
|
|
* those are automatically added, if needed, by g_module_build_path()
|
|
*
|
|
* Returns: a #GModule on success, or %NULL on failure.
|
|
*/
|
|
GModule *
|
|
glade_util_load_library (const gchar *library_name)
|
|
{
|
|
gchar *path;
|
|
GModule *module;
|
|
|
|
path = g_module_build_path (glade_app_get_modules_dir (), library_name);
|
|
|
|
if ((module = g_module_open (path, G_MODULE_BIND_LAZY)) == NULL)
|
|
{
|
|
g_warning (_("Unable to open the module %s (%s)."),
|
|
path, g_module_error());
|
|
}
|
|
|
|
g_free (path);
|
|
|
|
return module;
|
|
}
|
|
|
|
/**
|
|
* glade_util_file_is_writeable:
|
|
* @path: the path to the file
|
|
*
|
|
* Checks whether the file at @path is writeable
|
|
*
|
|
* Returns: TRUE if file is writeable
|
|
*/
|
|
gboolean
|
|
glade_util_file_is_writeable (const gchar *path)
|
|
{
|
|
GIOChannel *channel;
|
|
g_return_val_if_fail (path != NULL, FALSE);
|
|
|
|
/* The only way to really know if the file is writable */
|
|
if ((channel = g_io_channel_new_file (path, "a+", NULL)) != NULL)
|
|
{
|
|
g_io_channel_unref (channel);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* glade_util_have_devhelp:
|
|
*
|
|
* Returns: whether the devhelp module is loaded
|
|
*/
|
|
gboolean
|
|
glade_util_have_devhelp (void)
|
|
{
|
|
static gint have_devhelp = -1;
|
|
gchar *ptr;
|
|
gint cnt, ret, major, minor;
|
|
GError *error = NULL;
|
|
|
|
#define DEVHELP_OLD_MESSAGE \
|
|
"The DevHelp installed on your system is too old, " \
|
|
"devhelp feature will be disabled."
|
|
|
|
#define DEVHELP_MISSING_MESSAGE \
|
|
"No DevHelp installed on your system, " \
|
|
"devhelp feature will be disabled."
|
|
|
|
if (have_devhelp >= 0) return have_devhelp;
|
|
|
|
have_devhelp = 0;
|
|
|
|
if ((ptr = g_find_program_in_path ("devhelp")) != NULL)
|
|
{
|
|
g_free (ptr);
|
|
|
|
if (g_spawn_command_line_sync ("devhelp --version",
|
|
&ptr, NULL, &ret, &error))
|
|
{
|
|
/* If we have a successfull return code.. parse the output.
|
|
*/
|
|
if (ret == 0)
|
|
{
|
|
gchar name[16];
|
|
if ((cnt = sscanf (ptr, "%15s %d.%d\n",
|
|
name, &major, &minor)) == 3)
|
|
{
|
|
/* Devhelp 0.12 required.
|
|
*/
|
|
if (major >= 0 && minor >= 12)
|
|
have_devhelp = 1;
|
|
else
|
|
g_message (DEVHELP_OLD_MESSAGE);
|
|
}
|
|
else
|
|
|
|
{
|
|
if (ptr != NULL || strlen (ptr) > 0)
|
|
g_warning ("devhelp had unparsable output: "
|
|
"'%s' (parsed %d elements)", ptr, cnt);
|
|
else
|
|
g_message (DEVHELP_OLD_MESSAGE);
|
|
}
|
|
}
|
|
else g_warning ("devhelp had bad return code: '%d'", ret);
|
|
}
|
|
else
|
|
{
|
|
g_warning ("Error trying to launch devhelp: %s",
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
else g_message (DEVHELP_MISSING_MESSAGE);
|
|
|
|
return have_devhelp;
|
|
}
|
|
|
|
/**
|
|
* glade_util_get_devhelp_icon:
|
|
* @size: the preferred icon size
|
|
*
|
|
* Creates an image displaying the devhelp icon.
|
|
*
|
|
* Returns: a #GtkImage
|
|
*/
|
|
GtkWidget*
|
|
glade_util_get_devhelp_icon (GtkIconSize size)
|
|
{
|
|
GtkIconTheme *icon_theme;
|
|
GdkScreen *screen;
|
|
GtkWidget *image;
|
|
gchar *path;
|
|
|
|
image = gtk_image_new ();
|
|
screen = gtk_widget_get_screen (GTK_WIDGET (image));
|
|
icon_theme = gtk_icon_theme_get_for_screen (screen);
|
|
|
|
if (gtk_icon_theme_has_icon (icon_theme, GLADE_DEVHELP_ICON_NAME))
|
|
{
|
|
gtk_image_set_from_icon_name (GTK_IMAGE (image), GLADE_DEVHELP_ICON_NAME, size);
|
|
}
|
|
else
|
|
{
|
|
path = g_build_filename (glade_app_get_pixmaps_dir (), GLADE_DEVHELP_FALLBACK_ICON_FILE, NULL);
|
|
|
|
gtk_image_set_from_file (GTK_IMAGE (image), path);
|
|
|
|
g_free (path);
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
/**
|
|
* glade_util_search_devhep:
|
|
* @devhelp: the devhelp widget created by the devhelp module.
|
|
* @book: the devhelp book (or %NULL)
|
|
* @page: the page in the book (or %NULL)
|
|
* @search: the search string (or %NULL)
|
|
*
|
|
* Envokes devhelp with the appropriate search string
|
|
*
|
|
*/
|
|
void
|
|
glade_util_search_devhelp (const gchar *book,
|
|
const gchar *page,
|
|
const gchar *search)
|
|
{
|
|
GError *error = NULL;
|
|
gchar *book_comm = NULL, *page_comm = NULL;
|
|
gchar *string;
|
|
|
|
g_return_if_fail (glade_util_have_devhelp ());
|
|
|
|
if (book) book_comm = g_strdup_printf ("book:%s ", book);
|
|
if (page) page_comm = g_strdup_printf ("page:%s ", page);
|
|
|
|
string = g_strdup_printf ("devhelp -s \"%s%s%s\"",
|
|
book_comm ? book_comm : "",
|
|
page_comm ? page_comm : "",
|
|
search ? search : "");
|
|
|
|
if (g_spawn_command_line_async (string, &error) == FALSE)
|
|
{
|
|
g_warning ("Error envoking devhelp: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_free (string);
|
|
if (book_comm) g_free (book_comm);
|
|
if (page_comm) g_free (page_comm);
|
|
}
|
|
|
|
GtkWidget *
|
|
glade_util_get_placeholder_from_pointer (GtkContainer *container)
|
|
{
|
|
GtkWidget *toplevel;
|
|
GtkWidget *retval = NULL, *child;
|
|
GList *c, *l;
|
|
gint x, y, x2, y2;
|
|
|
|
g_return_val_if_fail (GTK_IS_CONTAINER (container), NULL);
|
|
|
|
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container));
|
|
|
|
gtk_widget_get_pointer (toplevel, &x, &y);
|
|
|
|
for (c = l = glade_util_container_get_all_children (container);
|
|
l;
|
|
l = g_list_next (l))
|
|
{
|
|
child = l->data;
|
|
|
|
if (GLADE_IS_PLACEHOLDER (child) &&
|
|
GTK_WIDGET_MAPPED (child))
|
|
{
|
|
gtk_widget_translate_coordinates (toplevel, child,
|
|
x, y, &x2, &y2);
|
|
|
|
|
|
if (x2 >= 0 && y2 >= 0 &&
|
|
x2 <= child->allocation.width &&
|
|
y2 <= child->allocation.height)
|
|
{
|
|
retval = child;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_list_free (c);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* glade_util_object_is_loading:
|
|
* @object: A #GObject
|
|
*
|
|
* Returns: Whether the object's project is being loaded or not.
|
|
*
|
|
*/
|
|
gboolean
|
|
glade_util_object_is_loading (GObject *object)
|
|
{
|
|
GladeProject *project;
|
|
GladeWidget *widget;
|
|
|
|
g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
|
|
|
|
widget = glade_widget_get_from_gobject (object);
|
|
g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE);
|
|
|
|
project = glade_widget_get_project (widget);
|
|
|
|
return glade_project_is_loading (project);
|
|
}
|
|
|
|
#ifdef G_OS_WIN32
|
|
#define SW_NORMAL 1
|
|
|
|
static gboolean
|
|
glade_util_url_show_win32 (const gchar *url)
|
|
{
|
|
gint retval;
|
|
/* win32 API call */
|
|
retval = ShellExecuteA (NULL, "open", url, NULL, NULL, SW_NORMAL);
|
|
|
|
if (code <= 32)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#else
|
|
|
|
/* pilfered from Beast - birnetutils.cc */
|
|
static gboolean
|
|
glade_util_url_show_unix (const gchar *url)
|
|
{
|
|
static struct {
|
|
const gchar *prg, *arg1, *prefix, *postfix;
|
|
gboolean asyncronous; /* start asyncronously and check exit code to catch launch errors */
|
|
volatile gboolean disabled;
|
|
} browsers[] = {
|
|
|
|
/* configurable, working browser launchers */
|
|
{ "gnome-open", NULL, "", "", 0 }, /* opens in background, correct exit_code */
|
|
{ "exo-open", NULL, "", "", 0 }, /* opens in background, correct exit_code */
|
|
|
|
/* non-configurable working browser launchers */
|
|
{ "kfmclient", "openURL", "", "", 0 }, /* opens in background, correct exit_code */
|
|
{ "gnome-moz-remote", "--newwin", "", "", 0 }, /* opens in background, correct exit_code */
|
|
|
|
#if 0 /* broken/unpredictable browser launchers */
|
|
{ "browser-config", NULL, "", "", 0 }, /* opens in background (+ sleep 5), broken exit_code (always 0) */
|
|
{ "xdg-open", NULL, "", "", 0 }, /* opens in foreground (first browser) or background, correct exit_code */
|
|
{ "sensible-browser", NULL, "", "", 0 }, /* opens in foreground (first browser) or background, correct exit_code */
|
|
{ "htmlview", NULL, "", "", 0 }, /* opens in foreground (first browser) or background, correct exit_code */
|
|
#endif
|
|
|
|
/* direct browser invocation */
|
|
{ "x-www-browser", NULL, "", "", 1 }, /* opens in foreground, browser alias */
|
|
{ "firefox", NULL, "", "", 1 }, /* opens in foreground, correct exit_code */
|
|
{ "mozilla-firefox", NULL, "", "", 1 }, /* opens in foreground, correct exit_code */
|
|
{ "mozilla", NULL, "", "", 1 }, /* opens in foreground, correct exit_code */
|
|
{ "konqueror", NULL, "", "", 1 }, /* opens in foreground, correct exit_code */
|
|
{ "opera", "-newwindow", "", "", 1 }, /* opens in foreground, correct exit_code */
|
|
{ "epiphany", NULL, "", "", 1 }, /* opens in foreground, correct exit_code */
|
|
{ "galeon", NULL, "", "", 1 }, /* opens in foreground, correct exit_code */
|
|
{ "amaya", NULL, "", "", 1 }, /* opens in foreground, correct exit_code */
|
|
{ "dillo", NULL, "", "", 1 }, /* opens in foreground, correct exit_code */
|
|
|
|
};
|
|
|
|
guint i;
|
|
for (i = 0; i < G_N_ELEMENTS (browsers); i++)
|
|
|
|
if (!browsers[i].disabled)
|
|
{
|
|
gchar *args[128] = { 0, };
|
|
guint n = 0;
|
|
gchar *string;
|
|
gchar fallback_error[64] = "Ok";
|
|
gboolean success;
|
|
|
|
args[n++] = (gchar*) browsers[i].prg;
|
|
|
|
if (browsers[i].arg1)
|
|
args[n++] = (gchar*) browsers[i].arg1;
|
|
|
|
string = g_strconcat (browsers[i].prefix, url, browsers[i].postfix, NULL);
|
|
args[n] = string;
|
|
|
|
if (!browsers[i].asyncronous) /* start syncronously and check exit code */
|
|
{
|
|
gint exit_status = -1;
|
|
success = g_spawn_sync (NULL, /* cwd */
|
|
args,
|
|
NULL, /* envp */
|
|
G_SPAWN_SEARCH_PATH,
|
|
NULL, /* child_setup() */
|
|
NULL, /* user_data */
|
|
NULL, /* standard_output */
|
|
NULL, /* standard_error */
|
|
&exit_status,
|
|
NULL);
|
|
success = success && !exit_status;
|
|
|
|
if (exit_status)
|
|
g_snprintf (fallback_error, sizeof (fallback_error), "exitcode: %u", exit_status);
|
|
|
|
}
|
|
else
|
|
{
|
|
success = g_spawn_async (NULL, /* cwd */
|
|
args,
|
|
NULL, /* envp */
|
|
G_SPAWN_SEARCH_PATH,
|
|
NULL, /* child_setup() */
|
|
NULL, /* user_data */
|
|
NULL, /* child_pid */
|
|
NULL);
|
|
}
|
|
|
|
g_free (string);
|
|
if (success)
|
|
return TRUE;
|
|
browsers[i].disabled = TRUE;
|
|
}
|
|
|
|
/* reset all disabled states if no browser could be found */
|
|
for (i = 0; i < G_N_ELEMENTS (browsers); i++)
|
|
browsers[i].disabled = FALSE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* glade_util_url_show:
|
|
* @url: An URL to display
|
|
*
|
|
* Portable function for showing an URL @url in a web browser.
|
|
*
|
|
* Returns: TRUE if a web browser was successfully launched, or FALSE
|
|
*
|
|
*/
|
|
gboolean
|
|
glade_util_url_show (const gchar *url)
|
|
{
|
|
g_return_val_if_fail (url != NULL, FALSE);
|
|
|
|
#ifdef G_OS_WIN32
|
|
return glade_util_url_show_win32 (url);
|
|
#else
|
|
return glade_util_url_show_unix (url);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* glade_util_get_file_mtime:
|
|
* @filename: A filename
|
|
* @error: return location for errors
|
|
*
|
|
* Gets the UTC modification time of file @filename.
|
|
*
|
|
* Returns: The mtime of the file, or %0 if the file attributes
|
|
* could not be read.
|
|
*/
|
|
time_t
|
|
glade_util_get_file_mtime (const gchar *filename, GError **error)
|
|
{
|
|
struct stat info;
|
|
gint retval;
|
|
|
|
retval = g_stat (filename, &info);
|
|
|
|
if (retval != 0) {
|
|
g_set_error (error,
|
|
G_FILE_ERROR,
|
|
g_file_error_from_errno (errno),
|
|
"could not stat file '%s': %s", filename, g_strerror (errno));
|
|
return (time_t) 0;
|
|
} else {
|
|
return info.st_mtime;
|
|
}
|
|
}
|