grim/guifications2

This was compatibility code for Pidgin 1.x.y; because it was written as a check
for "not 2.0.0" this code would be compiled in for 3.0.0. It looks like the
supporting code elsewhere was removed, causing this to leave an unresolved
symbol in guifications.so, thus causing Pidgin to refuse to load. Now we load
in Pidgin 3.0.0. Whether it works or not, I have no clue.
/*
* Guifications - The end all, be all, toaster popup plugin
* Copyright (C) 2003-2008 Gary Kramlich
*
* 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 02111-1301, USA.
*/
#include <gtk/gtk.h>
#ifdef HAVE_CONFIG_H
# include "../gf_config.h"
#endif
#include "gf_internal.h"
#include <account.h>
#include <blist.h>
#include <debug.h>
#include <gtkblist.h>
#include <gtkconv.h>
#include <gtkdialogs.h>
#include <gtklog.h>
#include <gtkpounce.h>
#include <gtkutils.h>
#include <pidginstock.h>
#include <plugin.h>
#include <version.h>
#ifdef HAVE_CONFIG_H
# include "../gf_config.h"
#endif
#include "gf_action.h"
#include "gf_display.h"
#include "gf_event.h"
#include "gf_event_info.h"
#include "gf_notification.h"
#include "gf_preferences.h"
#include "gf_utils.h"
struct _GfAction {
gchar *name;
gchar *i18n;
GfActionFunc func;
};
static GList *actions = NULL;
/*******************************************************************************
* API
******************************************************************************/
GfAction *
gf_action_new() {
GfAction *action;
action = g_new0(GfAction, 1);
return action;
}
void
gf_action_destroy(GfAction *action) {
g_return_if_fail(action);
g_free(action->name);
g_free(action->i18n);
g_free(action);
action = NULL;
}
void
gf_action_set_name(GfAction *action, const gchar *name) {
g_return_if_fail(action);
g_return_if_fail(name);
if(action->name)
g_free(action->name);
action->name = g_strdup(name);
}
const gchar *
gf_action_get_name(GfAction *action) {
g_return_val_if_fail(action, NULL);
return action->name;
}
void
gf_action_set_i18n(GfAction *action, const gchar *i18n) {
g_return_if_fail(action);
g_return_if_fail(i18n);
if(action->i18n)
g_free(action->i18n);
action->i18n = g_strdup(i18n);
}
const gchar *
gf_action_get_i18n(GfAction *action) {
g_return_val_if_fail(action, NULL);
return action->i18n;
}
void
gf_action_set_func(GfAction *action, GfActionFunc func) {
g_return_if_fail(action);
g_return_if_fail(func);
action->func = func;
}
GfActionFunc
gf_action_get_func(GfAction *action) {
g_return_val_if_fail(action, NULL);
return action->func;
}
void
gf_action_execute(GfAction *action, GfDisplay *display, GdkEventButton *event) {
g_return_if_fail(action);
g_return_if_fail(display);
action->func(display, event);
}
GfAction *
gf_action_find_with_name(const gchar *name) {
GfAction *action;
GList *l;
g_return_val_if_fail(name, NULL);
for(l = actions; l; l = l->next) {
action = GF_ACTION(l->data);
if(!g_ascii_strcasecmp(name, action->name))
return action;
}
return NULL;
}
GfAction *
gf_action_find_with_i18n(const gchar *i18n) {
GfAction *action;
GList *l;
g_return_val_if_fail(i18n, NULL);
for(l = actions; l; l = l->next) {
action = GF_ACTION(l->data);
if(!g_ascii_strcasecmp(i18n, action->i18n))
return action;
}
return NULL;
}
gint
gf_action_get_position(GfAction *action) {
g_return_val_if_fail(action, -1);
return g_list_index(actions, action);
}
/*******************************************************************************
* Sub System
******************************************************************************/
static void
gf_action_add_default(const gchar *name, const gchar *i18n, GfActionFunc func) {
GfAction *action;
g_return_if_fail(name);
g_return_if_fail(func);
action = gf_action_new();
gf_action_set_name(action, name);
gf_action_set_i18n(action, i18n);
gf_action_set_func(action, func);
gf_actions_add_action(action);
}
void
gf_actions_init() {
gf_action_add_default("close", _("Close"), gf_action_execute_close);
gf_action_add_default("open", _("Open Conversation"),
gf_action_execute_open_conv);
gf_action_add_default("context", _("Context Menu"),
gf_action_execute_context);
gf_action_add_default("info", _("Get Info"), gf_action_execute_info);
gf_action_add_default("log", _("Display Log"), gf_action_execute_log);
}
void
gf_actions_uninit() {
GList *l, *ll;
for(l = actions; l; l = ll) {
ll = l->next;
GfAction *action = GF_ACTION(l->data);
gf_actions_remove_action(GF_ACTION(l->data));
gf_action_destroy(action);
}
g_list_free(actions);
actions = NULL;
}
void
gf_actions_add_action(GfAction *action) {
g_return_if_fail(action);
actions = g_list_append(actions, action);
}
void
gf_actions_remove_action(GfAction *action) {
g_return_if_fail(action);
actions = g_list_remove(actions, action);
}
gint
gf_actions_count() {
return g_list_length(actions);
}
const gchar *
gf_actions_get_nth_name(gint nth) {
GfAction *action;
action = GF_ACTION(g_list_nth_data(actions, nth));
return action->name;
}
const gchar *
gf_actions_get_nth_i18n(gint nth) {
GfAction *action;
action = GF_ACTION(g_list_nth_data(actions, nth));
return action->i18n;
}
/*******************************************************************************
* Action Functions
******************************************************************************/
void
gf_action_execute_close(GfDisplay *display, GdkEventButton *gdk_event) {
g_return_if_fail(display);
gf_display_destroy(display);
}
static gboolean
conversation_exists(PurpleConversation *conv) {
PurpleConversation *lconv;
GList *l;
for(l = purple_get_conversations(); l; l = l->next) {
lconv = (PurpleConversation *)l->data;
if(conv == lconv)
return TRUE;
}
return FALSE;
}
void
gf_action_execute_open_conv(GfDisplay *display, GdkEventButton *gdk_event) {
GfEventInfo *info;
PurpleAccount *account = NULL;
PurpleBuddy *buddy = NULL;
PurpleConversation *conv = NULL;
const GHashTable *components = NULL;
const gchar *target;
g_return_if_fail(display);
info = gf_display_get_event_info(display);
account = gf_event_info_get_account(info);
buddy = gf_event_info_get_buddy(info);
conv = gf_event_info_get_conversation(info);
components = gf_event_info_get_components(info);
target = gf_event_info_get_target(info);
if(conv) { /* we have a conv */
if(!conversation_exists(conv)) {
/* the conv we have doesn't exist anymore.. */
const gchar *target;
target = gf_event_info_get_target(info);
conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, target, account);
}
} else if(components) { /* it's a chat invite */
const gchar *extra = gf_event_info_get_extra(info);
conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, extra, account);
if(!conv) {
serv_join_chat(account->gc, (GHashTable *)components);
gf_display_destroy(display);
return;
}
} else if (buddy) { /* we have a buddy or a target */
conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, account);
if(!conv)
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, buddy->name);
} else { /* the only thing that _should_ be left is warnings.. */
if(target) {
conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, target, account);
if(!conv)
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, target);
}
}
if(conv) {
purple_conversation_present(conv);
gf_display_destroy(display);
}
}
void
gf_action_execute_info(GfDisplay *display, GdkEventButton *gdk_event) {
GfEventInfo *info;
const gchar *target;
PurpleAccount *account;
g_return_if_fail(display);
info = gf_display_get_event_info(display);
account = gf_event_info_get_account(info);
target = gf_event_info_get_target(info);
/* This covers everything.
*
* We used to make a distinction between event types, but since we store
* the target for every event, and how you can't get info about a chat
* we just use the target. The target for a buddy event is the
* buddy->name, in a warning event it's the screen name of the person
* who warned you, and in a chat it's the person that said something.
*/
if(target) {
serv_get_info(account->gc, target);
gf_display_destroy(display);
}
}
void
gf_action_execute_log(GfDisplay *display, GdkEventButton *gdk_event) {
GfEventInfo *info;
PurpleAccount *account;
PurpleConversation *conv;
const gchar *target;
g_return_if_fail(display);
info = gf_display_get_event_info(display);
account = gf_event_info_get_account(info);
conv = gf_event_info_get_conversation(info);
target = gf_event_info_get_target(info);
if(conv) {
PurpleConversationType type;
type = purple_conversation_get_type(conv);
if(type == PURPLE_CONV_TYPE_IM || type == PURPLE_CONV_TYPE_CHAT) {
if(type == PURPLE_CONV_TYPE_IM) {
pidgin_log_show(type, target, account);
} else {
const gchar *name = purple_conversation_get_name(conv);
pidgin_log_show(type, name, account);
}
gf_display_destroy(display);
}
} else if(target) {
pidgin_log_show(PURPLE_CONV_TYPE_IM, target, account);
gf_display_destroy(display);
}
}
/******************************************************************************
* Context menu stuff
*****************************************************************************/
static gboolean
gf_action_context_destroy_cb(gpointer data) {
GfDisplay *display = GF_DISPLAY(data);
gf_display_destroy(display);
return FALSE;
}
static void
gf_action_context_hide_cb(GtkWidget *w, gpointer data) {
GfDisplay *display = GF_DISPLAY(data);
GfEventInfo *info = NULL;
gint display_time;
guint timeout_id;
g_return_if_fail(display);
info = gf_display_get_event_info(display);
g_return_if_fail(info);
display_time = purple_prefs_get_int(GF_PREF_BEHAVIOR_DISPLAY_TIME);
timeout_id = g_timeout_add(display_time * 500,
gf_action_context_destroy_cb, display);
gf_event_info_set_timeout_id(info, timeout_id);
}
static void
gf_action_context_position(GtkMenu *menu, gint *x, gint *y, gboolean pushin,
gpointer data)
{
GtkRequisition req;
gint scrheight = 0;
scrheight = gdk_screen_get_height(gtk_widget_get_screen(GTK_WIDGET(menu)));
gtk_widget_size_request(GTK_WIDGET(menu), &req);
if((*y + req.height > scrheight) && (scrheight - req.height > 0))
*y = scrheight - req.height;
}
static void
gf_action_context_info_cb(GtkWidget *menuitem, GfDisplay *display) {
gf_action_execute_info(display, NULL);
}
static void
gf_action_context_im_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info;
PurpleAccount *account;
PurpleConversation *conv = NULL;
PidginWindow *win = NULL;
const gchar *target;
info = gf_display_get_event_info(display);
account = gf_event_info_get_account(info);
target = gf_event_info_get_target(info);
conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, target, account);
if(!conv) {
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, target);
}
if(conv) {
win = PIDGIN_CONVERSATION(conv)->win;
if(!win) {
gf_display_destroy(display);
return;
}
pidgin_conv_window_switch_gtkconv(win, PIDGIN_CONVERSATION(conv));
gtk_window_present(GTK_WINDOW(win->window));
}
gf_display_destroy(display);
}
static void
gf_action_context_pounce_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info;
PurpleAccount *account = NULL;
PurpleBuddy *buddy = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
account = gf_event_info_get_account(info);
g_return_if_fail(account);
buddy = gf_event_info_get_buddy(info);
g_return_if_fail(buddy);
pidgin_pounce_editor_show(account, buddy->name, NULL);
}
static void
gf_action_context_log_buddy_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleAccount *account = NULL;
const gchar *target = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
account = gf_event_info_get_account(info);
g_return_if_fail(account);
target = gf_event_info_get_target(info);
g_return_if_fail(target);
pidgin_log_show(PURPLE_LOG_IM, target, account);
}
static void
gf_action_context_log_chat_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleAccount *account = NULL;
PurpleConversation *conv = NULL;
const gchar *name = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
account = gf_event_info_get_account(info);
g_return_if_fail(account);
conv = gf_event_info_get_conversation(info);
g_return_if_fail(conv);
name = purple_conversation_get_name(conv);
pidgin_log_show(PURPLE_LOG_CHAT, name, account);
}
static void
gf_action_context_alias_buddy_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleBuddy *buddy = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
buddy = gf_event_info_get_buddy(info);
g_return_if_fail(buddy);
pidgin_dialogs_alias_buddy(buddy);
}
static void
gf_action_context_alias_chat_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleAccount *account = NULL;
PurpleChat *chat = NULL;
PurpleConversation *conv = NULL;
const gchar *name = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
account = gf_event_info_get_account(info);
g_return_if_fail(account);
conv = gf_event_info_get_conversation(info);
g_return_if_fail(conv);
name = purple_conversation_get_name(conv);
chat = purple_blist_find_chat(account, name);
g_return_if_fail(chat);
pidgin_dialogs_alias_chat(chat);
}
static void
gf_action_context_add_buddy_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleAccount *account = NULL;
const gchar *target = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
account = gf_event_info_get_account(info);
g_return_if_fail(account);
target = gf_event_info_get_target(info);
g_return_if_fail(target);
purple_blist_request_add_buddy(account, target, NULL, NULL);
}
static void
gf_action_context_add_chat_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleAccount *account = NULL;
PurpleConversation *conv = NULL;
const gchar *name = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
account = gf_event_info_get_account(info);
g_return_if_fail(account);
conv = gf_event_info_get_conversation(info);
g_return_if_fail(conv);
name = purple_conversation_get_name(conv);
purple_blist_request_add_chat(account, NULL, NULL, name);
}
static void
gf_action_context_remove_buddy_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleBuddy *buddy = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
buddy = gf_event_info_get_buddy(info);
g_return_if_fail(buddy);
pidgin_dialogs_remove_buddy(buddy);
}
static void
gf_action_context_remove_chat_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleAccount *account = NULL;
PurpleChat *chat = NULL;
PurpleConversation *conv = NULL;
const gchar *name = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
account = gf_event_info_get_account(info);
g_return_if_fail(account);
conv = gf_event_info_get_conversation(info);
g_return_if_fail(conv);
name = purple_conversation_get_name(conv);
chat = purple_blist_find_chat(account, name);
g_return_if_fail(chat);
pidgin_dialogs_remove_chat(chat);
}
static void
gf_action_context_join_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleAccount *account = NULL;
const GHashTable *components = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
account = gf_event_info_get_account(info);
g_return_if_fail(account);
components = gf_event_info_get_components(info);
g_return_if_fail(components);
serv_join_chat(account->gc, (GHashTable *)components);
}
static void
gf_action_context_autojoin_cb(GtkWidget *menuitem, GfDisplay *display) {
GfEventInfo *info = NULL;
PurpleAccount *account = NULL;
PurpleChat *chat = NULL;
PurpleConversation *conv = NULL;
const gchar *name = NULL;
info = gf_display_get_event_info(display);
g_return_if_fail(info);
account = gf_event_info_get_account(info);
g_return_if_fail(account);
conv = gf_event_info_get_conversation(info);
g_return_if_fail(conv);
name = purple_conversation_get_name(conv);
chat = purple_blist_find_chat(account, name);
g_return_if_fail(chat);
purple_blist_node_set_bool((PurpleBlistNode *)chat, "gtk-autojoin",
gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)));
}
/* This function has come from hell.. I summoned it last time I sent a wicked
* soul to it's eternal resting place.
*/
void
gf_action_execute_context(GfDisplay *display, GdkEventButton *gdk_event) {
GfEventInfo *info = NULL;
PurpleAccount *account = NULL;
PurpleBuddy *buddy = NULL;
PurpleChat *chat = NULL;
PurpleConversation *conv = NULL;
PurpleConversationType type = 0;
PurplePlugin *prpl = NULL;
PurplePluginProtocolInfo *prpl_info = NULL;
GtkWidget *menu;
const gchar *target = NULL, *name = NULL;
gboolean chat_sep_added = FALSE;
guint timeout_id;
g_return_if_fail(display);
/* grab the stuff we need from the display and event info */
info = gf_display_get_event_info(display);
g_return_if_fail(info);
/* get the pidgin stuff we need */
account = gf_event_info_get_account(info);
g_return_if_fail(account);
/* we're going to show the menu as long as the timeout is removed.
* We need to remove it otherwise the display get's destroyed when
* the menu is shown, or it'll leak if we don't clean it up later.
*/
timeout_id = gf_event_info_get_timeout_id(info);
g_return_if_fail(g_source_remove(timeout_id));
buddy = gf_event_info_get_buddy(info);
conv = gf_event_info_get_conversation(info);
target = gf_event_info_get_target(info);
if(conv) {
type = purple_conversation_get_type(conv);
name = purple_conversation_get_name(conv);
}
if(conv)
chat = purple_blist_find_chat(account, name);
/* find the prpl and it's info */
prpl = purple_find_prpl(purple_account_get_protocol_id(account));
if(prpl)
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
/* create the menu */
menu = gtk_menu_new();
g_signal_connect(G_OBJECT(menu), "hide",
G_CALLBACK(gf_action_context_hide_cb), display);
gtk_widget_show(menu);
if(buddy || target) {
/* add get info if the prpl supports it */
if(prpl_info && prpl_info->get_info) {
pidgin_new_item_from_stock(menu, _("Get Info"), PIDGIN_STOCK_DIALOG_INFO,
G_CALLBACK(gf_action_context_info_cb),
display, 0, 0, NULL);
}
/* we can im anyone.. so.. */
pidgin_new_item_from_stock(menu, _("IM"), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW,
G_CALLBACK(gf_action_context_im_cb),
display, 0, 0, NULL);
}
/* It's pointless to add a buddy pounce for someone that isn't a buddy...
* Conversations currently allow this.. but that's because I haven't
* written a patch yet..
*/
if(buddy) {
pidgin_new_item_from_stock(menu, _("Add Buddy Pounce"), NULL,
G_CALLBACK(gf_action_context_pounce_cb),
display, 0, 0, NULL);
}
if(!buddy && target)
buddy = purple_find_buddy(account, target);
if(buddy) {
pidgin_new_item_from_stock(menu, _("View IM log"), NULL,
G_CALLBACK(gf_action_context_log_buddy_cb),
display, 0, 0, NULL);
/* add the protocol menu */
pidgin_append_blist_node_proto_menu(menu, account->gc,
(PurpleBlistNode *)buddy);
/* and now the extended menu */
pidgin_append_blist_node_extended_menu(menu, (PurpleBlistNode*)buddy);
/* separate it! */
pidgin_separator(menu);
}
/* we can't alias a non buddy, and we can't remove them either.. But if
* they're not a buddy we can add them!
*/
if(buddy) {
/* add the alias and remove button */
pidgin_new_item_from_stock(menu, _("Alias Buddy"), PIDGIN_STOCK_ALIAS,
G_CALLBACK(gf_action_context_alias_buddy_cb),
display, 0, 0, NULL);
pidgin_new_item_from_stock(menu, _("Remove Buddy"), GTK_STOCK_REMOVE,
G_CALLBACK(gf_action_context_remove_buddy_cb),
display, 0, 0, NULL);
} else if(target) {
pidgin_new_item_from_stock(menu, _("Add Buddy"), GTK_STOCK_ADD,
G_CALLBACK(gf_action_context_add_buddy_cb),
display, 0, 0, NULL);
}
/* Add a separtor if we have a buddy or target, and we have a chat */
if((buddy || target) && chat) {
pidgin_separator(menu);
chat_sep_added = TRUE;
}
if(chat) {
PurpleBlistNode *node = (PurpleBlistNode *)chat;
gboolean checked;
checked = (purple_blist_node_get_bool(node, "gtk-autojoin") ||
(purple_blist_node_get_string(node, "gtk-autojoin") != NULL));
/* add the join item, possibly redundant.. */
pidgin_new_item_from_stock(menu, _("Join"), PIDGIN_STOCK_CHAT,
G_CALLBACK(gf_action_context_join_cb),
display, 0, 0, NULL);
pidgin_new_check_item(menu, _("Auto-join"),
G_CALLBACK(gf_action_context_autojoin_cb),
display, checked);
}
/* if we have a conversation and it's a chat, we show the view log for it.
* if we don't do this check we end up with two menu items to view the
* same log..
*/
if(conv && type == PURPLE_CONV_TYPE_CHAT) {
if(!chat_sep_added) {
pidgin_separator(menu);
chat_sep_added = TRUE;
}
pidgin_new_item_from_stock(menu, _("View Chat Log"), NULL,
G_CALLBACK(gf_action_context_log_chat_cb),
display, 0, 0, NULL);
}
if(chat) {
/* add the proto menu for the chat */
pidgin_append_blist_node_proto_menu(menu, account->gc,
(PurpleBlistNode *)chat);
/* add the extended menu for the chat */
pidgin_append_blist_node_extended_menu(menu, (PurpleBlistNode *)chat);
/* add the alias and remove buttons */
pidgin_new_item_from_stock(menu, _("Alias Chat"), PIDGIN_STOCK_ALIAS,
G_CALLBACK(gf_action_context_alias_chat_cb),
display, 0, 0, NULL);
pidgin_new_item_from_stock(menu, _("Remove Chat"), GTK_STOCK_REMOVE,
G_CALLBACK(gf_action_context_remove_chat_cb),
display, 0, 0, NULL);
}
/* add the add chat item if it's not on our blist.. */
if(!chat && conv && type == PURPLE_CONV_TYPE_CHAT) {
pidgin_new_item_from_stock(menu, _("Add Chat"), GTK_STOCK_ADD,
G_CALLBACK(gf_action_context_add_chat_cb),
display, 0, 0, NULL);
}
/* show it already */
gtk_widget_show_all(menu);
gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
(GtkMenuPositionFunc)gf_action_context_position, display,
gdk_event->button, gdk_event->time);
}