pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Change the default irc server to libera.chat
release-2.x.y
2021-05-27, Gary Kramlich
c00b451a1dd8
Change the default irc server to libera.chat
This seems to make the most sense for users right now as many many channels have migrated away from freenode with many of them moving to libera.
Testing Done:
Compile only.
Reviewed at https://reviews.imfreedom.org/r/675/
/**
* @file gtkutils.c GTK+ utility functions
* @ingroup pidgin
*/
/* pidgin
*
* Pidgin is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* 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
*/
#define _PIDGIN_GTKUTILS_C_
#include
"internal.h"
#include
"pidgin.h"
#ifdef _WIN32
# ifdef small
# undef small
# endif
#endif
/*_WIN32*/
#ifdef USE_GTKSPELL
#
include
<gtkspell/gtkspell.h>
# ifdef _WIN32
#
include
"wspell.h"
# endif
#endif
#include
<gdk/gdkkeysyms.h>
#include
"conversation.h"
#include
"debug.h"
#include
"desktopitem.h"
#include
"imgstore.h"
#include
"notify.h"
#include
"prefs.h"
#include
"prpl.h"
#include
"request.h"
#include
"signals.h"
#include
"sound.h"
#include
"util.h"
#include
"gtkaccount.h"
#include
"gtkprefs.h"
#include
"gtkconv.h"
#include
"gtkdialogs.h"
#include
"gtkimhtml.h"
#include
"gtkimhtmltoolbar.h"
#include
"pidginstock.h"
#include
"gtkthemes.h"
#include
"gtkutils.h"
#include
"pidgin/minidialog.h"
typedef
struct
{
GtkWidget
*
menu
;
gint
default_item
;
}
AopMenu
;
static
guint
accels_save_timer
=
0
;
static
GSList
*
registered_url_handlers
=
NULL
;
static
gboolean
url_clicked_idle_cb
(
gpointer
data
)
{
purple_notify_uri
(
NULL
,
data
);
g_free
(
data
);
return
FALSE
;
}
static
gboolean
url_clicked_cb
(
GtkIMHtml
*
unused
,
GtkIMHtmlLink
*
link
)
{
const
char
*
uri
=
gtk_imhtml_link_get_url
(
link
);
g_idle_add
(
url_clicked_idle_cb
,
g_strdup
(
uri
));
return
TRUE
;
}
static
GtkIMHtmlFuncs
gtkimhtml_cbs
=
{
(
GtkIMHtmlGetImageFunc
)
purple_imgstore_find_by_id
,
(
GtkIMHtmlGetImageDataFunc
)
purple_imgstore_get_data
,
(
GtkIMHtmlGetImageSizeFunc
)
purple_imgstore_get_size
,
(
GtkIMHtmlGetImageFilenameFunc
)
purple_imgstore_get_filename
,
purple_imgstore_ref_by_id
,
purple_imgstore_unref_by_id
,
};
void
pidgin_setup_imhtml
(
GtkWidget
*
imhtml
)
{
g_return_if_fail
(
imhtml
!=
NULL
);
g_return_if_fail
(
GTK_IS_IMHTML
(
imhtml
));
pidgin_themes_smiley_themeize
(
imhtml
);
gtk_imhtml_set_funcs
(
GTK_IMHTML
(
imhtml
),
&
gtkimhtml_cbs
);
#ifdef _WIN32
if
(
!
purple_prefs_get_bool
(
PIDGIN_PREFS_ROOT
"/conversations/use_theme_font"
))
{
PangoFontDescription
*
desc
;
const
char
*
font
=
purple_prefs_get_string
(
PIDGIN_PREFS_ROOT
"/conversations/custom_font"
);
desc
=
pango_font_description_from_string
(
font
);
if
(
desc
)
{
gtk_widget_modify_font
(
imhtml
,
desc
);
pango_font_description_free
(
desc
);
}
}
#endif
}
static
void
pidgin_window_init
(
GtkWindow
*
wnd
,
const
char
*
title
,
guint
border_width
,
const
char
*
role
,
gboolean
resizable
)
{
if
(
title
)
gtk_window_set_title
(
wnd
,
title
);
#ifdef _WIN32
else
gtk_window_set_title
(
wnd
,
PIDGIN_ALERT_TITLE
);
#endif
gtk_container_set_border_width
(
GTK_CONTAINER
(
wnd
),
border_width
);
if
(
role
)
gtk_window_set_role
(
wnd
,
role
);
gtk_window_set_resizable
(
wnd
,
resizable
);
}
GtkWidget
*
pidgin_create_window
(
const
char
*
title
,
guint
border_width
,
const
char
*
role
,
gboolean
resizable
)
{
GtkWindow
*
wnd
=
NULL
;
wnd
=
GTK_WINDOW
(
gtk_window_new
(
GTK_WINDOW_TOPLEVEL
));
pidgin_window_init
(
wnd
,
title
,
border_width
,
role
,
resizable
);
return
GTK_WIDGET
(
wnd
);
}
GtkWidget
*
pidgin_create_small_button
(
GtkWidget
*
image
)
{
GtkWidget
*
button
;
button
=
gtk_button_new
();
gtk_button_set_relief
(
GTK_BUTTON
(
button
),
GTK_RELIEF_NONE
);
/* don't allow focus on the close button */
gtk_button_set_focus_on_click
(
GTK_BUTTON
(
button
),
FALSE
);
/* set style to make it as small as possible */
gtk_widget_set_name
(
button
,
"pidgin-small-close-button"
);
gtk_widget_show
(
image
);
gtk_container_add
(
GTK_CONTAINER
(
button
),
image
);
return
button
;
}
GtkWidget
*
pidgin_create_dialog
(
const
char
*
title
,
guint
border_width
,
const
char
*
role
,
gboolean
resizable
)
{
GtkWindow
*
wnd
=
NULL
;
wnd
=
GTK_WINDOW
(
gtk_dialog_new
());
pidgin_window_init
(
wnd
,
title
,
border_width
,
role
,
resizable
);
g_object_set
(
G_OBJECT
(
wnd
),
"has-separator"
,
FALSE
,
NULL
);
return
GTK_WIDGET
(
wnd
);
}
GtkWidget
*
pidgin_dialog_get_vbox_with_properties
(
GtkDialog
*
dialog
,
gboolean
homogeneous
,
gint
spacing
)
{
GtkBox
*
vbox
=
GTK_BOX
(
GTK_DIALOG
(
dialog
)
->
vbox
);
gtk_box_set_homogeneous
(
vbox
,
homogeneous
);
gtk_box_set_spacing
(
vbox
,
spacing
);
return
GTK_WIDGET
(
vbox
);
}
GtkWidget
*
pidgin_dialog_get_vbox
(
GtkDialog
*
dialog
)
{
return
GTK_DIALOG
(
dialog
)
->
vbox
;
}
GtkWidget
*
pidgin_dialog_get_action_area
(
GtkDialog
*
dialog
)
{
return
GTK_DIALOG
(
dialog
)
->
action_area
;
}
GtkWidget
*
pidgin_dialog_add_button
(
GtkDialog
*
dialog
,
const
char
*
label
,
GCallback
callback
,
gpointer
callbackdata
)
{
GtkWidget
*
button
=
gtk_button_new_from_stock
(
label
);
GtkWidget
*
bbox
=
pidgin_dialog_get_action_area
(
dialog
);
gtk_box_pack_start
(
GTK_BOX
(
bbox
),
button
,
FALSE
,
FALSE
,
0
);
if
(
callback
)
g_signal_connect
(
G_OBJECT
(
button
),
"clicked"
,
callback
,
callbackdata
);
gtk_widget_show
(
button
);
return
button
;
}
GtkWidget
*
pidgin_create_imhtml
(
gboolean
editable
,
GtkWidget
**
imhtml_ret
,
GtkWidget
**
toolbar_ret
,
GtkWidget
**
sw_ret
)
{
GtkWidget
*
frame
;
GtkWidget
*
imhtml
;
GtkWidget
*
sep
;
GtkWidget
*
sw
;
GtkWidget
*
toolbar
=
NULL
;
GtkWidget
*
vbox
;
frame
=
gtk_frame_new
(
NULL
);
gtk_frame_set_shadow_type
(
GTK_FRAME
(
frame
),
GTK_SHADOW_IN
);
vbox
=
gtk_vbox_new
(
FALSE
,
0
);
gtk_container_add
(
GTK_CONTAINER
(
frame
),
vbox
);
gtk_widget_show
(
vbox
);
if
(
editable
)
{
toolbar
=
gtk_imhtmltoolbar_new
();
gtk_box_pack_start
(
GTK_BOX
(
vbox
),
toolbar
,
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
toolbar
);
sep
=
gtk_hseparator_new
();
gtk_box_pack_start
(
GTK_BOX
(
vbox
),
sep
,
FALSE
,
FALSE
,
0
);
g_signal_connect_swapped
(
G_OBJECT
(
toolbar
),
"show"
,
G_CALLBACK
(
gtk_widget_show
),
sep
);
g_signal_connect_swapped
(
G_OBJECT
(
toolbar
),
"hide"
,
G_CALLBACK
(
gtk_widget_hide
),
sep
);
gtk_widget_show
(
sep
);
}
imhtml
=
gtk_imhtml_new
(
NULL
,
NULL
);
gtk_imhtml_set_editable
(
GTK_IMHTML
(
imhtml
),
editable
);
gtk_imhtml_set_format_functions
(
GTK_IMHTML
(
imhtml
),
GTK_IMHTML_ALL
^
GTK_IMHTML_IMAGE
);
gtk_text_view_set_wrap_mode
(
GTK_TEXT_VIEW
(
imhtml
),
GTK_WRAP_WORD_CHAR
);
#ifdef USE_GTKSPELL
if
(
editable
&&
purple_prefs_get_bool
(
PIDGIN_PREFS_ROOT
"/conversations/spellcheck"
))
pidgin_setup_gtkspell
(
GTK_TEXT_VIEW
(
imhtml
));
#endif
gtk_widget_show
(
imhtml
);
if
(
editable
)
{
gtk_imhtmltoolbar_attach
(
GTK_IMHTMLTOOLBAR
(
toolbar
),
imhtml
);
gtk_imhtmltoolbar_associate_smileys
(
GTK_IMHTMLTOOLBAR
(
toolbar
),
"default"
);
}
pidgin_setup_imhtml
(
imhtml
);
sw
=
pidgin_make_scrollable
(
imhtml
,
GTK_POLICY_AUTOMATIC
,
GTK_POLICY_AUTOMATIC
,
GTK_SHADOW_NONE
,
-1
,
-1
);
gtk_box_pack_start
(
GTK_BOX
(
vbox
),
sw
,
TRUE
,
TRUE
,
0
);
if
(
imhtml_ret
!=
NULL
)
*
imhtml_ret
=
imhtml
;
if
(
editable
&&
(
toolbar_ret
!=
NULL
))
*
toolbar_ret
=
toolbar
;
if
(
sw_ret
!=
NULL
)
*
sw_ret
=
sw
;
return
frame
;
}
void
pidgin_set_sensitive_if_input
(
GtkWidget
*
entry
,
GtkWidget
*
dialog
)
{
const
char
*
text
=
gtk_entry_get_text
(
GTK_ENTRY
(
entry
));
gtk_dialog_set_response_sensitive
(
GTK_DIALOG
(
dialog
),
GTK_RESPONSE_OK
,
(
*
text
!=
'\0'
));
}
void
pidgin_toggle_sensitive
(
GtkWidget
*
widget
,
GtkWidget
*
to_toggle
)
{
gboolean
sensitivity
;
if
(
to_toggle
==
NULL
)
return
;
sensitivity
=
GTK_WIDGET_IS_SENSITIVE
(
to_toggle
);
gtk_widget_set_sensitive
(
to_toggle
,
!
sensitivity
);
}
void
pidgin_toggle_sensitive_array
(
GtkWidget
*
w
,
GPtrArray
*
data
)
{
gboolean
sensitivity
;
gpointer
element
;
guint
i
;
for
(
i
=
0
;
i
<
data
->
len
;
i
++
)
{
element
=
g_ptr_array_index
(
data
,
i
);
if
(
element
==
NULL
)
continue
;
sensitivity
=
GTK_WIDGET_IS_SENSITIVE
(
element
);
gtk_widget_set_sensitive
(
element
,
!
sensitivity
);
}
}
void
pidgin_toggle_showhide
(
GtkWidget
*
widget
,
GtkWidget
*
to_toggle
)
{
if
(
to_toggle
==
NULL
)
return
;
if
(
GTK_WIDGET_VISIBLE
(
to_toggle
))
gtk_widget_hide
(
to_toggle
);
else
gtk_widget_show
(
to_toggle
);
}
GtkWidget
*
pidgin_separator
(
GtkWidget
*
menu
)
{
GtkWidget
*
menuitem
;
menuitem
=
gtk_separator_menu_item_new
();
gtk_widget_show
(
menuitem
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
menuitem
);
return
menuitem
;
}
GtkWidget
*
pidgin_new_item
(
GtkWidget
*
menu
,
const
char
*
str
)
{
GtkWidget
*
menuitem
;
GtkWidget
*
label
;
menuitem
=
gtk_menu_item_new
();
if
(
menu
)
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
menuitem
);
gtk_widget_show
(
menuitem
);
label
=
gtk_label_new
(
str
);
gtk_misc_set_alignment
(
GTK_MISC
(
label
),
0
,
0.5
);
gtk_label_set_pattern
(
GTK_LABEL
(
label
),
"_"
);
gtk_container_add
(
GTK_CONTAINER
(
menuitem
),
label
);
gtk_widget_show
(
label
);
/* FIXME: Go back and fix this
gtk_widget_add_accelerator(menuitem, "activate", accel, str[0],
GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
*/
pidgin_set_accessible_label
(
menuitem
,
label
);
return
menuitem
;
}
GtkWidget
*
pidgin_new_check_item
(
GtkWidget
*
menu
,
const
char
*
str
,
GCallback
cb
,
gpointer
data
,
gboolean
checked
)
{
GtkWidget
*
menuitem
;
menuitem
=
gtk_check_menu_item_new_with_mnemonic
(
str
);
if
(
menu
)
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
menuitem
);
gtk_check_menu_item_set_active
(
GTK_CHECK_MENU_ITEM
(
menuitem
),
checked
);
if
(
cb
)
g_signal_connect
(
G_OBJECT
(
menuitem
),
"activate"
,
cb
,
data
);
gtk_widget_show_all
(
menuitem
);
return
menuitem
;
}
GtkWidget
*
pidgin_pixbuf_toolbar_button_from_stock
(
const
char
*
icon
)
{
GtkWidget
*
button
,
*
image
,
*
bbox
;
button
=
gtk_toggle_button_new
();
gtk_button_set_relief
(
GTK_BUTTON
(
button
),
GTK_RELIEF_NONE
);
bbox
=
gtk_vbox_new
(
FALSE
,
0
);
gtk_container_add
(
GTK_CONTAINER
(
button
),
bbox
);
image
=
gtk_image_new_from_stock
(
icon
,
gtk_icon_size_from_name
(
PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
gtk_box_pack_start
(
GTK_BOX
(
bbox
),
image
,
FALSE
,
FALSE
,
0
);
gtk_widget_show_all
(
bbox
);
return
button
;
}
GtkWidget
*
pidgin_pixbuf_button_from_stock
(
const
char
*
text
,
const
char
*
icon
,
PidginButtonOrientation
style
)
{
GtkWidget
*
button
,
*
image
,
*
label
,
*
bbox
,
*
ibox
,
*
lbox
=
NULL
;
button
=
gtk_button_new
();
if
(
style
==
PIDGIN_BUTTON_HORIZONTAL
)
{
bbox
=
gtk_hbox_new
(
FALSE
,
0
);
ibox
=
gtk_hbox_new
(
FALSE
,
0
);
if
(
text
)
lbox
=
gtk_hbox_new
(
FALSE
,
0
);
}
else
{
bbox
=
gtk_vbox_new
(
FALSE
,
0
);
ibox
=
gtk_vbox_new
(
FALSE
,
0
);
if
(
text
)
lbox
=
gtk_vbox_new
(
FALSE
,
0
);
}
gtk_container_add
(
GTK_CONTAINER
(
button
),
bbox
);
if
(
icon
)
{
gtk_box_pack_start
(
GTK_BOX
(
bbox
),
ibox
,
TRUE
,
TRUE
,
0
);
image
=
gtk_image_new_from_stock
(
icon
,
GTK_ICON_SIZE_BUTTON
);
gtk_box_pack_end
(
GTK_BOX
(
ibox
),
image
,
FALSE
,
TRUE
,
0
);
}
if
(
text
)
{
gtk_box_pack_start
(
GTK_BOX
(
bbox
),
lbox
,
TRUE
,
TRUE
,
0
);
label
=
gtk_label_new
(
NULL
);
gtk_label_set_text_with_mnemonic
(
GTK_LABEL
(
label
),
text
);
gtk_label_set_mnemonic_widget
(
GTK_LABEL
(
label
),
button
);
gtk_box_pack_start
(
GTK_BOX
(
lbox
),
label
,
FALSE
,
TRUE
,
0
);
pidgin_set_accessible_label
(
button
,
label
);
}
gtk_widget_show_all
(
bbox
);
return
button
;
}
GtkWidget
*
pidgin_new_item_from_stock
(
GtkWidget
*
menu
,
const
char
*
str
,
const
char
*
icon
,
GCallback
cb
,
gpointer
data
,
guint
accel_key
,
guint
accel_mods
,
char
*
mod
)
{
GtkWidget
*
menuitem
;
/*
GtkWidget *hbox;
GtkWidget *label;
*/
GtkWidget
*
image
;
if
(
icon
==
NULL
)
menuitem
=
gtk_menu_item_new_with_mnemonic
(
str
);
else
menuitem
=
gtk_image_menu_item_new_with_mnemonic
(
str
);
if
(
menu
)
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
menuitem
);
if
(
cb
)
g_signal_connect
(
G_OBJECT
(
menuitem
),
"activate"
,
cb
,
data
);
if
(
icon
!=
NULL
)
{
image
=
gtk_image_new_from_stock
(
icon
,
gtk_icon_size_from_name
(
PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
menuitem
),
image
);
}
/* FIXME: this isn't right
if (mod) {
label = gtk_label_new(mod);
gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2);
gtk_widget_show(label);
}
*/
/*
if (accel_key) {
gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key,
accel_mods, GTK_ACCEL_LOCKED);
}
*/
gtk_widget_show_all
(
menuitem
);
return
menuitem
;
}
GtkWidget
*
pidgin_make_frame
(
GtkWidget
*
parent
,
const
char
*
title
)
{
GtkWidget
*
vbox
,
*
label
,
*
hbox
;
char
*
labeltitle
;
vbox
=
gtk_vbox_new
(
FALSE
,
PIDGIN_HIG_BOX_SPACE
);
gtk_box_pack_start
(
GTK_BOX
(
parent
),
vbox
,
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
vbox
);
label
=
gtk_label_new
(
NULL
);
labeltitle
=
g_strdup_printf
(
"<span weight=
\"
bold
\"
>%s</span>"
,
title
);
gtk_label_set_markup
(
GTK_LABEL
(
label
),
labeltitle
);
g_free
(
labeltitle
);
gtk_misc_set_alignment
(
GTK_MISC
(
label
),
0
,
0
);
gtk_box_pack_start
(
GTK_BOX
(
vbox
),
label
,
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
label
);
pidgin_set_accessible_label
(
vbox
,
label
);
hbox
=
gtk_hbox_new
(
FALSE
,
PIDGIN_HIG_BOX_SPACE
);
gtk_box_pack_start
(
GTK_BOX
(
vbox
),
hbox
,
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
hbox
);
label
=
gtk_label_new
(
" "
);
gtk_box_pack_start
(
GTK_BOX
(
hbox
),
label
,
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
label
);
vbox
=
gtk_vbox_new
(
FALSE
,
PIDGIN_HIG_BOX_SPACE
);
gtk_box_pack_start
(
GTK_BOX
(
hbox
),
vbox
,
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
vbox
);
return
vbox
;
}
static
gpointer
aop_option_menu_get_selected
(
GtkWidget
*
optmenu
,
GtkWidget
**
p_item
)
{
GtkWidget
*
menu
=
gtk_option_menu_get_menu
(
GTK_OPTION_MENU
(
optmenu
));
GtkWidget
*
item
=
gtk_menu_get_active
(
GTK_MENU
(
menu
));
if
(
p_item
)
(
*
p_item
)
=
item
;
return
item
?
g_object_get_data
(
G_OBJECT
(
item
),
"aop_per_item_data"
)
:
NULL
;
}
static
void
aop_menu_cb
(
GtkWidget
*
optmenu
,
GCallback
cb
)
{
GtkWidget
*
item
;
gpointer
per_item_data
;
per_item_data
=
aop_option_menu_get_selected
(
optmenu
,
&
item
);
if
(
cb
!=
NULL
)
{
((
void
(
*
)(
GtkWidget
*
,
gpointer
,
gpointer
))
cb
)(
item
,
per_item_data
,
g_object_get_data
(
G_OBJECT
(
optmenu
),
"user_data"
));
}
}
static
GtkWidget
*
aop_menu_item_new
(
GtkSizeGroup
*
sg
,
GdkPixbuf
*
pixbuf
,
const
char
*
lbl
,
gpointer
per_item_data
,
const
char
*
data
)
{
GtkWidget
*
item
;
GtkWidget
*
hbox
;
GtkWidget
*
image
;
GtkWidget
*
label
;
item
=
gtk_menu_item_new
();
gtk_widget_show
(
item
);
hbox
=
gtk_hbox_new
(
FALSE
,
4
);
gtk_widget_show
(
hbox
);
/* Create the image */
if
(
pixbuf
==
NULL
)
image
=
gtk_image_new
();
else
image
=
gtk_image_new_from_pixbuf
(
pixbuf
);
gtk_widget_show
(
image
);
if
(
sg
)
gtk_size_group_add_widget
(
sg
,
image
);
/* Create the label */
label
=
gtk_label_new
(
lbl
);
gtk_widget_show
(
label
);
gtk_label_set_justify
(
GTK_LABEL
(
label
),
GTK_JUSTIFY_LEFT
);
gtk_misc_set_alignment
(
GTK_MISC
(
label
),
0.0
,
0.5
);
gtk_container_add
(
GTK_CONTAINER
(
item
),
hbox
);
gtk_box_pack_start
(
GTK_BOX
(
hbox
),
image
,
FALSE
,
FALSE
,
0
);
gtk_box_pack_start
(
GTK_BOX
(
hbox
),
label
,
TRUE
,
TRUE
,
0
);
g_object_set_data
(
G_OBJECT
(
item
),
data
,
per_item_data
);
g_object_set_data
(
G_OBJECT
(
item
),
"aop_per_item_data"
,
per_item_data
);
pidgin_set_accessible_label
(
item
,
label
);
return
item
;
}
static
GdkPixbuf
*
pidgin_create_prpl_icon_from_prpl
(
PurplePlugin
*
prpl
,
PidginPrplIconSize
size
,
PurpleAccount
*
account
)
{
PurplePluginProtocolInfo
*
prpl_info
;
const
char
*
protoname
=
NULL
;
char
*
tmp
;
char
*
filename
=
NULL
;
GdkPixbuf
*
pixbuf
;
prpl_info
=
PURPLE_PLUGIN_PROTOCOL_INFO
(
prpl
);
if
(
prpl_info
->
list_icon
==
NULL
)
return
NULL
;
protoname
=
prpl_info
->
list_icon
(
account
,
NULL
);
if
(
protoname
==
NULL
)
return
NULL
;
/*
* Status icons will be themeable too, and then it will look up
* protoname from the theme
*/
tmp
=
g_strconcat
(
protoname
,
".png"
,
NULL
);
filename
=
g_build_filename
(
DATADIR
,
"pixmaps"
,
"pidgin"
,
"protocols"
,
size
==
PIDGIN_PRPL_ICON_SMALL
?
"16"
:
size
==
PIDGIN_PRPL_ICON_MEDIUM
?
"22"
:
"48"
,
tmp
,
NULL
);
g_free
(
tmp
);
pixbuf
=
pidgin_pixbuf_new_from_file
(
filename
);
g_free
(
filename
);
return
pixbuf
;
}
static
GtkWidget
*
aop_option_menu_new
(
AopMenu
*
aop_menu
,
GCallback
cb
,
gpointer
user_data
)
{
GtkWidget
*
optmenu
;
optmenu
=
gtk_option_menu_new
();
gtk_widget_show
(
optmenu
);
gtk_option_menu_set_menu
(
GTK_OPTION_MENU
(
optmenu
),
aop_menu
->
menu
);
if
(
aop_menu
->
default_item
!=
-1
)
gtk_option_menu_set_history
(
GTK_OPTION_MENU
(
optmenu
),
aop_menu
->
default_item
);
g_object_set_data_full
(
G_OBJECT
(
optmenu
),
"aop_menu"
,
aop_menu
,
(
GDestroyNotify
)
g_free
);
g_object_set_data
(
G_OBJECT
(
optmenu
),
"user_data"
,
user_data
);
g_signal_connect
(
G_OBJECT
(
optmenu
),
"changed"
,
G_CALLBACK
(
aop_menu_cb
),
cb
);
return
optmenu
;
}
static
void
aop_option_menu_replace_menu
(
GtkWidget
*
optmenu
,
AopMenu
*
new_aop_menu
)
{
if
(
gtk_option_menu_get_menu
(
GTK_OPTION_MENU
(
optmenu
)))
gtk_option_menu_remove_menu
(
GTK_OPTION_MENU
(
optmenu
));
gtk_option_menu_set_menu
(
GTK_OPTION_MENU
(
optmenu
),
new_aop_menu
->
menu
);
if
(
new_aop_menu
->
default_item
!=
-1
)
gtk_option_menu_set_history
(
GTK_OPTION_MENU
(
optmenu
),
new_aop_menu
->
default_item
);
g_object_set_data_full
(
G_OBJECT
(
optmenu
),
"aop_menu"
,
new_aop_menu
,
(
GDestroyNotify
)
g_free
);
}
static
void
aop_option_menu_select_by_data
(
GtkWidget
*
optmenu
,
gpointer
data
)
{
guint
idx
;
GList
*
llItr
=
NULL
;
for
(
idx
=
0
,
llItr
=
GTK_MENU_SHELL
(
gtk_option_menu_get_menu
(
GTK_OPTION_MENU
(
optmenu
)))
->
children
;
llItr
!=
NULL
;
llItr
=
llItr
->
next
,
idx
++
)
{
if
(
data
==
g_object_get_data
(
G_OBJECT
(
llItr
->
data
),
"aop_per_item_data"
))
{
gtk_option_menu_set_history
(
GTK_OPTION_MENU
(
optmenu
),
idx
);
break
;
}
}
}
static
AopMenu
*
create_protocols_menu
(
const
char
*
default_proto_id
)
{
AopMenu
*
aop_menu
=
NULL
;
PurplePlugin
*
plugin
;
GdkPixbuf
*
pixbuf
=
NULL
;
GtkSizeGroup
*
sg
;
GList
*
p
;
const
char
*
gtalk_name
=
NULL
;
int
i
;
aop_menu
=
g_malloc0
(
sizeof
(
AopMenu
));
aop_menu
->
default_item
=
-1
;
aop_menu
->
menu
=
gtk_menu_new
();
gtk_widget_show
(
aop_menu
->
menu
);
sg
=
gtk_size_group_new
(
GTK_SIZE_GROUP_HORIZONTAL
);
if
(
purple_find_prpl
(
"prpl-jabber"
))
{
gtalk_name
=
_
(
"Google Talk"
);
}
for
(
p
=
purple_plugins_get_protocols
(),
i
=
0
;
p
!=
NULL
;
p
=
p
->
next
,
i
++
)
{
plugin
=
(
PurplePlugin
*
)
p
->
data
;
if
(
gtalk_name
&&
strcmp
(
gtalk_name
,
plugin
->
info
->
name
)
<
0
)
{
char
*
filename
=
g_build_filename
(
DATADIR
,
"pixmaps"
,
"pidgin"
,
"protocols"
,
"16"
,
"google-talk.png"
,
NULL
);
GtkWidget
*
item
;
pixbuf
=
pidgin_pixbuf_new_from_file
(
filename
);
g_free
(
filename
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
aop_menu
->
menu
),
item
=
aop_menu_item_new
(
sg
,
pixbuf
,
gtalk_name
,
"prpl-jabber"
,
"protocol"
));
g_object_set_data
(
G_OBJECT
(
item
),
"fakegoogle"
,
GINT_TO_POINTER
(
1
));
if
(
pixbuf
)
g_object_unref
(
pixbuf
);
/* libpurple3 compatibility */
if
(
purple_strequal
(
default_proto_id
,
"prpl-gtalk"
))
aop_menu
->
default_item
=
i
;
gtalk_name
=
NULL
;
i
++
;
}
pixbuf
=
pidgin_create_prpl_icon_from_prpl
(
plugin
,
PIDGIN_PRPL_ICON_SMALL
,
NULL
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
aop_menu
->
menu
),
aop_menu_item_new
(
sg
,
pixbuf
,
plugin
->
info
->
name
,
plugin
->
info
->
id
,
"protocol"
));
if
(
pixbuf
)
g_object_unref
(
pixbuf
);
if
(
default_proto_id
!=
NULL
&&
purple_strequal
(
plugin
->
info
->
id
,
default_proto_id
))
aop_menu
->
default_item
=
i
;
}
g_object_unref
(
sg
);
return
aop_menu
;
}
GtkWidget
*
pidgin_protocol_option_menu_new
(
const
char
*
id
,
GCallback
cb
,
gpointer
user_data
)
{
return
aop_option_menu_new
(
create_protocols_menu
(
id
),
cb
,
user_data
);
}
const
char
*
pidgin_protocol_option_menu_get_selected
(
GtkWidget
*
optmenu
)
{
return
(
const
char
*
)
aop_option_menu_get_selected
(
optmenu
,
NULL
);
}
PurpleAccount
*
pidgin_account_option_menu_get_selected
(
GtkWidget
*
optmenu
)
{
return
(
PurpleAccount
*
)
aop_option_menu_get_selected
(
optmenu
,
NULL
);
}
static
AopMenu
*
create_account_menu
(
PurpleAccount
*
default_account
,
PurpleFilterAccountFunc
filter_func
,
gboolean
show_all
)
{
AopMenu
*
aop_menu
=
NULL
;
PurpleAccount
*
account
;
GdkPixbuf
*
pixbuf
=
NULL
;
GList
*
list
;
GList
*
p
;
GtkSizeGroup
*
sg
;
int
i
;
char
buf
[
256
];
if
(
show_all
)
list
=
purple_accounts_get_all
();
else
list
=
purple_connections_get_all
();
aop_menu
=
g_malloc0
(
sizeof
(
AopMenu
));
aop_menu
->
default_item
=
-1
;
aop_menu
->
menu
=
gtk_menu_new
();
gtk_widget_show
(
aop_menu
->
menu
);
sg
=
gtk_size_group_new
(
GTK_SIZE_GROUP_HORIZONTAL
);
for
(
p
=
list
,
i
=
0
;
p
!=
NULL
;
p
=
p
->
next
,
i
++
)
{
if
(
show_all
)
account
=
(
PurpleAccount
*
)
p
->
data
;
else
{
PurpleConnection
*
gc
=
(
PurpleConnection
*
)
p
->
data
;
account
=
purple_connection_get_account
(
gc
);
}
if
(
filter_func
&&
!
filter_func
(
account
))
{
i
--
;
continue
;
}
pixbuf
=
pidgin_create_prpl_icon
(
account
,
PIDGIN_PRPL_ICON_SMALL
);
if
(
pixbuf
)
{
if
(
purple_account_is_disconnected
(
account
)
&&
show_all
&&
purple_connections_get_all
())
gdk_pixbuf_saturate_and_pixelate
(
pixbuf
,
pixbuf
,
0.0
,
FALSE
);
}
if
(
purple_account_get_alias
(
account
))
{
g_snprintf
(
buf
,
sizeof
(
buf
),
"%s (%s) (%s)"
,
purple_account_get_username
(
account
),
purple_account_get_alias
(
account
),
purple_account_get_protocol_name
(
account
));
}
else
{
g_snprintf
(
buf
,
sizeof
(
buf
),
"%s (%s)"
,
purple_account_get_username
(
account
),
purple_account_get_protocol_name
(
account
));
}
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
aop_menu
->
menu
),
aop_menu_item_new
(
sg
,
pixbuf
,
buf
,
account
,
"account"
));
if
(
pixbuf
)
g_object_unref
(
pixbuf
);
if
(
default_account
&&
account
==
default_account
)
aop_menu
->
default_item
=
i
;
}
g_object_unref
(
sg
);
return
aop_menu
;
}
static
void
regenerate_account_menu
(
GtkWidget
*
optmenu
)
{
gboolean
show_all
;
PurpleAccount
*
account
;
PurpleFilterAccountFunc
filter_func
;
account
=
(
PurpleAccount
*
)
aop_option_menu_get_selected
(
optmenu
,
NULL
);
show_all
=
GPOINTER_TO_INT
(
g_object_get_data
(
G_OBJECT
(
optmenu
),
"show_all"
));
filter_func
=
g_object_get_data
(
G_OBJECT
(
optmenu
),
"filter_func"
);
aop_option_menu_replace_menu
(
optmenu
,
create_account_menu
(
account
,
filter_func
,
show_all
));
}
static
void
account_menu_sign_on_off_cb
(
PurpleConnection
*
gc
,
GtkWidget
*
optmenu
)
{
regenerate_account_menu
(
optmenu
);
}
static
void
account_menu_added_removed_cb
(
PurpleAccount
*
account
,
GtkWidget
*
optmenu
)
{
regenerate_account_menu
(
optmenu
);
}
static
gboolean
account_menu_destroyed_cb
(
GtkWidget
*
optmenu
,
GdkEvent
*
event
,
void
*
user_data
)
{
purple_signals_disconnect_by_handle
(
optmenu
);
return
FALSE
;
}
void
pidgin_account_option_menu_set_selected
(
GtkWidget
*
optmenu
,
PurpleAccount
*
account
)
{
aop_option_menu_select_by_data
(
optmenu
,
account
);
}
GtkWidget
*
pidgin_account_option_menu_new
(
PurpleAccount
*
default_account
,
gboolean
show_all
,
GCallback
cb
,
PurpleFilterAccountFunc
filter_func
,
gpointer
user_data
)
{
GtkWidget
*
optmenu
;
/* Create the option menu */
optmenu
=
aop_option_menu_new
(
create_account_menu
(
default_account
,
filter_func
,
show_all
),
cb
,
user_data
);
g_signal_connect
(
G_OBJECT
(
optmenu
),
"destroy"
,
G_CALLBACK
(
account_menu_destroyed_cb
),
NULL
);
/* Register the purple sign on/off event callbacks. */
purple_signal_connect
(
purple_connections_get_handle
(),
"signed-on"
,
optmenu
,
PURPLE_CALLBACK
(
account_menu_sign_on_off_cb
),
optmenu
);
purple_signal_connect
(
purple_connections_get_handle
(),
"signed-off"
,
optmenu
,
PURPLE_CALLBACK
(
account_menu_sign_on_off_cb
),
optmenu
);
purple_signal_connect
(
purple_accounts_get_handle
(),
"account-added"
,
optmenu
,
PURPLE_CALLBACK
(
account_menu_added_removed_cb
),
optmenu
);
purple_signal_connect
(
purple_accounts_get_handle
(),
"account-removed"
,
optmenu
,
PURPLE_CALLBACK
(
account_menu_added_removed_cb
),
optmenu
);
/* Set some data. */
g_object_set_data
(
G_OBJECT
(
optmenu
),
"user_data"
,
user_data
);
g_object_set_data
(
G_OBJECT
(
optmenu
),
"show_all"
,
GINT_TO_POINTER
(
show_all
));
g_object_set_data
(
G_OBJECT
(
optmenu
),
"filter_func"
,
filter_func
);
return
optmenu
;
}
gboolean
pidgin_check_if_dir
(
const
char
*
path
,
GtkFileSelection
*
filesel
)
{
char
*
dirname
=
NULL
;
if
(
g_file_test
(
path
,
G_FILE_TEST_IS_DIR
))
{
/* append a / if needed */
if
(
path
[
strlen
(
path
)
-
1
]
!=
G_DIR_SEPARATOR
)
{
dirname
=
g_strconcat
(
path
,
G_DIR_SEPARATOR_S
,
NULL
);
}
gtk_file_selection_set_filename
(
filesel
,
(
dirname
!=
NULL
)
?
dirname
:
path
);
g_free
(
dirname
);
return
TRUE
;
}
return
FALSE
;
}
void
pidgin_setup_gtkspell
(
GtkTextView
*
textview
)
{
#ifdef USE_GTKSPELL
GError
*
error
=
NULL
;
char
*
locale
=
NULL
;
g_return_if_fail
(
textview
!=
NULL
);
g_return_if_fail
(
GTK_IS_TEXT_VIEW
(
textview
));
if
(
gtkspell_new_attach
(
textview
,
locale
,
&
error
)
==
NULL
&&
error
)
{
purple_debug_warning
(
"gtkspell"
,
"Failed to setup GtkSpell: %s
\n
"
,
error
->
message
);
g_error_free
(
error
);
}
#endif
/* USE_GTKSPELL */
}
void
pidgin_save_accels_cb
(
GtkAccelGroup
*
accel_group
,
guint
arg1
,
GdkModifierType
arg2
,
GClosure
*
arg3
,
gpointer
data
)
{
purple_debug
(
PURPLE_DEBUG_MISC
,
"accels"
,
"accel changed, scheduling save.
\n
"
);
if
(
!
accels_save_timer
)
accels_save_timer
=
purple_timeout_add_seconds
(
5
,
pidgin_save_accels
,
NULL
);
}
gboolean
pidgin_save_accels
(
gpointer
data
)
{
char
*
filename
=
NULL
;
filename
=
g_build_filename
(
purple_user_dir
(),
G_DIR_SEPARATOR_S
,
"accels"
,
NULL
);
purple_debug
(
PURPLE_DEBUG_MISC
,
"accels"
,
"saving accels to %s
\n
"
,
filename
);
gtk_accel_map_save
(
filename
);
g_free
(
filename
);
accels_save_timer
=
0
;
return
FALSE
;
}
void
pidgin_load_accels
()
{
char
*
filename
=
NULL
;
filename
=
g_build_filename
(
purple_user_dir
(),
G_DIR_SEPARATOR_S
,
"accels"
,
NULL
);
gtk_accel_map_load
(
filename
);
g_free
(
filename
);
}
static
void
show_retrieveing_info
(
PurpleConnection
*
conn
,
const
char
*
name
)
{
PurpleNotifyUserInfo
*
info
=
purple_notify_user_info_new
();
purple_notify_user_info_add_pair
(
info
,
_
(
"Information"
),
_
(
"Retrieving..."
));
purple_notify_userinfo
(
conn
,
name
,
info
,
NULL
,
NULL
);
purple_notify_user_info_destroy
(
info
);
}
void
pidgin_retrieve_user_info
(
PurpleConnection
*
conn
,
const
char
*
name
)
{
show_retrieveing_info
(
conn
,
name
);
serv_get_info
(
conn
,
name
);
}
void
pidgin_retrieve_user_info_in_chat
(
PurpleConnection
*
conn
,
const
char
*
name
,
int
chat
)
{
char
*
who
=
NULL
;
PurplePluginProtocolInfo
*
prpl_info
=
NULL
;
if
(
chat
<
0
)
{
pidgin_retrieve_user_info
(
conn
,
name
);
return
;
}
prpl_info
=
PURPLE_PLUGIN_PROTOCOL_INFO
(
conn
->
prpl
);
if
(
prpl_info
!=
NULL
&&
prpl_info
->
get_cb_real_name
)
who
=
prpl_info
->
get_cb_real_name
(
conn
,
chat
,
name
);
if
(
prpl_info
==
NULL
||
prpl_info
->
get_cb_info
==
NULL
)
{
pidgin_retrieve_user_info
(
conn
,
who
?
who
:
name
);
g_free
(
who
);
return
;
}
show_retrieveing_info
(
conn
,
who
?
who
:
name
);
prpl_info
->
get_cb_info
(
conn
,
chat
,
name
);
g_free
(
who
);
}
gboolean
pidgin_parse_x_im_contact
(
const
char
*
msg
,
gboolean
all_accounts
,
PurpleAccount
**
ret_account
,
char
**
ret_protocol
,
char
**
ret_username
,
char
**
ret_alias
)
{
char
*
protocol
=
NULL
;
char
*
username
=
NULL
;
char
*
alias
=
NULL
;
char
*
str
;
char
*
s
;
gboolean
valid
;
g_return_val_if_fail
(
msg
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
ret_protocol
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
ret_username
!=
NULL
,
FALSE
);
s
=
str
=
g_strdup
(
msg
);
while
(
*
s
!=
'\r'
&&
*
s
!=
'\n'
&&
*
s
!=
'\0'
)
{
char
*
key
,
*
value
;
key
=
s
;
/* Grab the key */
while
(
*
s
!=
'\r'
&&
*
s
!=
'\n'
&&
*
s
!=
'\0'
&&
*
s
!=
' '
)
s
++
;
if
(
*
s
==
'\r'
)
s
++
;
if
(
*
s
==
'\n'
)
{
s
++
;
continue
;
}
if
(
*
s
!=
'\0'
)
*
s
++
=
'\0'
;
/* Clear past any whitespace */
while
(
*
s
!=
'\0'
&&
*
s
==
' '
)
s
++
;
/* Now let's grab until the end of the line. */
value
=
s
;
while
(
*
s
!=
'\r'
&&
*
s
!=
'\n'
&&
*
s
!=
'\0'
)
s
++
;
if
(
*
s
==
'\r'
)
*
s
++
=
'\0'
;
if
(
*
s
==
'\n'
)
*
s
++
=
'\0'
;
if
(
strchr
(
key
,
':'
)
!=
NULL
)
{
if
(
!
g_ascii_strcasecmp
(
key
,
"X-IM-Username:"
))
username
=
g_strdup
(
value
);
else
if
(
!
g_ascii_strcasecmp
(
key
,
"X-IM-Protocol:"
))
protocol
=
g_strdup
(
value
);
else
if
(
!
g_ascii_strcasecmp
(
key
,
"X-IM-Alias:"
))
alias
=
g_strdup
(
value
);
}
}
if
(
username
!=
NULL
&&
protocol
!=
NULL
)
{
valid
=
TRUE
;
*
ret_username
=
username
;
*
ret_protocol
=
protocol
;
if
(
ret_alias
!=
NULL
)
*
ret_alias
=
alias
;
/* Check for a compatible account. */
if
(
ret_account
!=
NULL
)
{
GList
*
list
;
PurpleAccount
*
account
=
NULL
;
GList
*
l
;
const
char
*
protoname
;
if
(
all_accounts
)
list
=
purple_accounts_get_all
();
else
list
=
purple_connections_get_all
();
for
(
l
=
list
;
l
!=
NULL
;
l
=
l
->
next
)
{
PurpleConnection
*
gc
;
PurplePluginProtocolInfo
*
prpl_info
=
NULL
;
PurplePlugin
*
plugin
;
if
(
all_accounts
)
{
account
=
(
PurpleAccount
*
)
l
->
data
;
plugin
=
purple_plugins_find_with_id
(
purple_account_get_protocol_id
(
account
));
if
(
plugin
==
NULL
)
{
account
=
NULL
;
continue
;
}
prpl_info
=
PURPLE_PLUGIN_PROTOCOL_INFO
(
plugin
);
}
else
{
gc
=
(
PurpleConnection
*
)
l
->
data
;
account
=
purple_connection_get_account
(
gc
);
prpl_info
=
PURPLE_PLUGIN_PROTOCOL_INFO
(
gc
->
prpl
);
}
protoname
=
prpl_info
->
list_icon
(
account
,
NULL
);
if
(
purple_strequal
(
protoname
,
protocol
))
break
;
account
=
NULL
;
}
*
ret_account
=
account
;
}
}
else
{
valid
=
FALSE
;
g_free
(
username
);
g_free
(
protocol
);
g_free
(
alias
);
}
g_free
(
str
);
return
valid
;
}
void
pidgin_set_accessible_label
(
GtkWidget
*
w
,
GtkWidget
*
l
)
{
AtkObject
*
acc
;
const
gchar
*
label_text
;
const
gchar
*
existing_name
;
acc
=
gtk_widget_get_accessible
(
w
);
/* If this object has no name, set it's name with the label text */
existing_name
=
atk_object_get_name
(
acc
);
if
(
!
existing_name
)
{
label_text
=
gtk_label_get_text
(
GTK_LABEL
(
l
));
if
(
label_text
)
atk_object_set_name
(
acc
,
label_text
);
}
pidgin_set_accessible_relations
(
w
,
l
);
}
void
pidgin_set_accessible_relations
(
GtkWidget
*
w
,
GtkWidget
*
l
)
{
AtkObject
*
acc
,
*
label
;
AtkObject
*
rel_obj
[
1
];
AtkRelationSet
*
set
;
AtkRelation
*
relation
;
acc
=
gtk_widget_get_accessible
(
w
);
label
=
gtk_widget_get_accessible
(
l
);
/* Make sure mnemonics work */
gtk_label_set_mnemonic_widget
(
GTK_LABEL
(
l
),
w
);
/* Create the labeled-by relation */
set
=
atk_object_ref_relation_set
(
acc
);
rel_obj
[
0
]
=
label
;
relation
=
atk_relation_new
(
rel_obj
,
1
,
ATK_RELATION_LABELLED_BY
);
atk_relation_set_add
(
set
,
relation
);
g_object_unref
(
relation
);
g_object_unref
(
set
);
/* Create the label-for relation */
set
=
atk_object_ref_relation_set
(
label
);
rel_obj
[
0
]
=
acc
;
relation
=
atk_relation_new
(
rel_obj
,
1
,
ATK_RELATION_LABEL_FOR
);
atk_relation_set_add
(
set
,
relation
);
g_object_unref
(
relation
);
g_object_unref
(
set
);
}
void
pidgin_menu_position_func_helper
(
GtkMenu
*
menu
,
gint
*
x
,
gint
*
y
,
gboolean
*
push_in
,
gpointer
data
)
{
GtkWidget
*
widget
;
GtkRequisition
requisition
;
GdkScreen
*
screen
;
GdkRectangle
monitor
;
gint
monitor_num
;
gint
space_left
,
space_right
,
space_above
,
space_below
;
gint
needed_width
;
gint
needed_height
;
gint
xthickness
;
gint
ythickness
;
gboolean
rtl
;
g_return_if_fail
(
GTK_IS_MENU
(
menu
));
widget
=
GTK_WIDGET
(
menu
);
screen
=
gtk_widget_get_screen
(
widget
);
xthickness
=
widget
->
style
->
xthickness
;
ythickness
=
widget
->
style
->
ythickness
;
rtl
=
(
gtk_widget_get_direction
(
widget
)
==
GTK_TEXT_DIR_RTL
);
/*
* We need the requisition to figure out the right place to
* popup the menu. In fact, we always need to ask here, since
* if a size_request was queued while we weren't popped up,
* the requisition won't have been recomputed yet.
*/
gtk_widget_size_request
(
widget
,
&
requisition
);
monitor_num
=
gdk_screen_get_monitor_at_point
(
screen
,
*
x
,
*
y
);
*
push_in
=
FALSE
;
/*
* The placement of popup menus horizontally works like this (with
* RTL in parentheses)
*
* - If there is enough room to the right (left) of the mouse cursor,
* position the menu there.
*
* - Otherwise, if if there is enough room to the left (right) of the
* mouse cursor, position the menu there.
*
* - Otherwise if the menu is smaller than the monitor, position it
* on the side of the mouse cursor that has the most space available
*
* - Otherwise (if there is simply not enough room for the menu on the
* monitor), position it as far left (right) as possible.
*
* Positioning in the vertical direction is similar: first try below
* mouse cursor, then above.
*/
gdk_screen_get_monitor_geometry
(
screen
,
monitor_num
,
&
monitor
);
space_left
=
*
x
-
monitor
.
x
;
space_right
=
monitor
.
x
+
monitor
.
width
-
*
x
-
1
;
space_above
=
*
y
-
monitor
.
y
;
space_below
=
monitor
.
y
+
monitor
.
height
-
*
y
-
1
;
/* position horizontally */
/* the amount of space we need to position the menu. Note the
* menu is offset "xthickness" pixels
*/
needed_width
=
requisition
.
width
-
xthickness
;
if
(
needed_width
<=
space_left
||
needed_width
<=
space_right
)
{
if
((
rtl
&&
needed_width
<=
space_left
)
||
(
!
rtl
&&
needed_width
>
space_right
))
{
/* position left */
*
x
=
*
x
+
xthickness
-
requisition
.
width
+
1
;
}
else
{
/* position right */
*
x
=
*
x
-
xthickness
;
}
/* x is clamped on-screen further down */
}
else
if
(
requisition
.
width
<=
monitor
.
width
)
{
/* the menu is too big to fit on either side of the mouse
* cursor, but smaller than the monitor. Position it on
* the side that has the most space
*/
if
(
space_left
>
space_right
)
{
/* left justify */
*
x
=
monitor
.
x
;
}
else
{
/* right justify */
*
x
=
monitor
.
x
+
monitor
.
width
-
requisition
.
width
;
}
}
else
/* menu is simply too big for the monitor */
{
if
(
rtl
)
{
/* right justify */
*
x
=
monitor
.
x
+
monitor
.
width
-
requisition
.
width
;
}
else
{
/* left justify */
*
x
=
monitor
.
x
;
}
}
/* Position vertically. The algorithm is the same as above, but
* simpler because we don't have to take RTL into account.
*/
needed_height
=
requisition
.
height
-
ythickness
;
if
(
needed_height
<=
space_above
||
needed_height
<=
space_below
)
{
if
(
needed_height
<=
space_below
)
*
y
=
*
y
-
ythickness
;
else
*
y
=
*
y
+
ythickness
-
requisition
.
height
+
1
;
*
y
=
CLAMP
(
*
y
,
monitor
.
y
,
monitor
.
y
+
monitor
.
height
-
requisition
.
height
);
}
else
if
(
needed_height
>
space_below
&&
needed_height
>
space_above
)
{
if
(
space_below
>=
space_above
)
*
y
=
monitor
.
y
+
monitor
.
height
-
requisition
.
height
;
else
*
y
=
monitor
.
y
;
}
else
{
*
y
=
monitor
.
y
;
}
}
void
pidgin_treeview_popup_menu_position_func
(
GtkMenu
*
menu
,
gint
*
x
,
gint
*
y
,
gboolean
*
push_in
,
gpointer
data
)
{
GtkWidget
*
widget
=
GTK_WIDGET
(
data
);
GtkTreeView
*
tv
=
GTK_TREE_VIEW
(
data
);
GtkTreePath
*
path
;
GtkTreeViewColumn
*
col
;
GdkRectangle
rect
;
gint
ythickness
=
GTK_WIDGET
(
menu
)
->
style
->
ythickness
;
gdk_window_get_origin
(
widget
->
window
,
x
,
y
);
gtk_tree_view_get_cursor
(
tv
,
&
path
,
&
col
);
gtk_tree_view_get_cell_area
(
tv
,
path
,
col
,
&
rect
);
*
x
+=
rect
.
x
+
rect
.
width
;
*
y
+=
rect
.
y
+
rect
.
height
+
ythickness
;
pidgin_menu_position_func_helper
(
menu
,
x
,
y
,
push_in
,
data
);
}
enum
{
DND_FILE_TRANSFER
,
DND_IM_IMAGE
,
DND_BUDDY_ICON
};
typedef
struct
{
char
*
filename
;
PurpleAccount
*
account
;
char
*
who
;
}
_DndData
;
static
void
dnd_image_ok_callback
(
_DndData
*
data
,
int
choice
)
{
const
gchar
*
shortname
;
gchar
*
filedata
;
size_t
size
;
struct
stat
st
;
GError
*
err
=
NULL
;
PurpleConversation
*
conv
;
PidginConversation
*
gtkconv
;
GtkTextIter
iter
;
int
id
;
PurpleBuddy
*
buddy
;
PurpleContact
*
contact
;
switch
(
choice
)
{
case
DND_BUDDY_ICON
:
if
(
g_stat
(
data
->
filename
,
&
st
))
{
char
*
str
;
str
=
g_strdup_printf
(
_
(
"The following error has occurred loading %s: %s"
),
data
->
filename
,
g_strerror
(
errno
));
purple_notify_error
(
NULL
,
NULL
,
_
(
"Failed to load image"
),
str
);
g_free
(
str
);
break
;
}
buddy
=
purple_find_buddy
(
data
->
account
,
data
->
who
);
if
(
!
buddy
)
{
purple_debug_info
(
"custom-icon"
,
"You can only set custom icons for people on your buddylist.
\n
"
);
break
;
}
contact
=
purple_buddy_get_contact
(
buddy
);
purple_buddy_icons_node_set_custom_icon_from_file
((
PurpleBlistNode
*
)
contact
,
data
->
filename
);
break
;
case
DND_FILE_TRANSFER
:
serv_send_file
(
purple_account_get_connection
(
data
->
account
),
data
->
who
,
data
->
filename
);
break
;
case
DND_IM_IMAGE
:
conv
=
purple_conversation_new
(
PURPLE_CONV_TYPE_IM
,
data
->
account
,
data
->
who
);
gtkconv
=
PIDGIN_CONVERSATION
(
conv
);
if
(
!
g_file_get_contents
(
data
->
filename
,
&
filedata
,
&
size
,
&
err
))
{
char
*
str
;
str
=
g_strdup_printf
(
_
(
"The following error has occurred loading %s: %s"
),
data
->
filename
,
err
->
message
);
purple_notify_error
(
NULL
,
NULL
,
_
(
"Failed to load image"
),
str
);
g_error_free
(
err
);
g_free
(
str
);
break
;
}
shortname
=
strrchr
(
data
->
filename
,
G_DIR_SEPARATOR
);
shortname
=
shortname
?
shortname
+
1
:
data
->
filename
;
id
=
purple_imgstore_add_with_id
(
filedata
,
size
,
shortname
);
gtk_text_buffer_get_iter_at_mark
(
GTK_IMHTML
(
gtkconv
->
entry
)
->
text_buffer
,
&
iter
,
gtk_text_buffer_get_insert
(
GTK_IMHTML
(
gtkconv
->
entry
)
->
text_buffer
));
gtk_imhtml_insert_image_at_iter
(
GTK_IMHTML
(
gtkconv
->
entry
),
id
,
&
iter
);
purple_imgstore_unref_by_id
(
id
);
break
;
}
g_free
(
data
->
filename
);
g_free
(
data
->
who
);
g_free
(
data
);
}
static
void
dnd_image_cancel_callback
(
_DndData
*
data
,
int
choice
)
{
g_free
(
data
->
filename
);
g_free
(
data
->
who
);
g_free
(
data
);
}
static
void
dnd_set_icon_ok_cb
(
_DndData
*
data
)
{
dnd_image_ok_callback
(
data
,
DND_BUDDY_ICON
);
}
static
void
dnd_set_icon_cancel_cb
(
_DndData
*
data
)
{
g_free
(
data
->
filename
);
g_free
(
data
->
who
);
g_free
(
data
);
}
void
pidgin_dnd_file_manage
(
GtkSelectionData
*
sd
,
PurpleAccount
*
account
,
const
char
*
who
)
{
GdkPixbuf
*
pb
;
GList
*
files
=
purple_uri_list_extract_filenames
((
const
gchar
*
)
sd
->
data
);
PurpleConnection
*
gc
=
purple_account_get_connection
(
account
);
PurplePluginProtocolInfo
*
prpl_info
=
NULL
;
#ifndef _WIN32
PurpleDesktopItem
*
item
;
#endif
gchar
*
filename
=
NULL
;
gchar
*
basename
=
NULL
;
g_return_if_fail
(
account
!=
NULL
);
g_return_if_fail
(
who
!=
NULL
);
for
(
;
files
;
files
=
g_list_delete_link
(
files
,
files
))
{
g_free
(
filename
);
g_free
(
basename
);
filename
=
files
->
data
;
basename
=
g_path_get_basename
(
filename
);
/* XXX - Make ft API support creating a transfer with more than one file */
if
(
!
g_file_test
(
filename
,
G_FILE_TEST_EXISTS
))
{
continue
;
}
/* XXX - make ft api suupport sending a directory */
/* Are we dealing with a directory? */
if
(
g_file_test
(
filename
,
G_FILE_TEST_IS_DIR
))
{
char
*
str
,
*
str2
;
str
=
g_strdup_printf
(
_
(
"Cannot send folder %s."
),
basename
);
str2
=
g_strdup_printf
(
_
(
"%s cannot transfer a folder. You will need to send the files within individually."
),
PIDGIN_NAME
);
purple_notify_error
(
NULL
,
NULL
,
str
,
str2
);
g_free
(
str
);
g_free
(
str2
);
continue
;
}
/* Are we dealing with an image? */
pb
=
pidgin_pixbuf_new_from_file
(
filename
);
if
(
pb
)
{
_DndData
*
data
=
g_malloc
(
sizeof
(
_DndData
));
gboolean
ft
=
FALSE
,
im
=
FALSE
;
data
->
who
=
g_strdup
(
who
);
data
->
filename
=
g_strdup
(
filename
);
data
->
account
=
account
;
if
(
gc
)
prpl_info
=
PURPLE_PLUGIN_PROTOCOL_INFO
(
gc
->
prpl
);
if
(
prpl_info
&&
prpl_info
->
options
&
OPT_PROTO_IM_IMAGE
)
im
=
TRUE
;
if
(
prpl_info
&&
prpl_info
->
can_receive_file
)
ft
=
prpl_info
->
can_receive_file
(
gc
,
who
);
else
if
(
prpl_info
&&
prpl_info
->
send_file
)
ft
=
TRUE
;
if
(
im
&&
ft
)
purple_request_choice
(
NULL
,
NULL
,
_
(
"You have dragged an image"
),
_
(
"You can send this image as a file transfer, "
"embed it into this message, or use it as the buddy icon for this user."
),
DND_FILE_TRANSFER
,
_
(
"OK"
),
(
GCallback
)
dnd_image_ok_callback
,
_
(
"Cancel"
),
(
GCallback
)
dnd_image_cancel_callback
,
account
,
who
,
NULL
,
data
,
_
(
"Set as buddy icon"
),
DND_BUDDY_ICON
,
_
(
"Send image file"
),
DND_FILE_TRANSFER
,
_
(
"Insert in message"
),
DND_IM_IMAGE
,
NULL
);
else
if
(
!
(
im
||
ft
))
purple_request_yes_no
(
NULL
,
NULL
,
_
(
"You have dragged an image"
),
_
(
"Would you like to set it as the buddy icon for this user?"
),
PURPLE_DEFAULT_ACTION_NONE
,
account
,
who
,
NULL
,
data
,
(
GCallback
)
dnd_set_icon_ok_cb
,
(
GCallback
)
dnd_set_icon_cancel_cb
);
else
purple_request_choice
(
NULL
,
NULL
,
_
(
"You have dragged an image"
),
(
ft
?
_
(
"You can send this image as a file transfer, or use it as the buddy icon for this user."
)
:
_
(
"You can insert this image into this message, or use it as the buddy icon for this user"
)),
(
ft
?
DND_FILE_TRANSFER
:
DND_IM_IMAGE
),
_
(
"OK"
),
(
GCallback
)
dnd_image_ok_callback
,
_
(
"Cancel"
),
(
GCallback
)
dnd_image_cancel_callback
,
account
,
who
,
NULL
,
data
,
_
(
"Set as buddy icon"
),
DND_BUDDY_ICON
,
(
ft
?
_
(
"Send image file"
)
:
_
(
"Insert in message"
)),
(
ft
?
DND_FILE_TRANSFER
:
DND_IM_IMAGE
),
NULL
);
g_object_unref
(
G_OBJECT
(
pb
));
g_free
(
basename
);
while
(
files
)
{
g_free
(
files
->
data
);
files
=
g_list_delete_link
(
files
,
files
);
}
return
;
}
#ifndef _WIN32
/* Are we trying to send a .desktop file? */
else
if
(
purple_str_has_suffix
(
basename
,
".desktop"
)
&&
(
item
=
purple_desktop_item_new_from_file
(
filename
)))
{
PurpleDesktopItemType
dtype
;
char
key
[
64
];
const
char
*
itemname
=
NULL
;
const
char
*
const
*
langs
;
langs
=
g_get_language_names
();
if
(
langs
[
0
])
{
g_snprintf
(
key
,
sizeof
(
key
),
"Name[%s]"
,
langs
[
0
]);
itemname
=
purple_desktop_item_get_string
(
item
,
key
);
}
if
(
!
itemname
)
itemname
=
purple_desktop_item_get_string
(
item
,
"Name"
);
dtype
=
purple_desktop_item_get_entry_type
(
item
);
switch
(
dtype
)
{
PurpleConversation
*
conv
;
PidginConversation
*
gtkconv
;
case
PURPLE_DESKTOP_ITEM_TYPE_LINK
:
conv
=
purple_conversation_new
(
PURPLE_CONV_TYPE_IM
,
account
,
who
);
gtkconv
=
PIDGIN_CONVERSATION
(
conv
);
gtk_imhtml_insert_link
(
GTK_IMHTML
(
gtkconv
->
entry
),
gtk_text_buffer_get_insert
(
GTK_IMHTML
(
gtkconv
->
entry
)
->
text_buffer
),
purple_desktop_item_get_string
(
item
,
"URL"
),
itemname
);
break
;
default
:
/* I don't know if we really want to do anything here. Most of
* the desktop item types are crap like "MIME Type" (I have no
* clue how that would be a desktop item) and "Comment"...
* nothing we can really send. The only logical one is
* "Application," but do we really want to send a binary and
* nothing else? Probably not. I'll just give an error and
* return. */
/* The original patch sent the icon used by the launcher. That's probably wrong */
purple_notify_error
(
NULL
,
NULL
,
_
(
"Cannot send launcher"
),
_
(
"You dragged a desktop launcher. Most "
"likely you wanted to send the target "
"of this launcher instead of this "
"launcher itself."
));
break
;
}
purple_desktop_item_unref
(
item
);
g_free
(
basename
);
while
(
files
)
{
g_free
(
files
->
data
);
files
=
g_list_delete_link
(
files
,
files
);
}
return
;
}
#endif
/* _WIN32 */
/* Everything is fine, let's send */
serv_send_file
(
gc
,
who
,
filename
);
}
g_free
(
filename
);
g_free
(
basename
);
}
void
pidgin_buddy_icon_get_scale_size
(
GdkPixbuf
*
buf
,
PurpleBuddyIconSpec
*
spec
,
PurpleIconScaleRules
rules
,
int
*
width
,
int
*
height
)
{
*
width
=
gdk_pixbuf_get_width
(
buf
);
*
height
=
gdk_pixbuf_get_height
(
buf
);
if
((
spec
==
NULL
)
||
!
(
spec
->
scale_rules
&
rules
))
return
;
purple_buddy_icon_get_scale_size
(
spec
,
width
,
height
);
/* and now for some arbitrary sanity checks */
if
(
*
width
>
100
)
*
width
=
100
;
if
(
*
height
>
100
)
*
height
=
100
;
}
GdkPixbuf
*
pidgin_create_status_icon
(
PurpleStatusPrimitive
prim
,
GtkWidget
*
w
,
const
char
*
size
)
{
GtkIconSize
icon_size
=
gtk_icon_size_from_name
(
size
);
GdkPixbuf
*
pixbuf
=
NULL
;
const
char
*
stock
=
pidgin_stock_id_from_status_primitive
(
prim
);
pixbuf
=
gtk_widget_render_icon
(
w
,
stock
?
stock
:
PIDGIN_STOCK_STATUS_AVAILABLE
,
icon_size
,
"GtkWidget"
);
return
pixbuf
;
}
static
const
char
*
stock_id_from_status_primitive_idle
(
PurpleStatusPrimitive
prim
,
gboolean
idle
)
{
const
char
*
stock
=
NULL
;
switch
(
prim
)
{
case
PURPLE_STATUS_UNSET
:
stock
=
NULL
;
break
;
case
PURPLE_STATUS_UNAVAILABLE
:
stock
=
idle
?
PIDGIN_STOCK_STATUS_BUSY_I
:
PIDGIN_STOCK_STATUS_BUSY
;
break
;
case
PURPLE_STATUS_AWAY
:
stock
=
idle
?
PIDGIN_STOCK_STATUS_AWAY_I
:
PIDGIN_STOCK_STATUS_AWAY
;
break
;
case
PURPLE_STATUS_EXTENDED_AWAY
:
stock
=
idle
?
PIDGIN_STOCK_STATUS_XA_I
:
PIDGIN_STOCK_STATUS_XA
;
break
;
case
PURPLE_STATUS_INVISIBLE
:
stock
=
PIDGIN_STOCK_STATUS_INVISIBLE
;
break
;
case
PURPLE_STATUS_OFFLINE
:
stock
=
idle
?
PIDGIN_STOCK_STATUS_OFFLINE_I
:
PIDGIN_STOCK_STATUS_OFFLINE
;
break
;
default
:
stock
=
idle
?
PIDGIN_STOCK_STATUS_AVAILABLE_I
:
PIDGIN_STOCK_STATUS_AVAILABLE
;
break
;
}
return
stock
;
}
const
char
*
pidgin_stock_id_from_status_primitive
(
PurpleStatusPrimitive
prim
)
{
return
stock_id_from_status_primitive_idle
(
prim
,
FALSE
);
}
const
char
*
pidgin_stock_id_from_presence
(
PurplePresence
*
presence
)
{
PurpleStatus
*
status
;
PurpleStatusType
*
type
;
PurpleStatusPrimitive
prim
;
gboolean
idle
;
g_return_val_if_fail
(
presence
,
NULL
);
status
=
purple_presence_get_active_status
(
presence
);
type
=
purple_status_get_type
(
status
);
prim
=
purple_status_type_get_primitive
(
type
);
idle
=
purple_presence_is_idle
(
presence
);
return
stock_id_from_status_primitive_idle
(
prim
,
idle
);
}
GdkPixbuf
*
pidgin_create_prpl_icon
(
PurpleAccount
*
account
,
PidginPrplIconSize
size
)
{
PurplePlugin
*
prpl
;
g_return_val_if_fail
(
account
!=
NULL
,
NULL
);
prpl
=
purple_find_prpl
(
purple_account_get_protocol_id
(
account
));
if
(
prpl
==
NULL
)
return
NULL
;
return
pidgin_create_prpl_icon_from_prpl
(
prpl
,
size
,
account
);
}
static
void
menu_action_cb
(
GtkMenuItem
*
item
,
gpointer
object
)
{
gpointer
data
;
void
(
*
callback
)(
gpointer
,
gpointer
);
callback
=
g_object_get_data
(
G_OBJECT
(
item
),
"purplecallback"
);
data
=
g_object_get_data
(
G_OBJECT
(
item
),
"purplecallbackdata"
);
if
(
callback
)
callback
(
object
,
data
);
}
GtkWidget
*
pidgin_append_menu_action
(
GtkWidget
*
menu
,
PurpleMenuAction
*
act
,
gpointer
object
)
{
GtkWidget
*
menuitem
;
if
(
act
==
NULL
)
{
return
pidgin_separator
(
menu
);
}
if
(
act
->
children
==
NULL
)
{
menuitem
=
gtk_menu_item_new_with_mnemonic
(
act
->
label
);
if
(
act
->
callback
!=
NULL
)
{
g_object_set_data
(
G_OBJECT
(
menuitem
),
"purplecallback"
,
act
->
callback
);
g_object_set_data
(
G_OBJECT
(
menuitem
),
"purplecallbackdata"
,
act
->
data
);
g_signal_connect
(
G_OBJECT
(
menuitem
),
"activate"
,
G_CALLBACK
(
menu_action_cb
),
object
);
}
else
{
gtk_widget_set_sensitive
(
menuitem
,
FALSE
);
}
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
menuitem
);
}
else
{
GList
*
l
=
NULL
;
GtkWidget
*
submenu
=
NULL
;
GtkAccelGroup
*
group
;
menuitem
=
gtk_menu_item_new_with_mnemonic
(
act
->
label
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
menuitem
);
submenu
=
gtk_menu_new
();
gtk_menu_item_set_submenu
(
GTK_MENU_ITEM
(
menuitem
),
submenu
);
group
=
gtk_menu_get_accel_group
(
GTK_MENU
(
menu
));
if
(
group
)
{
char
*
path
=
g_strdup_printf
(
"%s/%s"
,
GTK_MENU_ITEM
(
menuitem
)
->
accel_path
,
act
->
label
);
gtk_menu_set_accel_path
(
GTK_MENU
(
submenu
),
path
);
g_free
(
path
);
gtk_menu_set_accel_group
(
GTK_MENU
(
submenu
),
group
);
}
for
(
l
=
act
->
children
;
l
;
l
=
l
->
next
)
{
PurpleMenuAction
*
act
=
(
PurpleMenuAction
*
)
l
->
data
;
pidgin_append_menu_action
(
submenu
,
act
,
object
);
}
g_list_free
(
act
->
children
);
act
->
children
=
NULL
;
}
purple_menu_action_free
(
act
);
return
menuitem
;
}
typedef
struct
{
GtkWidget
*
entry
;
GtkWidget
*
accountopt
;
PidginFilterBuddyCompletionEntryFunc
filter_func
;
gpointer
filter_func_user_data
;
GtkListStore
*
store
;
}
PidginCompletionData
;
static
gboolean
buddyname_completion_match_func
(
GtkEntryCompletion
*
completion
,
const
gchar
*
key
,
GtkTreeIter
*
iter
,
gpointer
user_data
)
{
GtkTreeModel
*
model
;
GValue
val1
;
GValue
val2
;
const
char
*
tmp
;
model
=
gtk_entry_completion_get_model
(
completion
);
val1
.
g_type
=
0
;
gtk_tree_model_get_value
(
model
,
iter
,
2
,
&
val1
);
tmp
=
g_value_get_string
(
&
val1
);
if
(
tmp
!=
NULL
&&
purple_str_has_prefix
(
tmp
,
key
))
{
g_value_unset
(
&
val1
);
return
TRUE
;
}
g_value_unset
(
&
val1
);
val2
.
g_type
=
0
;
gtk_tree_model_get_value
(
model
,
iter
,
3
,
&
val2
);
tmp
=
g_value_get_string
(
&
val2
);
if
(
tmp
!=
NULL
&&
purple_str_has_prefix
(
tmp
,
key
))
{
g_value_unset
(
&
val2
);
return
TRUE
;
}
g_value_unset
(
&
val2
);
return
FALSE
;
}
static
gboolean
buddyname_completion_match_selected_cb
(
GtkEntryCompletion
*
completion
,
GtkTreeModel
*
model
,
GtkTreeIter
*
iter
,
PidginCompletionData
*
data
)
{
GValue
val
;
GtkWidget
*
optmenu
=
data
->
accountopt
;
PurpleAccount
*
account
;
val
.
g_type
=
0
;
gtk_tree_model_get_value
(
model
,
iter
,
1
,
&
val
);
gtk_entry_set_text
(
GTK_ENTRY
(
data
->
entry
),
g_value_get_string
(
&
val
));
g_value_unset
(
&
val
);
gtk_tree_model_get_value
(
model
,
iter
,
4
,
&
val
);
account
=
g_value_get_pointer
(
&
val
);
g_value_unset
(
&
val
);
if
(
account
==
NULL
)
return
TRUE
;
if
(
optmenu
!=
NULL
)
aop_option_menu_select_by_data
(
optmenu
,
account
);
return
TRUE
;
}
static
void
add_buddyname_autocomplete_entry
(
GtkListStore
*
store
,
const
char
*
buddy_alias
,
const
char
*
contact_alias
,
const
PurpleAccount
*
account
,
const
char
*
buddyname
)
{
GtkTreeIter
iter
;
gboolean
completion_added
=
FALSE
;
gchar
*
normalized_buddyname
;
gchar
*
tmp
;
tmp
=
g_utf8_normalize
(
buddyname
,
-1
,
G_NORMALIZE_DEFAULT
);
normalized_buddyname
=
g_utf8_casefold
(
tmp
,
-1
);
g_free
(
tmp
);
/* There's no sense listing things like: 'xxx "xxx"'
when the name and buddy alias match. */
if
(
buddy_alias
&&
!
purple_strequal
(
buddy_alias
,
buddyname
))
{
char
*
completion_entry
=
g_strdup_printf
(
"%s
\"
%s
\"
"
,
buddyname
,
buddy_alias
);
char
*
tmp2
=
g_utf8_normalize
(
buddy_alias
,
-1
,
G_NORMALIZE_DEFAULT
);
tmp
=
g_utf8_casefold
(
tmp2
,
-1
);
g_free
(
tmp2
);
gtk_list_store_append
(
store
,
&
iter
);
gtk_list_store_set
(
store
,
&
iter
,
0
,
completion_entry
,
1
,
buddyname
,
2
,
normalized_buddyname
,
3
,
tmp
,
4
,
account
,
-1
);
g_free
(
completion_entry
);
g_free
(
tmp
);
completion_added
=
TRUE
;
}
/* There's no sense listing things like: 'xxx "xxx"'
when the name and contact alias match. */
if
(
contact_alias
&&
!
purple_strequal
(
contact_alias
,
buddyname
))
{
/* We don't want duplicates when the contact and buddy alias match. */
if
(
!
purple_strequal
(
contact_alias
,
buddy_alias
))
{
char
*
completion_entry
=
g_strdup_printf
(
"%s
\"
%s
\"
"
,
buddyname
,
contact_alias
);
char
*
tmp2
=
g_utf8_normalize
(
contact_alias
,
-1
,
G_NORMALIZE_DEFAULT
);
tmp
=
g_utf8_casefold
(
tmp2
,
-1
);
g_free
(
tmp2
);
gtk_list_store_append
(
store
,
&
iter
);
gtk_list_store_set
(
store
,
&
iter
,
0
,
completion_entry
,
1
,
buddyname
,
2
,
normalized_buddyname
,
3
,
tmp
,
4
,
account
,
-1
);
g_free
(
completion_entry
);
g_free
(
tmp
);
completion_added
=
TRUE
;
}
}
if
(
completion_added
==
FALSE
)
{
/* Add the buddy's name. */
gtk_list_store_append
(
store
,
&
iter
);
gtk_list_store_set
(
store
,
&
iter
,
0
,
buddyname
,
1
,
buddyname
,
2
,
normalized_buddyname
,
3
,
NULL
,
4
,
account
,
-1
);
}
g_free
(
normalized_buddyname
);
}
static
void
get_log_set_name
(
PurpleLogSet
*
set
,
gpointer
value
,
PidginCompletionData
*
data
)
{
PidginFilterBuddyCompletionEntryFunc
filter_func
=
data
->
filter_func
;
gpointer
user_data
=
data
->
filter_func_user_data
;
/* 1. Don't show buddies because we will have gotten them already.
* 2. The boxes that use this autocomplete code handle only IMs. */
if
(
!
set
->
buddy
&&
set
->
type
==
PURPLE_LOG_IM
)
{
PidginBuddyCompletionEntry
entry
;
entry
.
is_buddy
=
FALSE
;
entry
.
entry
.
logged_buddy
=
set
;
if
(
filter_func
(
&
entry
,
user_data
))
{
add_buddyname_autocomplete_entry
(
data
->
store
,
NULL
,
NULL
,
set
->
account
,
set
->
name
);
}
}
}
static
void
add_completion_list
(
PidginCompletionData
*
data
)
{
PurpleBlistNode
*
gnode
,
*
cnode
,
*
bnode
;
PidginFilterBuddyCompletionEntryFunc
filter_func
=
data
->
filter_func
;
gpointer
user_data
=
data
->
filter_func_user_data
;
GHashTable
*
sets
;
gtk_list_store_clear
(
data
->
store
);
for
(
gnode
=
purple_get_blist
()
->
root
;
gnode
!=
NULL
;
gnode
=
gnode
->
next
)
{
if
(
!
PURPLE_BLIST_NODE_IS_GROUP
(
gnode
))
continue
;
for
(
cnode
=
gnode
->
child
;
cnode
!=
NULL
;
cnode
=
cnode
->
next
)
{
if
(
!
PURPLE_BLIST_NODE_IS_CONTACT
(
cnode
))
continue
;
for
(
bnode
=
cnode
->
child
;
bnode
!=
NULL
;
bnode
=
bnode
->
next
)
{
PidginBuddyCompletionEntry
entry
;
entry
.
is_buddy
=
TRUE
;
entry
.
entry
.
buddy
=
(
PurpleBuddy
*
)
bnode
;
if
(
filter_func
(
&
entry
,
user_data
))
{
add_buddyname_autocomplete_entry
(
data
->
store
,
((
PurpleContact
*
)
cnode
)
->
alias
,
purple_buddy_get_contact_alias
(
entry
.
entry
.
buddy
),
entry
.
entry
.
buddy
->
account
,
entry
.
entry
.
buddy
->
name
);
}
}
}
}
sets
=
purple_log_get_log_sets
();
g_hash_table_foreach
(
sets
,
(
GHFunc
)
get_log_set_name
,
data
);
g_hash_table_destroy
(
sets
);
}
static
void
buddyname_autocomplete_destroyed_cb
(
GtkWidget
*
widget
,
gpointer
data
)
{
g_free
(
data
);
purple_signals_disconnect_by_handle
(
widget
);
}
static
void
repopulate_autocomplete
(
gpointer
something
,
gpointer
data
)
{
add_completion_list
(
data
);
}
void
pidgin_setup_screenname_autocomplete_with_filter
(
GtkWidget
*
entry
,
GtkWidget
*
accountopt
,
PidginFilterBuddyCompletionEntryFunc
filter_func
,
gpointer
user_data
)
{
PidginCompletionData
*
data
;
/*
* Store the displayed completion value, the buddy name, the UTF-8
* normalized & casefolded buddy name, the UTF-8 normalized &
* casefolded value for comparison, and the account.
*/
GtkListStore
*
store
;
GtkEntryCompletion
*
completion
;
data
=
g_new0
(
PidginCompletionData
,
1
);
store
=
gtk_list_store_new
(
5
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_POINTER
);
data
->
entry
=
entry
;
data
->
accountopt
=
accountopt
;
if
(
filter_func
==
NULL
)
{
data
->
filter_func
=
pidgin_screenname_autocomplete_default_filter
;
data
->
filter_func_user_data
=
NULL
;
}
else
{
data
->
filter_func
=
filter_func
;
data
->
filter_func_user_data
=
user_data
;
}
data
->
store
=
store
;
add_completion_list
(
data
);
/* Sort the completion list by buddy name */
gtk_tree_sortable_set_sort_column_id
(
GTK_TREE_SORTABLE
(
store
),
1
,
GTK_SORT_ASCENDING
);
completion
=
gtk_entry_completion_new
();
gtk_entry_completion_set_match_func
(
completion
,
buddyname_completion_match_func
,
NULL
,
NULL
);
g_signal_connect
(
G_OBJECT
(
completion
),
"match-selected"
,
G_CALLBACK
(
buddyname_completion_match_selected_cb
),
data
);
gtk_entry_set_completion
(
GTK_ENTRY
(
entry
),
completion
);
g_object_unref
(
completion
);
gtk_entry_completion_set_model
(
completion
,
GTK_TREE_MODEL
(
store
));
g_object_unref
(
store
);
gtk_entry_completion_set_text_column
(
completion
,
0
);
purple_signal_connect
(
purple_connections_get_handle
(),
"signed-on"
,
entry
,
PURPLE_CALLBACK
(
repopulate_autocomplete
),
data
);
purple_signal_connect
(
purple_connections_get_handle
(),
"signed-off"
,
entry
,
PURPLE_CALLBACK
(
repopulate_autocomplete
),
data
);
purple_signal_connect
(
purple_accounts_get_handle
(),
"account-added"
,
entry
,
PURPLE_CALLBACK
(
repopulate_autocomplete
),
data
);
purple_signal_connect
(
purple_accounts_get_handle
(),
"account-removed"
,
entry
,
PURPLE_CALLBACK
(
repopulate_autocomplete
),
data
);
g_signal_connect
(
G_OBJECT
(
entry
),
"destroy"
,
G_CALLBACK
(
buddyname_autocomplete_destroyed_cb
),
data
);
}
gboolean
pidgin_screenname_autocomplete_default_filter
(
const
PidginBuddyCompletionEntry
*
completion_entry
,
gpointer
all_accounts
)
{
gboolean
all
=
GPOINTER_TO_INT
(
all_accounts
);
if
(
completion_entry
->
is_buddy
)
{
return
all
||
purple_account_is_connected
(
completion_entry
->
entry
.
buddy
->
account
);
}
else
{
return
all
||
(
completion_entry
->
entry
.
logged_buddy
->
account
!=
NULL
&&
purple_account_is_connected
(
completion_entry
->
entry
.
logged_buddy
->
account
));
}
}
void
pidgin_setup_screenname_autocomplete
(
GtkWidget
*
entry
,
GtkWidget
*
accountopt
,
gboolean
all
)
{
pidgin_setup_screenname_autocomplete_with_filter
(
entry
,
accountopt
,
pidgin_screenname_autocomplete_default_filter
,
GINT_TO_POINTER
(
all
));
}
void
pidgin_set_cursor
(
GtkWidget
*
widget
,
GdkCursorType
cursor_type
)
{
GdkCursor
*
cursor
;
g_return_if_fail
(
widget
!=
NULL
);
if
(
widget
->
window
==
NULL
)
return
;
cursor
=
gdk_cursor_new
(
cursor_type
);
gdk_window_set_cursor
(
widget
->
window
,
cursor
);
gdk_cursor_unref
(
cursor
);
gdk_display_flush
(
gdk_drawable_get_display
(
GDK_DRAWABLE
(
widget
->
window
)));
}
void
pidgin_clear_cursor
(
GtkWidget
*
widget
)
{
g_return_if_fail
(
widget
!=
NULL
);
if
(
widget
->
window
==
NULL
)
return
;
gdk_window_set_cursor
(
widget
->
window
,
NULL
);
}
struct
_icon_chooser
{
GtkWidget
*
icon_filesel
;
GtkWidget
*
icon_preview
;
GtkWidget
*
icon_text
;
void
(
*
callback
)(
const
char
*
,
gpointer
);
gpointer
data
;
};
static
void
icon_filesel_choose_cb
(
GtkWidget
*
widget
,
gint
response
,
struct
_icon_chooser
*
dialog
)
{
char
*
filename
,
*
current_folder
;
if
(
response
!=
GTK_RESPONSE_ACCEPT
)
{
if
(
response
==
GTK_RESPONSE_CANCEL
)
{
gtk_widget_destroy
(
dialog
->
icon_filesel
);
}
dialog
->
icon_filesel
=
NULL
;
if
(
dialog
->
callback
)
dialog
->
callback
(
NULL
,
dialog
->
data
);
g_free
(
dialog
);
return
;
}
filename
=
gtk_file_chooser_get_filename
(
GTK_FILE_CHOOSER
(
dialog
->
icon_filesel
));
current_folder
=
gtk_file_chooser_get_current_folder
(
GTK_FILE_CHOOSER
(
dialog
->
icon_filesel
));
if
(
current_folder
!=
NULL
)
{
purple_prefs_set_path
(
PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder"
,
current_folder
);
g_free
(
current_folder
);
}
if
(
dialog
->
callback
)
dialog
->
callback
(
filename
,
dialog
->
data
);
gtk_widget_destroy
(
dialog
->
icon_filesel
);
g_free
(
filename
);
g_free
(
dialog
);
}
static
void
icon_preview_change_cb
(
GtkFileChooser
*
widget
,
struct
_icon_chooser
*
dialog
)
{
GdkPixbuf
*
pixbuf
;
int
height
,
width
;
char
*
basename
,
*
markup
,
*
size
;
struct
stat
st
;
char
*
filename
;
filename
=
gtk_file_chooser_get_preview_filename
(
GTK_FILE_CHOOSER
(
dialog
->
icon_filesel
));
if
(
!
filename
||
g_stat
(
filename
,
&
st
)
||
!
(
pixbuf
=
pidgin_pixbuf_new_from_file_at_size
(
filename
,
128
,
128
)))
{
gtk_image_set_from_pixbuf
(
GTK_IMAGE
(
dialog
->
icon_preview
),
NULL
);
gtk_label_set_markup
(
GTK_LABEL
(
dialog
->
icon_text
),
""
);
g_free
(
filename
);
return
;
}
gdk_pixbuf_get_file_info
(
filename
,
&
width
,
&
height
);
basename
=
g_path_get_basename
(
filename
);
size
=
purple_str_size_to_units
(
st
.
st_size
);
markup
=
g_strdup_printf
(
_
(
"<b>File:</b> %s
\n
"
"<b>File size:</b> %s
\n
"
"<b>Image size:</b> %dx%d"
),
basename
,
size
,
width
,
height
);
gtk_image_set_from_pixbuf
(
GTK_IMAGE
(
dialog
->
icon_preview
),
pixbuf
);
gtk_label_set_markup
(
GTK_LABEL
(
dialog
->
icon_text
),
markup
);
g_object_unref
(
G_OBJECT
(
pixbuf
));
g_free
(
filename
);
g_free
(
basename
);
g_free
(
size
);
g_free
(
markup
);
}
GtkWidget
*
pidgin_buddy_icon_chooser_new
(
GtkWindow
*
parent
,
void
(
*
callback
)(
const
char
*
,
gpointer
),
gpointer
data
)
{
struct
_icon_chooser
*
dialog
=
g_new0
(
struct
_icon_chooser
,
1
);
GtkWidget
*
vbox
;
const
char
*
current_folder
;
dialog
->
callback
=
callback
;
dialog
->
data
=
data
;
current_folder
=
purple_prefs_get_path
(
PIDGIN_PREFS_ROOT
"/filelocations/last_icon_folder"
);
dialog
->
icon_filesel
=
gtk_file_chooser_dialog_new
(
_
(
"Buddy Icon"
),
parent
,
GTK_FILE_CHOOSER_ACTION_OPEN
,
GTK_STOCK_CANCEL
,
GTK_RESPONSE_CANCEL
,
GTK_STOCK_OPEN
,
GTK_RESPONSE_ACCEPT
,
NULL
);
gtk_dialog_set_default_response
(
GTK_DIALOG
(
dialog
->
icon_filesel
),
GTK_RESPONSE_ACCEPT
);
if
((
current_folder
!=
NULL
)
&&
(
*
current_folder
!=
'\0'
))
gtk_file_chooser_set_current_folder
(
GTK_FILE_CHOOSER
(
dialog
->
icon_filesel
),
current_folder
);
dialog
->
icon_preview
=
gtk_image_new
();
dialog
->
icon_text
=
gtk_label_new
(
NULL
);
vbox
=
gtk_vbox_new
(
FALSE
,
PIDGIN_HIG_BOX_SPACE
);
gtk_widget_set_size_request
(
GTK_WIDGET
(
vbox
),
-1
,
50
);
gtk_box_pack_start
(
GTK_BOX
(
vbox
),
GTK_WIDGET
(
dialog
->
icon_preview
),
TRUE
,
FALSE
,
0
);
gtk_box_pack_end
(
GTK_BOX
(
vbox
),
GTK_WIDGET
(
dialog
->
icon_text
),
FALSE
,
FALSE
,
0
);
gtk_widget_show_all
(
vbox
);
gtk_file_chooser_set_preview_widget
(
GTK_FILE_CHOOSER
(
dialog
->
icon_filesel
),
vbox
);
gtk_file_chooser_set_preview_widget_active
(
GTK_FILE_CHOOSER
(
dialog
->
icon_filesel
),
TRUE
);
gtk_file_chooser_set_use_preview_label
(
GTK_FILE_CHOOSER
(
dialog
->
icon_filesel
),
FALSE
);
g_signal_connect
(
G_OBJECT
(
dialog
->
icon_filesel
),
"update-preview"
,
G_CALLBACK
(
icon_preview_change_cb
),
dialog
);
g_signal_connect
(
G_OBJECT
(
dialog
->
icon_filesel
),
"response"
,
G_CALLBACK
(
icon_filesel_choose_cb
),
dialog
);
icon_preview_change_cb
(
NULL
,
dialog
);
#ifdef _WIN32
g_signal_connect
(
G_OBJECT
(
dialog
->
icon_filesel
),
"show"
,
G_CALLBACK
(
winpidgin_ensure_onscreen
),
dialog
->
icon_filesel
);
#endif
return
dialog
->
icon_filesel
;
}
/**
* @return True if any string from array a exists in array b.
*/
static
gboolean
str_array_match
(
char
**
a
,
char
**
b
)
{
int
i
,
j
;
if
(
!
a
||
!
b
)
return
FALSE
;
for
(
i
=
0
;
a
[
i
]
!=
NULL
;
i
++
)
for
(
j
=
0
;
b
[
j
]
!=
NULL
;
j
++
)
if
(
!
g_ascii_strcasecmp
(
a
[
i
],
b
[
j
]))
return
TRUE
;
return
FALSE
;
}
gpointer
pidgin_convert_buddy_icon
(
PurplePlugin
*
plugin
,
const
char
*
path
,
size_t
*
len
)
{
PurplePluginProtocolInfo
*
prpl_info
;
PurpleBuddyIconSpec
*
spec
;
int
orig_width
,
orig_height
,
new_width
,
new_height
;
GdkPixbufFormat
*
format
;
char
**
pixbuf_formats
;
char
**
prpl_formats
;
GError
*
error
=
NULL
;
gchar
*
contents
;
gsize
length
;
GdkPixbuf
*
pixbuf
,
*
original
;
float
scale_factor
;
int
i
;
gchar
*
tmp
;
prpl_info
=
PURPLE_PLUGIN_PROTOCOL_INFO
(
plugin
);
spec
=
&
prpl_info
->
icon_spec
;
g_return_val_if_fail
(
spec
->
format
!=
NULL
,
NULL
);
format
=
gdk_pixbuf_get_file_info
(
path
,
&
orig_width
,
&
orig_height
);
if
(
format
==
NULL
)
{
purple_debug_warning
(
"buddyicon"
,
"Could not get file info of %s
\n
"
,
path
);
return
NULL
;
}
pixbuf_formats
=
gdk_pixbuf_format_get_extensions
(
format
);
prpl_formats
=
g_strsplit
(
spec
->
format
,
","
,
0
);
if
(
str_array_match
(
pixbuf_formats
,
prpl_formats
)
&&
/* This is an acceptable format AND */
(
!
(
spec
->
scale_rules
&
PURPLE_ICON_SCALE_SEND
)
||
/* The prpl doesn't scale before it sends OR */
(
spec
->
min_width
<=
orig_width
&&
spec
->
max_width
>=
orig_width
&&
spec
->
min_height
<=
orig_height
&&
spec
->
max_height
>=
orig_height
)))
/* The icon is the correct size */
{
g_strfreev
(
pixbuf_formats
);
if
(
!
g_file_get_contents
(
path
,
&
contents
,
&
length
,
&
error
))
{
purple_debug_warning
(
"buddyicon"
,
"Could not get file contents "
"of %s: %s
\n
"
,
path
,
error
->
message
);
g_strfreev
(
prpl_formats
);
return
NULL
;
}
if
(
spec
->
max_filesize
==
0
||
length
<
spec
->
max_filesize
)
{
/* The supplied image fits the file size, dimensions and type
constraints. Great! Return it without making any changes. */
if
(
len
)
*
len
=
length
;
g_strfreev
(
prpl_formats
);
return
contents
;
}
/* The image was too big. Fall-through and try scaling it down. */
g_free
(
contents
);
}
else
{
g_strfreev
(
pixbuf_formats
);
}
/* The original image wasn't compatible. Scale it or convert file type. */
pixbuf
=
gdk_pixbuf_new_from_file
(
path
,
&
error
);
if
(
error
)
{
purple_debug_warning
(
"buddyicon"
,
"Could not open icon '%s' for "
"conversion: %s
\n
"
,
path
,
error
->
message
);
g_error_free
(
error
);
g_strfreev
(
prpl_formats
);
return
NULL
;
}
original
=
g_object_ref
(
G_OBJECT
(
pixbuf
));
new_width
=
orig_width
;
new_height
=
orig_height
;
/* Make sure the image is the correct dimensions */
if
(
spec
->
scale_rules
&
PURPLE_ICON_SCALE_SEND
&&
(
orig_width
<
spec
->
min_width
||
orig_width
>
spec
->
max_width
||
orig_height
<
spec
->
min_height
||
orig_height
>
spec
->
max_height
))
{
purple_buddy_icon_get_scale_size
(
spec
,
&
new_width
,
&
new_height
);
g_object_unref
(
G_OBJECT
(
pixbuf
));
pixbuf
=
gdk_pixbuf_scale_simple
(
original
,
new_width
,
new_height
,
GDK_INTERP_HYPER
);
}
scale_factor
=
1
;
do
{
for
(
i
=
0
;
prpl_formats
[
i
];
i
++
)
{
int
quality
=
100
;
do
{
const
char
*
key
=
NULL
;
const
char
*
value
=
NULL
;
gchar
tmp_buf
[
4
];
purple_debug_info
(
"buddyicon"
,
"Converting buddy icon to %s
\n
"
,
prpl_formats
[
i
]);
if
(
purple_strequal
(
prpl_formats
[
i
],
"png"
))
{
key
=
"compression"
;
value
=
"9"
;
}
else
if
(
purple_strequal
(
prpl_formats
[
i
],
"jpeg"
))
{
sprintf
(
tmp_buf
,
"%u"
,
quality
);
key
=
"quality"
;
value
=
tmp_buf
;
}
if
(
!
gdk_pixbuf_save_to_buffer
(
pixbuf
,
&
contents
,
&
length
,
prpl_formats
[
i
],
&
error
,
key
,
value
,
NULL
))
{
/* The NULL checking of error is necessary due to this bug:
* http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
purple_debug_warning
(
"buddyicon"
,
"Could not convert to %s: %s
\n
"
,
prpl_formats
[
i
],
(
error
&&
error
->
message
)
?
error
->
message
:
"Unknown error"
);
g_error_free
(
error
);
error
=
NULL
;
/* We couldn't convert to this image type. Try the next
image type. */
break
;
}
if
(
spec
->
max_filesize
==
0
||
length
<=
spec
->
max_filesize
)
{
/* We were able to save the image as this image type and
have it be within the size constraints. Great! Return
the image. */
purple_debug_info
(
"buddyicon"
,
"Converted image from "
"%dx%d to %dx%d, format=%s, quality=%u, "
"filesize=%zu
\n
"
,
orig_width
,
orig_height
,
new_width
,
new_height
,
prpl_formats
[
i
],
quality
,
length
);
if
(
len
)
*
len
=
length
;
g_strfreev
(
prpl_formats
);
g_object_unref
(
G_OBJECT
(
pixbuf
));
g_object_unref
(
G_OBJECT
(
original
));
return
contents
;
}
g_free
(
contents
);
if
(
!
purple_strequal
(
prpl_formats
[
i
],
"jpeg"
))
{
/* File size was too big and we can't lower the quality,
so skip to the next image type. */
break
;
}
/* File size was too big, but we're dealing with jpeg so try
lowering the quality. */
quality
-=
5
;
}
while
(
quality
>=
70
);
}
/* We couldn't save the image in any format that was below the max
file size. Maybe we can reduce the image dimensions? */
scale_factor
*=
0.8
;
new_width
=
orig_width
*
scale_factor
;
new_height
=
orig_height
*
scale_factor
;
g_object_unref
(
G_OBJECT
(
pixbuf
));
pixbuf
=
gdk_pixbuf_scale_simple
(
original
,
new_width
,
new_height
,
GDK_INTERP_HYPER
);
}
while
((
new_width
>
10
||
new_height
>
10
)
&&
new_width
>
spec
->
min_width
&&
new_height
>
spec
->
min_height
);
g_strfreev
(
prpl_formats
);
g_object_unref
(
G_OBJECT
(
pixbuf
));
g_object_unref
(
G_OBJECT
(
original
));
tmp
=
g_strdup_printf
(
_
(
"The file '%s' is too large for %s. Please try a smaller image.
\n
"
),
path
,
plugin
->
info
->
name
);
purple_notify_error
(
NULL
,
_
(
"Icon Error"
),
_
(
"Could not set icon"
),
tmp
);
g_free
(
tmp
);
return
NULL
;
}
void
pidgin_set_custom_buddy_icon
(
PurpleAccount
*
account
,
const
char
*
who
,
const
char
*
filename
)
{
PurpleBuddy
*
buddy
;
PurpleContact
*
contact
;
buddy
=
purple_find_buddy
(
account
,
who
);
if
(
!
buddy
)
{
purple_debug_info
(
"custom-icon"
,
"You can only set custom icon for someone in your buddylist.
\n
"
);
return
;
}
contact
=
purple_buddy_get_contact
(
buddy
);
purple_buddy_icons_node_set_custom_icon_from_file
((
PurpleBlistNode
*
)
contact
,
filename
);
}
char
*
pidgin_make_pretty_arrows
(
const
char
*
str
)
{
char
*
ret
;
char
**
split
=
g_strsplit
(
str
,
"->"
,
-1
);
ret
=
g_strjoinv
(
"
\342\207\250
"
,
split
);
g_strfreev
(
split
);
split
=
g_strsplit
(
ret
,
"<-"
,
-1
);
g_free
(
ret
);
ret
=
g_strjoinv
(
"
\342\207\246
"
,
split
);
g_strfreev
(
split
);
return
ret
;
}
void
pidgin_set_urgent
(
GtkWindow
*
window
,
gboolean
urgent
)
{
#if defined _WIN32
winpidgin_window_flash
(
window
,
urgent
);
#else
gtk_window_set_urgency_hint
(
window
,
urgent
);
#endif
}
static
GSList
*
minidialogs
=
NULL
;
static
void
*
pidgin_utils_get_handle
(
void
)
{
static
int
handle
;
return
&
handle
;
}
static
void
connection_signed_off_cb
(
PurpleConnection
*
gc
)
{
GSList
*
list
,
*
l_next
;
for
(
list
=
minidialogs
;
list
;
list
=
l_next
)
{
l_next
=
list
->
next
;
if
(
g_object_get_data
(
G_OBJECT
(
list
->
data
),
"gc"
)
==
gc
)
{
gtk_widget_destroy
(
GTK_WIDGET
(
list
->
data
));
}
}
}
static
void
alert_killed_cb
(
GtkWidget
*
widget
)
{
minidialogs
=
g_slist_remove
(
minidialogs
,
widget
);
}
struct
_old_button_clicked_cb_data
{
PidginUtilMiniDialogCallback
cb
;
gpointer
data
;
};
static
void
old_mini_dialog_button_clicked_cb
(
PidginMiniDialog
*
mini_dialog
,
GtkButton
*
button
,
gpointer
user_data
)
{
struct
_old_button_clicked_cb_data
*
data
=
user_data
;
data
->
cb
(
data
->
data
,
button
);
}
static
void
old_mini_dialog_destroy_cb
(
GtkWidget
*
dialog
,
GList
*
cb_datas
)
{
while
(
cb_datas
!=
NULL
)
{
g_free
(
cb_datas
->
data
);
cb_datas
=
g_list_delete_link
(
cb_datas
,
cb_datas
);
}
}
static
void
mini_dialog_init
(
PidginMiniDialog
*
mini_dialog
,
PurpleConnection
*
gc
,
void
*
user_data
,
va_list
args
)
{
const
char
*
button_text
;
GList
*
cb_datas
=
NULL
;
static
gboolean
first_call
=
TRUE
;
if
(
first_call
)
{
first_call
=
FALSE
;
purple_signal_connect
(
purple_connections_get_handle
(),
"signed-off"
,
pidgin_utils_get_handle
(),
PURPLE_CALLBACK
(
connection_signed_off_cb
),
NULL
);
}
g_object_set_data
(
G_OBJECT
(
mini_dialog
),
"gc"
,
gc
);
g_signal_connect
(
G_OBJECT
(
mini_dialog
),
"destroy"
,
G_CALLBACK
(
alert_killed_cb
),
NULL
);
while
((
button_text
=
va_arg
(
args
,
char
*
)))
{
struct
_old_button_clicked_cb_data
*
data
=
NULL
;
PidginMiniDialogCallback
wrapper_cb
=
NULL
;
PidginUtilMiniDialogCallback
callback
=
va_arg
(
args
,
PidginUtilMiniDialogCallback
);
if
(
callback
!=
NULL
)
{
data
=
g_new0
(
struct
_old_button_clicked_cb_data
,
1
);
data
->
cb
=
callback
;
data
->
data
=
user_data
;
wrapper_cb
=
old_mini_dialog_button_clicked_cb
;
}
pidgin_mini_dialog_add_button
(
mini_dialog
,
button_text
,
wrapper_cb
,
data
);
cb_datas
=
g_list_append
(
cb_datas
,
data
);
}
g_signal_connect
(
G_OBJECT
(
mini_dialog
),
"destroy"
,
G_CALLBACK
(
old_mini_dialog_destroy_cb
),
cb_datas
);
}
#define INIT_AND_RETURN_MINI_DIALOG(mini_dialog) \
va_list args; \
va_start(args, user_data); \
mini_dialog_init(mini_dialog, gc, user_data, args); \
va_end(args); \
return GTK_WIDGET(mini_dialog);
GtkWidget
*
pidgin_make_mini_dialog
(
PurpleConnection
*
gc
,
const
char
*
icon_name
,
const
char
*
primary
,
const
char
*
secondary
,
void
*
user_data
,
...)
{
PidginMiniDialog
*
mini_dialog
=
pidgin_mini_dialog_new
(
primary
,
secondary
,
icon_name
);
INIT_AND_RETURN_MINI_DIALOG
(
mini_dialog
);
}
GtkWidget
*
pidgin_make_mini_dialog_with_custom_icon
(
PurpleConnection
*
gc
,
GdkPixbuf
*
custom_icon
,
const
char
*
primary
,
const
char
*
secondary
,
void
*
user_data
,
...)
{
PidginMiniDialog
*
mini_dialog
=
pidgin_mini_dialog_new_with_custom_icon
(
primary
,
secondary
,
custom_icon
);
INIT_AND_RETURN_MINI_DIALOG
(
mini_dialog
);
}
/*
* "This is so dead sexy."
* "Two thumbs up."
* "Best movie of the year."
*
* This is the function that handles CTRL+F searching in the buddy list.
* It finds the top-most buddy/group/chat/whatever containing the
* entered string.
*
* It's somewhat ineffecient, because we strip all the HTML from the
* "name" column of the buddy list (because the GtkTreeModel does not
* contain the screen name in a non-markedup format). But the alternative
* is to add an extra column to the GtkTreeModel. And this function is
* used rarely, so it shouldn't matter TOO much.
*/
gboolean
pidgin_tree_view_search_equal_func
(
GtkTreeModel
*
model
,
gint
column
,
const
gchar
*
key
,
GtkTreeIter
*
iter
,
gpointer
data
)
{
gchar
*
enteredstring
;
gchar
*
tmp
;
gchar
*
withmarkup
;
gchar
*
nomarkup
;
gchar
*
normalized
;
gboolean
result
;
size_t
i
;
size_t
len
;
PangoLogAttr
*
log_attrs
;
gchar
*
word
;
if
(
g_ascii_strcasecmp
(
key
,
"Global Thermonuclear War"
)
==
0
)
{
purple_notify_info
(
NULL
,
"WOPR"
,
"Wouldn't you prefer a nice game of chess?"
,
NULL
);
return
FALSE
;
}
gtk_tree_model_get
(
model
,
iter
,
column
,
&
withmarkup
,
-1
);
if
(
withmarkup
==
NULL
)
/* This is probably a separator */
return
TRUE
;
tmp
=
g_utf8_normalize
(
key
,
-1
,
G_NORMALIZE_DEFAULT
);
enteredstring
=
g_utf8_casefold
(
tmp
,
-1
);
g_free
(
tmp
);
nomarkup
=
purple_markup_strip_html
(
withmarkup
);
tmp
=
g_utf8_normalize
(
nomarkup
,
-1
,
G_NORMALIZE_DEFAULT
);
g_free
(
nomarkup
);
normalized
=
g_utf8_casefold
(
tmp
,
-1
);
g_free
(
tmp
);
if
(
purple_str_has_prefix
(
normalized
,
enteredstring
))
{
g_free
(
withmarkup
);
g_free
(
enteredstring
);
g_free
(
normalized
);
return
FALSE
;
}
/* Use Pango to separate by words. */
len
=
g_utf8_strlen
(
normalized
,
-1
);
log_attrs
=
g_new
(
PangoLogAttr
,
len
+
1
);
pango_get_log_attrs
(
normalized
,
strlen
(
normalized
),
-1
,
NULL
,
log_attrs
,
len
+
1
);
word
=
normalized
;
result
=
TRUE
;
for
(
i
=
0
;
i
<
(
len
-
1
)
;
i
++
)
{
if
(
log_attrs
[
i
].
is_word_start
&&
purple_str_has_prefix
(
word
,
enteredstring
))
{
result
=
FALSE
;
break
;
}
word
=
g_utf8_next_char
(
word
);
}
g_free
(
log_attrs
);
/* The non-Pango version. */
#if 0
word = normalized;
result = TRUE;
while (word[0] != '\0')
{
gunichar c = g_utf8_get_char(word);
if (!g_unichar_isalnum(c))
{
word = g_utf8_find_next_char(word, NULL);
if (purple_str_has_prefix(word, enteredstring))
{
result = FALSE;
break;
}
}
else
word = g_utf8_find_next_char(word, NULL);
}
#endif
g_free
(
withmarkup
);
g_free
(
enteredstring
);
g_free
(
normalized
);
return
result
;
}
gboolean
pidgin_gdk_pixbuf_is_opaque
(
GdkPixbuf
*
pixbuf
)
{
int
height
,
rowstride
,
i
;
unsigned
char
*
pixels
;
unsigned
char
*
row
;
if
(
!
gdk_pixbuf_get_has_alpha
(
pixbuf
))
return
TRUE
;
height
=
gdk_pixbuf_get_height
(
pixbuf
);
rowstride
=
gdk_pixbuf_get_rowstride
(
pixbuf
);
pixels
=
gdk_pixbuf_get_pixels
(
pixbuf
);
row
=
pixels
;
for
(
i
=
3
;
i
<
rowstride
;
i
+=
4
)
{
if
(
row
[
i
]
<
0xfe
)
return
FALSE
;
}
for
(
i
=
1
;
i
<
height
-
1
;
i
++
)
{
row
=
pixels
+
(
i
*
rowstride
);
if
(
row
[
3
]
<
0xfe
||
row
[
rowstride
-
1
]
<
0xfe
)
{
return
FALSE
;
}
}
row
=
pixels
+
((
height
-
1
)
*
rowstride
);
for
(
i
=
3
;
i
<
rowstride
;
i
+=
4
)
{
if
(
row
[
i
]
<
0xfe
)
return
FALSE
;
}
return
TRUE
;
}
void
pidgin_gdk_pixbuf_make_round
(
GdkPixbuf
*
pixbuf
)
{
int
width
,
height
,
rowstride
;
guchar
*
pixels
;
if
(
!
gdk_pixbuf_get_has_alpha
(
pixbuf
))
return
;
width
=
gdk_pixbuf_get_width
(
pixbuf
);
height
=
gdk_pixbuf_get_height
(
pixbuf
);
rowstride
=
gdk_pixbuf_get_rowstride
(
pixbuf
);
pixels
=
gdk_pixbuf_get_pixels
(
pixbuf
);
if
(
width
<
6
||
height
<
6
)
return
;
/* Top left */
pixels
[
3
]
=
0
;
pixels
[
7
]
=
0x80
;
pixels
[
11
]
=
0xC0
;
pixels
[
rowstride
+
3
]
=
0x80
;
pixels
[
rowstride
*
2
+
3
]
=
0xC0
;
/* Top right */
pixels
[
width
*
4
-
1
]
=
0
;
pixels
[
width
*
4
-
5
]
=
0x80
;
pixels
[
width
*
4
-
9
]
=
0xC0
;
pixels
[
rowstride
+
(
width
*
4
)
-
1
]
=
0x80
;
pixels
[(
2
*
rowstride
)
+
(
width
*
4
)
-
1
]
=
0xC0
;
/* Bottom left */
pixels
[(
height
-
1
)
*
rowstride
+
3
]
=
0
;
pixels
[(
height
-
1
)
*
rowstride
+
7
]
=
0x80
;
pixels
[(
height
-
1
)
*
rowstride
+
11
]
=
0xC0
;
pixels
[(
height
-
2
)
*
rowstride
+
3
]
=
0x80
;
pixels
[(
height
-
3
)
*
rowstride
+
3
]
=
0xC0
;
/* Bottom right */
pixels
[
height
*
rowstride
-
1
]
=
0
;
pixels
[(
height
-
1
)
*
rowstride
-
1
]
=
0x80
;
pixels
[(
height
-
2
)
*
rowstride
-
1
]
=
0xC0
;
pixels
[
height
*
rowstride
-
5
]
=
0x80
;
pixels
[
height
*
rowstride
-
9
]
=
0xC0
;
}
const
char
*
pidgin_get_dim_grey_string
(
GtkWidget
*
widget
)
{
static
char
dim_grey_string
[
8
]
=
""
;
GtkStyle
*
style
;
if
(
!
widget
)
return
"dim grey"
;
style
=
gtk_widget_get_style
(
widget
);
if
(
!
style
)
return
"dim grey"
;
snprintf
(
dim_grey_string
,
sizeof
(
dim_grey_string
),
"#%02x%02x%02x"
,
style
->
text_aa
[
GTK_STATE_NORMAL
].
red
>>
8
,
style
->
text_aa
[
GTK_STATE_NORMAL
].
green
>>
8
,
style
->
text_aa
[
GTK_STATE_NORMAL
].
blue
>>
8
);
return
dim_grey_string
;
}
static
void
combo_box_changed_cb
(
GtkComboBox
*
combo_box
,
GtkEntry
*
entry
)
{
char
*
text
=
gtk_combo_box_get_active_text
(
combo_box
);
gtk_entry_set_text
(
entry
,
text
?
text
:
""
);
g_free
(
text
);
}
static
gboolean
entry_key_pressed_cb
(
GtkWidget
*
entry
,
GdkEventKey
*
key
,
GtkComboBox
*
combo
)
{
if
(
key
->
keyval
==
GDK_Down
||
key
->
keyval
==
GDK_Up
)
{
gtk_combo_box_popup
(
combo
);
return
TRUE
;
}
return
FALSE
;
}
GtkWidget
*
pidgin_text_combo_box_entry_new
(
const
char
*
default_item
,
GList
*
items
)
{
GtkComboBox
*
ret
=
NULL
;
GtkWidget
*
the_entry
=
NULL
;
ret
=
GTK_COMBO_BOX
(
gtk_combo_box_entry_new_text
());
the_entry
=
gtk_entry_new
();
gtk_container_add
(
GTK_CONTAINER
(
ret
),
the_entry
);
if
(
default_item
)
gtk_entry_set_text
(
GTK_ENTRY
(
the_entry
),
default_item
);
for
(;
items
!=
NULL
;
items
=
items
->
next
)
{
char
*
text
=
items
->
data
;
if
(
text
&&
*
text
)
gtk_combo_box_append_text
(
ret
,
text
);
}
g_signal_connect
(
G_OBJECT
(
ret
),
"changed"
,
(
GCallback
)
combo_box_changed_cb
,
the_entry
);
g_signal_connect_after
(
G_OBJECT
(
the_entry
),
"key-press-event"
,
G_CALLBACK
(
entry_key_pressed_cb
),
ret
);
return
GTK_WIDGET
(
ret
);
}
const
char
*
pidgin_text_combo_box_entry_get_text
(
GtkWidget
*
widget
)
{
return
gtk_entry_get_text
(
GTK_ENTRY
(
GTK_BIN
((
widget
))
->
child
));
}
void
pidgin_text_combo_box_entry_set_text
(
GtkWidget
*
widget
,
const
char
*
text
)
{
gtk_entry_set_text
(
GTK_ENTRY
(
GTK_BIN
((
widget
))
->
child
),
(
text
));
}
GtkWidget
*
pidgin_add_widget_to_vbox
(
GtkBox
*
vbox
,
const
char
*
widget_label
,
GtkSizeGroup
*
sg
,
GtkWidget
*
widget
,
gboolean
expand
,
GtkWidget
**
p_label
)
{
GtkWidget
*
hbox
;
GtkWidget
*
label
=
NULL
;
if
(
widget_label
)
{
hbox
=
gtk_hbox_new
(
FALSE
,
5
);
gtk_widget_show
(
hbox
);
gtk_box_pack_start
(
vbox
,
hbox
,
FALSE
,
FALSE
,
0
);
label
=
gtk_label_new_with_mnemonic
(
widget_label
);
gtk_widget_show
(
label
);
if
(
sg
)
{
gtk_misc_set_alignment
(
GTK_MISC
(
label
),
0
,
0.5
);
gtk_size_group_add_widget
(
sg
,
label
);
}
gtk_box_pack_start
(
GTK_BOX
(
hbox
),
label
,
FALSE
,
FALSE
,
0
);
}
else
{
hbox
=
GTK_WIDGET
(
vbox
);
}
gtk_widget_show
(
widget
);
gtk_box_pack_start
(
GTK_BOX
(
hbox
),
widget
,
expand
,
TRUE
,
0
);
if
(
label
)
{
gtk_label_set_mnemonic_widget
(
GTK_LABEL
(
label
),
widget
);
pidgin_set_accessible_label
(
widget
,
label
);
}
if
(
p_label
)
(
*
p_label
)
=
label
;
return
hbox
;
}
gboolean
pidgin_auto_parent_window
(
GtkWidget
*
widget
)
{
#if 0
/* This looks at the most recent window that received focus, and makes
* that the parent window. */
#ifndef _WIN32
static GdkAtom _WindowTime = GDK_NONE;
static GdkAtom _Cardinal = GDK_NONE;
GList *windows = NULL;
GtkWidget *parent = NULL;
time_t window_time = 0;
windows = gtk_window_list_toplevels();
if (_WindowTime == GDK_NONE) {
_WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
}
if (_Cardinal == GDK_NONE) {
_Cardinal = gdk_atom_intern("CARDINAL", FALSE);
}
while (windows) {
GtkWidget *window = windows->data;
guchar *data = NULL;
int al = 0;
time_t value;
windows = g_list_delete_link(windows, windows);
if (window == widget ||
!GTK_WIDGET_VISIBLE(window))
continue;
if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
NULL, NULL, &al, &data))
continue;
value = *(time_t *)data;
if (window_time < value) {
window_time = value;
parent = window;
}
g_free(data);
}
if (windows)
g_list_free(windows);
if (parent) {
if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
/* The window is in focus, and the new window was not triggered by a keypress/click
* event. So do not set it transient, to avoid focus stealing and all that.
*/
return FALSE;
}
gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
return TRUE;
}
return FALSE;
#endif
#else
/* This finds the currently active window and makes that the parent window. */
GList
*
windows
=
NULL
;
GtkWidget
*
parent
=
NULL
;
GdkEvent
*
event
=
gtk_get_current_event
();
GdkWindow
*
menu
=
NULL
;
if
(
event
==
NULL
)
/* The window was not triggered by a user action. */
return
FALSE
;
/* We need to special case events from a popup menu. */
if
(
event
->
type
==
GDK_BUTTON_RELEASE
)
{
/* XXX: Neither of the following works:
menu = event->button.window;
menu = gdk_window_get_parent(event->button.window);
menu = gdk_window_get_toplevel(event->button.window);
*/
}
else
if
(
event
->
type
==
GDK_KEY_PRESS
)
menu
=
event
->
key
.
window
;
windows
=
gtk_window_list_toplevels
();
while
(
windows
)
{
GtkWidget
*
window
=
windows
->
data
;
windows
=
g_list_delete_link
(
windows
,
windows
);
if
(
window
==
widget
||
!
GTK_WIDGET_VISIBLE
(
window
))
{
continue
;
}
if
(
gtk_window_has_toplevel_focus
(
GTK_WINDOW
(
window
))
||
(
menu
&&
menu
==
window
->
window
))
{
parent
=
window
;
break
;
}
}
if
(
windows
)
g_list_free
(
windows
);
if
(
parent
)
{
gtk_window_set_transient_for
(
GTK_WINDOW
(
widget
),
GTK_WINDOW
(
parent
));
return
TRUE
;
}
return
FALSE
;
#endif
}
static
GObject
*
pidgin_pixbuf_from_data_helper
(
const
guchar
*
buf
,
gsize
count
,
gboolean
animated
)
{
GObject
*
pixbuf
;
GdkPixbufLoader
*
loader
;
GError
*
error
=
NULL
;
loader
=
gdk_pixbuf_loader_new
();
if
(
!
gdk_pixbuf_loader_write
(
loader
,
buf
,
count
,
&
error
)
||
error
)
{
purple_debug_warning
(
"gtkutils"
,
"gdk_pixbuf_loader_write() "
"failed with size=%zu: %s
\n
"
,
count
,
error
?
error
->
message
:
"(no error message)"
);
if
(
error
)
g_error_free
(
error
);
g_object_unref
(
G_OBJECT
(
loader
));
return
NULL
;
}
if
(
!
gdk_pixbuf_loader_close
(
loader
,
&
error
)
||
error
)
{
purple_debug_warning
(
"gtkutils"
,
"gdk_pixbuf_loader_close() "
"failed for image of size %zu: %s
\n
"
,
count
,
error
?
error
->
message
:
"(no error message)"
);
if
(
error
)
g_error_free
(
error
);
g_object_unref
(
G_OBJECT
(
loader
));
return
NULL
;
}
if
(
animated
)
pixbuf
=
G_OBJECT
(
gdk_pixbuf_loader_get_animation
(
loader
));
else
pixbuf
=
G_OBJECT
(
gdk_pixbuf_loader_get_pixbuf
(
loader
));
if
(
!
pixbuf
)
{
purple_debug_warning
(
"gtkutils"
,
"%s() returned NULL for image "
"of size %zu
\n
"
,
animated
?
"gdk_pixbuf_loader_get_animation"
:
"gdk_pixbuf_loader_get_pixbuf"
,
count
);
g_object_unref
(
G_OBJECT
(
loader
));
return
NULL
;
}
g_object_ref
(
pixbuf
);
g_object_unref
(
G_OBJECT
(
loader
));
return
pixbuf
;
}
GdkPixbuf
*
pidgin_pixbuf_from_data
(
const
guchar
*
buf
,
gsize
count
)
{
return
GDK_PIXBUF
(
pidgin_pixbuf_from_data_helper
(
buf
,
count
,
FALSE
));
}
GdkPixbufAnimation
*
pidgin_pixbuf_anim_from_data
(
const
guchar
*
buf
,
gsize
count
)
{
return
GDK_PIXBUF_ANIMATION
(
pidgin_pixbuf_from_data_helper
(
buf
,
count
,
TRUE
));
}
GdkPixbuf
*
pidgin_pixbuf_from_imgstore
(
PurpleStoredImage
*
image
)
{
return
pidgin_pixbuf_from_data
(
purple_imgstore_get_data
(
image
),
purple_imgstore_get_size
(
image
));
}
GdkPixbuf
*
pidgin_pixbuf_new_from_file
(
const
gchar
*
filename
)
{
GdkPixbuf
*
pixbuf
;
GError
*
error
=
NULL
;
pixbuf
=
gdk_pixbuf_new_from_file
(
filename
,
&
error
);
if
(
!
pixbuf
||
error
)
{
purple_debug_warning
(
"gtkutils"
,
"gdk_pixbuf_new_from_file() "
"returned %s for file %s: %s
\n
"
,
pixbuf
?
"something"
:
"nothing"
,
filename
,
error
?
error
->
message
:
"(no error message)"
);
if
(
error
)
g_error_free
(
error
);
if
(
pixbuf
)
g_object_unref
(
G_OBJECT
(
pixbuf
));
return
NULL
;
}
return
pixbuf
;
}
GdkPixbuf
*
pidgin_pixbuf_new_from_file_at_size
(
const
char
*
filename
,
int
width
,
int
height
)
{
GdkPixbuf
*
pixbuf
;
GError
*
error
=
NULL
;
pixbuf
=
gdk_pixbuf_new_from_file_at_size
(
filename
,
width
,
height
,
&
error
);
if
(
!
pixbuf
||
error
)
{
purple_debug_warning
(
"gtkutils"
,
"gdk_pixbuf_new_from_file_at_size() "
"returned %s for file %s: %s
\n
"
,
pixbuf
?
"something"
:
"nothing"
,
filename
,
error
?
error
->
message
:
"(no error message)"
);
if
(
error
)
g_error_free
(
error
);
if
(
pixbuf
)
g_object_unref
(
G_OBJECT
(
pixbuf
));
return
NULL
;
}
return
pixbuf
;
}
GdkPixbuf
*
pidgin_pixbuf_new_from_file_at_scale
(
const
char
*
filename
,
int
width
,
int
height
,
gboolean
preserve_aspect_ratio
)
{
GdkPixbuf
*
pixbuf
;
GError
*
error
=
NULL
;
pixbuf
=
gdk_pixbuf_new_from_file_at_scale
(
filename
,
width
,
height
,
preserve_aspect_ratio
,
&
error
);
if
(
!
pixbuf
||
error
)
{
purple_debug_warning
(
"gtkutils"
,
"gdk_pixbuf_new_from_file_at_scale() "
"returned %s for file %s: %s
\n
"
,
pixbuf
?
"something"
:
"nothing"
,
filename
,
error
?
error
->
message
:
"(no error message)"
);
if
(
error
)
g_error_free
(
error
);
if
(
pixbuf
)
g_object_unref
(
G_OBJECT
(
pixbuf
));
return
NULL
;
}
return
pixbuf
;
}
static
void
url_copy
(
GtkWidget
*
w
,
gchar
*
url
)
{
GtkClipboard
*
clipboard
;
clipboard
=
gtk_widget_get_clipboard
(
w
,
GDK_SELECTION_PRIMARY
);
gtk_clipboard_set_text
(
clipboard
,
url
,
-1
);
clipboard
=
gtk_widget_get_clipboard
(
w
,
GDK_SELECTION_CLIPBOARD
);
gtk_clipboard_set_text
(
clipboard
,
url
,
-1
);
}
static
gboolean
link_context_menu
(
GtkIMHtml
*
imhtml
,
GtkIMHtmlLink
*
link
,
GtkWidget
*
menu
)
{
GtkWidget
*
img
,
*
item
;
const
char
*
url
;
url
=
gtk_imhtml_link_get_url
(
link
);
/* Open Link */
img
=
gtk_image_new_from_stock
(
GTK_STOCK_JUMP_TO
,
GTK_ICON_SIZE_MENU
);
item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Open Link"
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
item
),
img
);
g_signal_connect_swapped
(
G_OBJECT
(
item
),
"activate"
,
G_CALLBACK
(
gtk_imhtml_link_activate
),
link
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
/* Copy Link Location */
img
=
gtk_image_new_from_stock
(
GTK_STOCK_COPY
,
GTK_ICON_SIZE_MENU
);
item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Copy Link Location"
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
item
),
img
);
g_signal_connect
(
G_OBJECT
(
item
),
"activate"
,
G_CALLBACK
(
url_copy
),
(
gpointer
)
url
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
return
TRUE
;
}
static
gboolean
copy_email_address
(
GtkIMHtml
*
imhtml
,
GtkIMHtmlLink
*
link
,
GtkWidget
*
menu
)
{
GtkWidget
*
img
,
*
item
;
const
char
*
text
;
char
*
address
;
#define MAILTOSIZE (sizeof("mailto:") - 1)
text
=
gtk_imhtml_link_get_url
(
link
);
g_return_val_if_fail
(
text
&&
strlen
(
text
)
>
MAILTOSIZE
,
FALSE
);
address
=
(
char
*
)
text
+
MAILTOSIZE
;
/* Copy Email Address */
img
=
gtk_image_new_from_stock
(
GTK_STOCK_COPY
,
GTK_ICON_SIZE_MENU
);
item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Copy Email Address"
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
item
),
img
);
g_signal_connect
(
G_OBJECT
(
item
),
"activate"
,
G_CALLBACK
(
url_copy
),
address
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
return
TRUE
;
}
/**
* @param filename The path to a file. Specifically this is the link target
* from a link in an IM window with the leading "file://" removed.
*/
static
void
open_file
(
GtkIMHtml
*
imhtml
,
const
char
*
filename
)
{
/* Copied from gtkft.c:open_button_cb */
#ifdef _WIN32
/* If using Win32... */
int
code
;
/* Escape URI by replacing double-quote with 2 double-quotes. */
gchar
*
escaped
=
purple_strreplace
(
filename
,
"
\"
"
,
"
\"\"
"
);
gchar
*
param
=
g_strconcat
(
"/select,
\"
"
,
escaped
,
"
\"
"
,
NULL
);
wchar_t
*
wc_param
=
g_utf8_to_utf16
(
param
,
-1
,
NULL
,
NULL
,
NULL
);
/* TODO: Better to use SHOpenFolderAndSelectItems()? */
code
=
(
int
)
ShellExecuteW
(
NULL
,
L
"OPEN"
,
L
"explorer.exe"
,
wc_param
,
NULL
,
SW_NORMAL
);
g_free
(
wc_param
);
g_free
(
param
);
g_free
(
escaped
);
if
(
code
==
SE_ERR_ASSOCINCOMPLETE
||
code
==
SE_ERR_NOASSOC
)
{
purple_notify_error
(
imhtml
,
NULL
,
_
(
"There is no application configured to open this type of file."
),
NULL
);
}
else
if
(
code
<
32
)
{
purple_notify_error
(
imhtml
,
NULL
,
_
(
"An error occurred while opening the file."
),
NULL
);
purple_debug_warning
(
"gtkutils"
,
"filename: %s; code: %d
\n
"
,
filename
,
code
);
}
#else
char
*
command
=
NULL
;
char
*
tmp
=
NULL
;
GError
*
error
=
NULL
;
if
(
purple_running_gnome
())
{
char
*
escaped
=
g_shell_quote
(
filename
);
command
=
g_strdup_printf
(
"gnome-open %s"
,
escaped
);
g_free
(
escaped
);
}
else
if
(
purple_running_kde
())
{
char
*
escaped
=
g_shell_quote
(
filename
);
if
(
purple_str_has_suffix
(
filename
,
".desktop"
))
command
=
g_strdup_printf
(
"kfmclient openURL %s 'text/plain'"
,
escaped
);
else
command
=
g_strdup_printf
(
"kfmclient openURL %s"
,
escaped
);
g_free
(
escaped
);
}
else
{
purple_notify_uri
(
NULL
,
filename
);
return
;
}
if
(
purple_program_is_valid
(
command
))
{
gint
exit_status
;
if
(
!
g_spawn_command_line_sync
(
command
,
NULL
,
NULL
,
&
exit_status
,
&
error
))
{
tmp
=
g_strdup_printf
(
_
(
"Error launching %s: %s"
),
filename
,
error
->
message
);
purple_notify_error
(
imhtml
,
NULL
,
_
(
"Unable to open file."
),
tmp
);
g_free
(
tmp
);
g_error_free
(
error
);
}
if
(
exit_status
!=
0
)
{
char
*
primary
=
g_strdup_printf
(
_
(
"Error running %s"
),
command
);
char
*
secondary
=
g_strdup_printf
(
_
(
"Process returned error code %d"
),
exit_status
);
purple_notify_error
(
imhtml
,
NULL
,
primary
,
secondary
);
g_free
(
tmp
);
}
}
#endif
}
#define FILELINKSIZE (sizeof("file:
//") - 1)
static
gboolean
file_clicked_cb
(
GtkIMHtml
*
imhtml
,
GtkIMHtmlLink
*
link
)
{
/* Strip "file://" from the URI. */
const
char
*
filename
=
gtk_imhtml_link_get_url
(
link
)
+
FILELINKSIZE
;
open_file
(
imhtml
,
filename
);
return
TRUE
;
}
static
gboolean
open_containing_cb
(
GtkIMHtml
*
imhtml
,
const
char
*
url
)
{
char
*
dir
=
g_path_get_dirname
(
url
+
FILELINKSIZE
);
open_file
(
imhtml
,
dir
);
g_free
(
dir
);
return
TRUE
;
}
static
gboolean
file_context_menu
(
GtkIMHtml
*
imhtml
,
GtkIMHtmlLink
*
link
,
GtkWidget
*
menu
)
{
GtkWidget
*
img
,
*
item
;
const
char
*
url
;
url
=
gtk_imhtml_link_get_url
(
link
);
/* Open File */
img
=
gtk_image_new_from_stock
(
GTK_STOCK_JUMP_TO
,
GTK_ICON_SIZE_MENU
);
item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Open File"
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
item
),
img
);
g_signal_connect_swapped
(
G_OBJECT
(
item
),
"activate"
,
G_CALLBACK
(
gtk_imhtml_link_activate
),
link
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
/* Open Containing Directory */
img
=
gtk_image_new_from_stock
(
GTK_STOCK_DIRECTORY
,
GTK_ICON_SIZE_MENU
);
item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"Open _Containing Directory"
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
item
),
img
);
g_signal_connect
(
G_OBJECT
(
item
),
"activate"
,
G_CALLBACK
(
open_containing_cb
),
(
gpointer
)
url
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
return
TRUE
;
}
#define AUDIOLINKSIZE (sizeof("audio:
//") - 1)
static
gboolean
audio_clicked_cb
(
GtkIMHtml
*
imhtml
,
GtkIMHtmlLink
*
link
)
{
const
char
*
uri
;
PidginConversation
*
conv
=
g_object_get_data
(
G_OBJECT
(
imhtml
),
"gtkconv"
);
if
(
!
conv
)
/* no playback in debug window */
return
TRUE
;
uri
=
gtk_imhtml_link_get_url
(
link
)
+
AUDIOLINKSIZE
;
purple_sound_play_file
(
uri
,
NULL
);
return
TRUE
;
}
static
void
savefile_write_cb
(
gpointer
user_data
,
char
*
file
)
{
char
*
temp_file
=
user_data
;
gchar
*
contents
;
gsize
length
;
GError
*
error
=
NULL
;
if
(
!
g_file_get_contents
(
temp_file
,
&
contents
,
&
length
,
&
error
))
{
purple_debug_error
(
"gtkutils"
,
"Unable to read contents of %s: %s
\n
"
,
temp_file
,
error
->
message
);
g_error_free
(
error
);
return
;
}
if
(
!
purple_util_write_data_to_file_absolute
(
file
,
contents
,
length
))
{
purple_debug_error
(
"gtkutils"
,
"Unable to write contents to %s
\n
"
,
file
);
}
}
static
gboolean
save_file_cb
(
GtkWidget
*
item
,
const
char
*
url
)
{
PidginConversation
*
conv
=
g_object_get_data
(
G_OBJECT
(
item
),
"gtkconv"
);
if
(
!
conv
)
return
TRUE
;
purple_request_file
(
conv
->
active_conv
,
_
(
"Save File"
),
NULL
,
TRUE
,
G_CALLBACK
(
savefile_write_cb
),
NULL
,
conv
->
active_conv
->
account
,
NULL
,
conv
->
active_conv
,
(
void
*
)
url
);
return
TRUE
;
}
static
gboolean
audio_context_menu
(
GtkIMHtml
*
imhtml
,
GtkIMHtmlLink
*
link
,
GtkWidget
*
menu
)
{
GtkWidget
*
img
,
*
item
;
const
char
*
url
;
PidginConversation
*
conv
=
g_object_get_data
(
G_OBJECT
(
imhtml
),
"gtkconv"
);
if
(
!
conv
)
/* No menu in debug window */
return
TRUE
;
url
=
gtk_imhtml_link_get_url
(
link
);
/* Play Sound */
img
=
gtk_image_new_from_stock
(
GTK_STOCK_MEDIA_PLAY
,
GTK_ICON_SIZE_MENU
);
item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Play Sound"
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
item
),
img
);
g_signal_connect_swapped
(
G_OBJECT
(
item
),
"activate"
,
G_CALLBACK
(
gtk_imhtml_link_activate
),
link
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
/* Save File */
img
=
gtk_image_new_from_stock
(
GTK_STOCK_SAVE
,
GTK_ICON_SIZE_MENU
);
item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Save File"
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
item
),
img
);
g_signal_connect
(
G_OBJECT
(
item
),
"activate"
,
G_CALLBACK
(
save_file_cb
),
(
gpointer
)(
url
+
AUDIOLINKSIZE
));
g_object_set_data
(
G_OBJECT
(
item
),
"gtkconv"
,
conv
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
return
TRUE
;
}
/* XXX: The following two functions are for demonstration purposes only! */
static
gboolean
open_dialog
(
GtkIMHtml
*
imhtml
,
GtkIMHtmlLink
*
link
)
{
const
char
*
url
;
const
char
*
str
;
url
=
gtk_imhtml_link_get_url
(
link
);
if
(
!
url
||
strlen
(
url
)
<
sizeof
(
"open://"
))
return
FALSE
;
str
=
url
+
sizeof
(
"open://"
)
-
1
;
if
(
purple_strequal
(
str
,
"accounts"
))
pidgin_accounts_window_show
();
else
if
(
purple_strequal
(
str
,
"prefs"
))
pidgin_prefs_show
();
else
return
FALSE
;
return
TRUE
;
}
static
gboolean
dummy
(
GtkIMHtml
*
imhtml
,
GtkIMHtmlLink
*
link
,
GtkWidget
*
menu
)
{
return
TRUE
;
}
static
gboolean
register_gnome_url_handlers
(
void
)
{
char
*
tmp
;
char
*
err
;
char
*
c
;
char
*
start
;
tmp
=
g_find_program_in_path
(
"gconftool-2"
);
if
(
tmp
==
NULL
)
return
FALSE
;
g_free
(
tmp
);
tmp
=
NULL
;
if
(
!
g_spawn_command_line_sync
(
"gconftool-2 --all-dirs /desktop/gnome/url-handlers"
,
&
tmp
,
&
err
,
NULL
,
NULL
))
{
g_free
(
tmp
);
g_free
(
err
);
g_return_val_if_reached
(
FALSE
);
}
g_free
(
err
);
err
=
NULL
;
for
(
c
=
start
=
tmp
;
*
c
;
c
++
)
{
/* Skip leading spaces. */
if
(
c
==
start
&&
*
c
==
' '
)
start
=
c
+
1
;
else
if
(
*
c
==
'\n'
)
{
*
c
=
'\0'
;
if
(
g_str_has_prefix
(
start
,
"/desktop/gnome/url-handlers/"
))
{
char
*
cmd
;
char
*
tmp2
=
NULL
;
char
*
protocol
;
/* If there is an enabled boolean, honor it. */
cmd
=
g_strdup_printf
(
"gconftool-2 -g %s/enabled"
,
start
);
if
(
g_spawn_command_line_sync
(
cmd
,
&
tmp2
,
&
err
,
NULL
,
NULL
))
{
g_free
(
err
);
err
=
NULL
;
if
(
purple_strequal
(
tmp2
,
"false
\n
"
))
{
g_free
(
tmp2
);
g_free
(
cmd
);
start
=
c
+
1
;
continue
;
}
}
g_free
(
cmd
);
g_free
(
tmp2
);
start
+=
sizeof
(
"/desktop/gnome/url-handlers/"
)
-
1
;
protocol
=
g_strdup_printf
(
"%s:"
,
start
);
registered_url_handlers
=
g_slist_prepend
(
registered_url_handlers
,
protocol
);
gtk_imhtml_class_register_protocol
(
protocol
,
url_clicked_cb
,
link_context_menu
);
}
start
=
c
+
1
;
}
}
g_free
(
tmp
);
return
(
registered_url_handlers
!=
NULL
);
}
#ifdef _WIN32
static
void
winpidgin_register_win32_url_handlers
(
void
)
{
int
idx
=
0
;
LONG
ret
=
ERROR_SUCCESS
;
do
{
DWORD
nameSize
=
256
;
wchar_t
start
[
256
];
ret
=
RegEnumKeyExW
(
HKEY_CLASSES_ROOT
,
idx
++
,
start
,
&
nameSize
,
NULL
,
NULL
,
NULL
,
NULL
);
if
(
ret
==
ERROR_SUCCESS
)
{
HKEY
reg_key
=
NULL
;
ret
=
RegOpenKeyExW
(
HKEY_CLASSES_ROOT
,
start
,
0
,
KEY_READ
,
&
reg_key
);
if
(
ret
==
ERROR_SUCCESS
)
{
ret
=
RegQueryValueExW
(
reg_key
,
L
"URL Protocol"
,
NULL
,
NULL
,
NULL
,
NULL
);
if
(
ret
==
ERROR_SUCCESS
)
{
gchar
*
utf8
=
g_utf16_to_utf8
(
start
,
-1
,
NULL
,
NULL
,
NULL
);
gchar
*
protocol
=
g_strdup_printf
(
"%s:"
,
utf8
);
g_free
(
utf8
);
registered_url_handlers
=
g_slist_prepend
(
registered_url_handlers
,
protocol
);
/* We still pass everything to the "http" "open" handler for security reasons */
gtk_imhtml_class_register_protocol
(
protocol
,
url_clicked_cb
,
link_context_menu
);
}
RegCloseKey
(
reg_key
);
}
ret
=
ERROR_SUCCESS
;
}
}
while
(
ret
==
ERROR_SUCCESS
);
if
(
ret
!=
ERROR_NO_MORE_ITEMS
)
purple_debug_error
(
"winpidgin"
,
"Error iterating HKEY_CLASSES_ROOT subkeys: %ld
\n
"
,
ret
);
}
#endif
GtkWidget
*
pidgin_make_scrollable
(
GtkWidget
*
child
,
GtkPolicyType
hscrollbar_policy
,
GtkPolicyType
vscrollbar_policy
,
GtkShadowType
shadow_type
,
int
width
,
int
height
)
{
GtkWidget
*
sw
=
gtk_scrolled_window_new
(
NULL
,
NULL
);
if
(
G_LIKELY
(
sw
))
{
gtk_widget_show
(
sw
);
gtk_scrolled_window_set_policy
(
GTK_SCROLLED_WINDOW
(
sw
),
hscrollbar_policy
,
vscrollbar_policy
);
gtk_scrolled_window_set_shadow_type
(
GTK_SCROLLED_WINDOW
(
sw
),
shadow_type
);
if
(
width
!=
-1
||
height
!=
-1
)
gtk_widget_set_size_request
(
sw
,
width
,
height
);
if
(
child
)
{
if
(
GTK_WIDGET_GET_CLASS
(
child
)
->
set_scroll_adjustments_signal
)
gtk_container_add
(
GTK_CONTAINER
(
sw
),
child
);
else
gtk_scrolled_window_add_with_viewport
(
GTK_SCROLLED_WINDOW
(
sw
),
child
);
}
return
sw
;
}
return
child
;
}
void
pidgin_utils_init
(
void
)
{
gtk_imhtml_class_register_protocol
(
"http://"
,
url_clicked_cb
,
link_context_menu
);
gtk_imhtml_class_register_protocol
(
"https://"
,
url_clicked_cb
,
link_context_menu
);
gtk_imhtml_class_register_protocol
(
"ftp://"
,
url_clicked_cb
,
link_context_menu
);
gtk_imhtml_class_register_protocol
(
"gopher://"
,
url_clicked_cb
,
link_context_menu
);
gtk_imhtml_class_register_protocol
(
"mailto:"
,
url_clicked_cb
,
copy_email_address
);
gtk_imhtml_class_register_protocol
(
"file://"
,
file_clicked_cb
,
file_context_menu
);
gtk_imhtml_class_register_protocol
(
"audio://"
,
audio_clicked_cb
,
audio_context_menu
);
/* Example custom URL handler. */
gtk_imhtml_class_register_protocol
(
"open://"
,
open_dialog
,
dummy
);
/* If we're under GNOME, try registering the system URL handlers. */
if
(
purple_running_gnome
())
register_gnome_url_handlers
();
/* Used to make small buttons */
gtk_rc_parse_string
(
"style
\"
pidgin-small-close-button
\"\n
"
"{
\n
"
"GtkWidget::focus-padding = 0
\n
"
"GtkWidget::focus-line-width = 0
\n
"
"xthickness = 0
\n
"
"ythickness = 0
\n
"
"GtkContainer::border-width = 0
\n
"
"GtkButton::inner-border = {0, 0, 0, 0}
\n
"
"GtkButton::default-border = {0, 0, 0, 0}
\n
"
"}
\n
"
"widget
\"
*.pidgin-small-close-button
\"
style
\"
pidgin-small-close-button
\"
"
);
#ifdef _WIN32
winpidgin_register_win32_url_handlers
();
#endif
}
void
pidgin_utils_uninit
(
void
)
{
gtk_imhtml_class_register_protocol
(
"open://"
,
NULL
,
NULL
);
/* If we have GNOME handlers registered, unregister them. */
if
(
registered_url_handlers
)
{
GSList
*
l
;
for
(
l
=
registered_url_handlers
;
l
;
l
=
l
->
next
)
{
gtk_imhtml_class_register_protocol
((
char
*
)
l
->
data
,
NULL
,
NULL
);
g_free
(
l
->
data
);
}
g_slist_free
(
registered_url_handlers
);
registered_url_handlers
=
NULL
;
return
;
}
gtk_imhtml_class_register_protocol
(
"audio://"
,
NULL
,
NULL
);
gtk_imhtml_class_register_protocol
(
"file://"
,
NULL
,
NULL
);
gtk_imhtml_class_register_protocol
(
"http://"
,
NULL
,
NULL
);
gtk_imhtml_class_register_protocol
(
"https://"
,
NULL
,
NULL
);
gtk_imhtml_class_register_protocol
(
"ftp://"
,
NULL
,
NULL
);
gtk_imhtml_class_register_protocol
(
"mailto:"
,
NULL
,
NULL
);
gtk_imhtml_class_register_protocol
(
"gopher://"
,
NULL
,
NULL
);
}