pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Clean changed translations
release-2.x.y
2020-06-09, Pidgin Translators
f4697fc7e8f7
Clean changed translations
/**
* @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
;
}
/* Special case for AIM and ICQ */
if
(
account
==
NULL
&&
(
purple_strequal
(
protocol
,
"aim"
)
||
purple_strequal
(
protocol
,
"icq"
)))
{
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
,
"aim"
)
||
purple_strequal
(
protoname
,
"icq"
))
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
);
}