pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Remove the usage of Stock items from gtksavedstatues
2021-08-12, Gary Kramlich
f8cb01c54b84
Remove the usage of Stock items from gtksavedstatues
Testing Done:
Verified the saved statuses dialog looked the same.
Reviewed at https://reviews.imfreedom.org/r/889/
/* 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
*/
#ifdef HAVE_CONFIG_H
#
include
<config.h>
#endif
#include
<errno.h>
#include
<glib/gi18n-lib.h>
#include
<glib/gstdio.h>
#include
<purple.h>
#ifdef _WIN32
# undef small
#
include
<shellapi.h>
#endif
/*_WIN32*/
#include
<gdk/gdkkeysyms.h>
#include
<talkatu.h>
#include
"gtkaccount.h"
#include
"gtkconv.h"
#include
"gtkdialogs.h"
#include
"gtkrequest.h"
#include
"gtkutils.h"
#include
"minidialog.h"
#include
"pidgincore.h"
#include
"pidginstock.h"
/******************************************************************************
* Enums
*****************************************************************************/
enum
{
AOP_ICON_COLUMN
,
AOP_NAME_COLUMN
,
AOP_DATA_COLUMN
,
AOP_COLUMN_COUNT
};
enum
{
DND_FILE_TRANSFER
,
DND_IM_IMAGE
,
DND_BUDDY_ICON
};
enum
{
COMPLETION_DISPLAYED_COLUMN
,
/* displayed completion value */
COMPLETION_BUDDY_COLUMN
,
/* buddy name */
COMPLETION_NORMALIZED_COLUMN
,
/* UTF-8 normalized & casefolded buddy name */
COMPLETION_COMPARISON_COLUMN
,
/* UTF-8 normalized & casefolded value for comparison */
COMPLETION_ACCOUNT_COLUMN
,
/* account */
COMPLETION_COLUMN_COUNT
};
/******************************************************************************
* Structs
*****************************************************************************/
typedef
struct
{
GtkTreeModel
*
model
;
gint
default_item
;
}
AopMenu
;
typedef
struct
{
char
*
filename
;
PurpleAccount
*
account
;
char
*
who
;
}
_DndData
;
typedef
struct
{
GtkWidget
*
entry
;
GtkWidget
*
accountopt
;
PidginFilterBuddyCompletionEntryFunc
filter_func
;
gpointer
filter_func_user_data
;
GtkListStore
*
store
;
}
PidginCompletionData
;
struct
_icon_chooser
{
GtkFileChooserNative
*
icon_filesel
;
GtkWidget
*
icon_preview
;
GtkWidget
*
icon_text
;
void
(
*
callback
)(
const
char
*
,
gpointer
);
gpointer
data
;
};
/******************************************************************************
* Globals
*****************************************************************************/
static
guint
accels_save_timer
=
0
;
/******************************************************************************
* Code
*****************************************************************************/
GtkWidget
*
pidgin_dialog_get_vbox_with_properties
(
GtkDialog
*
dialog
,
gboolean
homogeneous
,
gint
spacing
)
{
GtkBox
*
vbox
=
GTK_BOX
(
gtk_dialog_get_content_area
(
GTK_DIALOG
(
dialog
)));
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_get_content_area
(
GTK_DIALOG
(
dialog
));
}
GtkWidget
*
pidgin_dialog_get_action_area
(
GtkDialog
*
dialog
)
{
return
gtk_dialog_get_action_area
(
GTK_DIALOG
(
dialog
));
}
GtkWidget
*
pidgin_dialog_add_button
(
GtkDialog
*
dialog
,
const
char
*
label
,
GCallback
callback
,
gpointer
callbackdata
)
{
GtkWidget
*
button
=
gtk_button_new_with_mnemonic
(
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_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_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_new_menu_item
(
GtkWidget
*
menu
,
const
char
*
mnemonic
,
const
char
*
icon
,
GCallback
cb
,
gpointer
data
)
{
GtkWidget
*
menuitem
;
GtkWidget
*
box
;
GtkWidget
*
label
;
box
=
gtk_box_new
(
GTK_ORIENTATION_HORIZONTAL
,
6
);
menuitem
=
gtk_menu_item_new
();
if
(
cb
)
g_signal_connect
(
G_OBJECT
(
menuitem
),
"activate"
,
cb
,
data
);
if
(
icon
)
{
GtkWidget
*
image
;
image
=
gtk_image_new_from_stock
(
icon
,
GTK_ICON_SIZE_MENU
);
gtk_container_add
(
GTK_CONTAINER
(
box
),
image
);
}
label
=
gtk_label_new_with_mnemonic
(
mnemonic
);
gtk_container_add
(
GTK_CONTAINER
(
box
),
label
);
gtk_container_add
(
GTK_CONTAINER
(
menuitem
),
box
);
if
(
menu
)
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
menuitem
);
gtk_widget_show_all
(
menuitem
);
return
menuitem
;
}
GtkWidget
*
pidgin_make_frame
(
GtkWidget
*
parent
,
const
char
*
title
)
{
GtkWidget
*
vbox
,
*
vbox2
,
*
hbox
;
GtkLabel
*
label
;
char
*
labeltitle
;
vbox
=
gtk_box_new
(
GTK_ORIENTATION_VERTICAL
,
6
);
gtk_box_pack_start
(
GTK_BOX
(
parent
),
vbox
,
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
vbox
);
label
=
GTK_LABEL
(
gtk_label_new
(
NULL
));
labeltitle
=
g_strdup_printf
(
"<span weight=
\"
bold
\"
>%s</span>"
,
title
);
gtk_label_set_markup
(
label
,
labeltitle
);
g_free
(
labeltitle
);
gtk_label_set_xalign
(
GTK_LABEL
(
label
),
0
);
gtk_label_set_yalign
(
GTK_LABEL
(
label
),
0
);
gtk_box_pack_start
(
GTK_BOX
(
vbox
),
GTK_WIDGET
(
label
),
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
GTK_WIDGET
(
label
));
pidgin_set_accessible_label
(
vbox
,
label
);
hbox
=
gtk_box_new
(
GTK_ORIENTATION_HORIZONTAL
,
6
);
gtk_box_pack_start
(
GTK_BOX
(
vbox
),
hbox
,
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
hbox
);
label
=
GTK_LABEL
(
gtk_label_new
(
" "
));
gtk_box_pack_start
(
GTK_BOX
(
hbox
),
GTK_WIDGET
(
label
),
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
GTK_WIDGET
(
label
));
vbox2
=
gtk_box_new
(
GTK_ORIENTATION_VERTICAL
,
6
);
gtk_box_pack_start
(
GTK_BOX
(
hbox
),
vbox2
,
FALSE
,
FALSE
,
0
);
gtk_widget_show
(
vbox2
);
g_object_set_data
(
G_OBJECT
(
vbox2
),
"main-vbox"
,
vbox
);
return
vbox2
;
}
GdkPixbuf
*
pidgin_create_icon_from_protocol
(
PurpleProtocol
*
protocol
,
PidginProtocolIconSize
size
,
PurpleAccount
*
account
)
{
const
char
*
protoname
=
NULL
;
char
*
tmp
;
char
*
filename
=
NULL
;
GdkPixbuf
*
pixbuf
;
protoname
=
purple_protocol_get_list_icon
(
protocol
,
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
(
"im-"
,
protoname
,
".png"
,
NULL
);
filename
=
g_build_filename
(
PURPLE_DATADIR
,
"pidgin"
,
"icons"
,
"hicolor"
,
(
size
==
PIDGIN_PROTOCOL_ICON_SMALL
)
?
"16x16"
:
((
size
==
PIDGIN_PROTOCOL_ICON_MEDIUM
)
?
"22x22"
:
"48x48"
),
"apps"
,
tmp
,
NULL
);
g_free
(
tmp
);
pixbuf
=
pidgin_pixbuf_new_from_file
(
filename
);
g_free
(
filename
);
return
pixbuf
;
}
static
void
aop_option_menu_select_by_data
(
GtkWidget
*
optmenu
,
gpointer
data
)
{
GtkTreeModel
*
model
;
GtkTreeIter
iter
;
gpointer
iter_data
;
model
=
gtk_combo_box_get_model
(
GTK_COMBO_BOX
(
optmenu
));
if
(
gtk_tree_model_get_iter_first
(
model
,
&
iter
))
{
do
{
gtk_tree_model_get
(
model
,
&
iter
,
AOP_DATA_COLUMN
,
&
iter_data
,
-1
);
if
(
iter_data
==
data
)
{
gtk_combo_box_set_active_iter
(
GTK_COMBO_BOX
(
optmenu
),
&
iter
);
return
;
}
}
while
(
gtk_tree_model_iter_next
(
model
,
&
iter
));
}
}
void
pidgin_save_accels_cb
(
GtkAccelGroup
*
accel_group
,
guint
arg1
,
GdkModifierType
arg2
,
GClosure
*
arg3
,
gpointer
data
)
{
purple_debug_misc
(
"accels"
,
"accel changed, scheduling save."
);
if
(
!
accels_save_timer
)
accels_save_timer
=
g_timeout_add_seconds
(
5
,
pidgin_save_accels
,
NULL
);
}
gboolean
pidgin_save_accels
(
gpointer
data
)
{
char
*
filename
=
NULL
;
filename
=
g_build_filename
(
purple_config_dir
(),
"accels"
,
NULL
);
purple_debug_misc
(
"accels"
,
"saving accels to %s"
,
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_config_dir
(),
"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_plaintext
(
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
);
purple_serv_get_info
(
conn
,
name
);
}
void
pidgin_retrieve_user_info_in_chat
(
PurpleConnection
*
conn
,
const
char
*
name
,
int
chat
)
{
char
*
who
=
NULL
;
PurpleProtocol
*
protocol
=
NULL
;
if
(
chat
<
0
)
{
pidgin_retrieve_user_info
(
conn
,
name
);
return
;
}
protocol
=
purple_connection_get_protocol
(
conn
);
if
(
protocol
!=
NULL
)
who
=
purple_protocol_chat_get_user_real_name
(
PURPLE_PROTOCOL_CHAT
(
protocol
),
conn
,
chat
,
name
);
pidgin_retrieve_user_info
(
conn
,
who
?
who
:
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
==
' '
)
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
;
PurpleProtocol
*
proto
=
NULL
;
if
(
all_accounts
)
{
account
=
(
PurpleAccount
*
)
l
->
data
;
proto
=
purple_account_get_protocol
(
account
);
if
(
proto
==
NULL
)
{
account
=
NULL
;
continue
;
}
}
else
{
gc
=
(
PurpleConnection
*
)
l
->
data
;
account
=
purple_connection_get_account
(
gc
);
proto
=
purple_connection_get_protocol
(
gc
);
}
protoname
=
purple_protocol_get_list_icon
(
proto
,
account
,
NULL
);
if
(
purple_strequal
(
protoname
,
protocol
))
break
;
account
=
NULL
;
}
*
ret_account
=
account
;
}
}
else
{
valid
=
FALSE
;
g_free
(
username
);
g_free
(
protocol
);
g_free
(
alias
);
}
g_free
(
str
);
return
valid
;
}
void
pidgin_set_accessible_label
(
GtkWidget
*
w
,
GtkLabel
*
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
(
l
);
if
(
label_text
)
atk_object_set_name
(
acc
,
label_text
);
}
pidgin_set_accessible_relations
(
w
,
l
);
}
void
pidgin_set_accessible_relations
(
GtkWidget
*
w
,
GtkLabel
*
l
)
{
AtkObject
*
acc
,
*
label
;
AtkObject
*
rel_obj
[
1
];
AtkRelationSet
*
set
;
AtkRelation
*
relation
;
acc
=
gtk_widget_get_accessible
(
w
);
label
=
gtk_widget_get_accessible
(
GTK_WIDGET
(
l
));
/* Make sure mnemonics work */
gtk_label_set_mnemonic_widget
(
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_popup_at_treeview_selection
(
GtkWidget
*
menu
,
GtkWidget
*
treeview
)
{
GtkTreePath
*
path
;
GtkTreeViewColumn
*
column
;
GdkWindow
*
bin_window
;
GdkRectangle
rect
;
gtk_tree_view_get_cursor
(
GTK_TREE_VIEW
(
treeview
),
&
path
,
&
column
);
g_return_if_fail
(
path
!=
NULL
);
if
(
column
==
NULL
)
column
=
gtk_tree_view_get_column
(
GTK_TREE_VIEW
(
treeview
),
0
);
bin_window
=
gtk_tree_view_get_bin_window
(
GTK_TREE_VIEW
(
treeview
));
gtk_tree_view_get_cell_area
(
GTK_TREE_VIEW
(
treeview
),
path
,
column
,
&
rect
);
gtk_menu_popup_at_rect
(
GTK_MENU
(
menu
),
bin_window
,
&
rect
,
GDK_GRAVITY_SOUTH_WEST
,
GDK_GRAVITY_NORTH_WEST
,
NULL
);
gtk_tree_path_free
(
path
);
}
static
void
dnd_image_ok_callback
(
_DndData
*
data
,
int
choice
)
{
const
gchar
*
shortname
;
gchar
*
filedata
;
size_t
size
;
GStatBuf
st
;
GError
*
err
=
NULL
;
PurpleBuddy
*
buddy
;
PurpleContact
*
contact
;
PurpleImage
*
img
;
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
,
NULL
);
g_free
(
str
);
break
;
}
buddy
=
purple_blist_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
:
purple_serv_send_file
(
purple_account_get_connection
(
data
->
account
),
data
->
who
,
data
->
filename
);
break
;
case
DND_IM_IMAGE
:
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
,
NULL
);
g_error_free
(
err
);
g_free
(
str
);
break
;
}
shortname
=
strrchr
(
data
->
filename
,
G_DIR_SEPARATOR
);
shortname
=
shortname
?
shortname
+
1
:
data
->
filename
;
img
=
purple_image_new_from_data
((
guint8
*
)
filedata
,
size
);
purple_image_set_friendly_filename
(
img
,
shortname
);
# warning fix this when talkatu has a way to programmatically insert an image
// pidgin_webview_insert_image(PIDGIN_WEBVIEW(gtkconv->entry), img);
g_object_unref
(
img
);
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
);
}
static
void
pidgin_dnd_file_send_image
(
PurpleAccount
*
account
,
const
gchar
*
who
,
const
gchar
*
filename
)
{
PurpleConnection
*
gc
=
purple_account_get_connection
(
account
);
PurpleProtocol
*
protocol
=
NULL
;
_DndData
*
data
=
g_new0
(
_DndData
,
1
);
gboolean
ft
=
FALSE
,
im
=
FALSE
;
data
->
who
=
g_strdup
(
who
);
data
->
filename
=
g_strdup
(
filename
);
data
->
account
=
account
;
if
(
gc
)
protocol
=
purple_connection_get_protocol
(
gc
);
if
(
!
(
purple_connection_get_flags
(
gc
)
&
PURPLE_CONNECTION_FLAG_NO_IMAGES
))
im
=
TRUE
;
if
(
protocol
&&
PURPLE_IS_PROTOCOL_XFER
(
protocol
))
{
PurpleProtocolXferInterface
*
iface
=
PURPLE_PROTOCOL_XFER_GET_IFACE
(
protocol
);
if
(
iface
->
can_receive
)
{
ft
=
purple_protocol_xfer_can_receive
(
PURPLE_PROTOCOL_XFER
(
protocol
),
gc
,
who
);
}
else
{
ft
=
(
iface
->
send_file
)
?
TRUE
:
FALSE
;
}
}
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."
),
(
gpointer
)
DND_FILE_TRANSFER
,
_
(
"OK"
),
(
GCallback
)
dnd_image_ok_callback
,
_
(
"Cancel"
),
(
GCallback
)
dnd_image_cancel_callback
,
purple_request_cpar_from_account
(
account
),
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
,
purple_request_cpar_from_account
(
account
),
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"
)),
GINT_TO_POINTER
(
ft
?
DND_FILE_TRANSFER
:
DND_IM_IMAGE
),
_
(
"OK"
),
(
GCallback
)
dnd_image_ok_callback
,
_
(
"Cancel"
),
(
GCallback
)
dnd_image_cancel_callback
,
purple_request_cpar_from_account
(
account
),
data
,
_
(
"Set as buddy icon"
),
DND_BUDDY_ICON
,
(
ft
?
_
(
"Send image file"
)
:
_
(
"Insert in message"
)),
(
ft
?
DND_FILE_TRANSFER
:
DND_IM_IMAGE
),
NULL
);
}
}
#ifndef _WIN32
static
void
pidgin_dnd_file_send_desktop
(
PurpleAccount
*
account
,
const
gchar
*
who
,
const
gchar
*
filename
)
{
gchar
*
name
;
gchar
*
type
;
gchar
*
url
;
GKeyFile
*
desktop_file
;
PurpleConversation
*
conv
;
PidginConversation
*
gtkconv
;
GError
*
error
=
NULL
;
desktop_file
=
g_key_file_new
();
if
(
!
g_key_file_load_from_file
(
desktop_file
,
filename
,
G_KEY_FILE_NONE
,
&
error
))
{
if
(
error
)
{
purple_debug_warning
(
"D&D"
,
"Failed to load %s: %s
\n
"
,
filename
,
error
->
message
);
g_error_free
(
error
);
}
return
;
}
name
=
g_key_file_get_string
(
desktop_file
,
G_KEY_FILE_DESKTOP_GROUP
,
G_KEY_FILE_DESKTOP_KEY_NAME
,
&
error
);
if
(
error
)
{
purple_debug_warning
(
"D&D"
,
"Failed to read the Name from a desktop file: %s
\n
"
,
error
->
message
);
g_error_free
(
error
);
}
type
=
g_key_file_get_string
(
desktop_file
,
G_KEY_FILE_DESKTOP_GROUP
,
G_KEY_FILE_DESKTOP_KEY_TYPE
,
&
error
);
if
(
error
)
{
purple_debug_warning
(
"D&D"
,
"Failed to read the Type from a desktop file: %s
\n
"
,
error
->
message
);
g_error_free
(
error
);
}
url
=
g_key_file_get_string
(
desktop_file
,
G_KEY_FILE_DESKTOP_GROUP
,
G_KEY_FILE_DESKTOP_KEY_URL
,
&
error
);
if
(
error
)
{
purple_debug_warning
(
"D&D"
,
"Failed to read the Type from a desktop file: %s
\n
"
,
error
->
message
);
g_error_free
(
error
);
}
/* If any of this is null, do nothing. */
if
(
!
name
||
!
type
||
!
url
)
{
g_free
(
type
);
g_free
(
name
);
g_free
(
url
);
return
;
}
/* 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 */
if
(
purple_strequal
(
type
,
"Link"
))
{
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."
),
NULL
);
}
else
{
GtkTextBuffer
*
buffer
=
NULL
;
GtkTextMark
*
mark
=
NULL
;
GtkTextIter
iter
;
conv
=
PURPLE_CONVERSATION
(
purple_im_conversation_new
(
account
,
who
));
gtkconv
=
PIDGIN_CONVERSATION
(
conv
);
buffer
=
talkatu_editor_get_buffer
(
TALKATU_EDITOR
(
gtkconv
->
editor
));
mark
=
gtk_text_buffer_get_insert
(
buffer
);
gtk_text_buffer_get_iter_at_mark
(
buffer
,
&
iter
,
mark
);
talkatu_buffer_insert_link
(
TALKATU_BUFFER
(
buffer
),
&
iter
,
name
,
url
);
}
g_free
(
type
);
g_free
(
name
);
g_free
(
url
);
}
#endif
/* _WIN32 */
void
pidgin_dnd_file_manage
(
GtkSelectionData
*
sd
,
PurpleAccount
*
account
,
const
char
*
who
)
{
GdkPixbuf
*
pb
;
GList
*
files
=
purple_uri_list_extract_filenames
((
const
gchar
*
)
gtk_selection_data_get_data
(
sd
));
PurpleConnection
*
gc
=
purple_account_get_connection
(
account
);
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
,
purple_request_cpar_from_connection
(
gc
));
g_free
(
str
);
g_free
(
str2
);
continue
;
}
/* Are we dealing with an image? */
pb
=
pidgin_pixbuf_new_from_file
(
filename
);
if
(
pb
)
{
pidgin_dnd_file_send_image
(
account
,
who
,
filename
);
g_object_unref
(
G_OBJECT
(
pb
));
continue
;
}
#ifndef _WIN32
/* Are we trying to send a .desktop file? */
else
if
(
g_str_has_suffix
(
basename
,
".desktop"
))
{
pidgin_dnd_file_send_desktop
(
account
,
who
,
filename
);
continue
;
}
#endif
/* _WIN32 */
/* Everything is fine, let's send */
purple_serv_send_file
(
gc
,
who
,
filename
);
}
g_free
(
filename
);
g_free
(
basename
);
}
void
pidgin_buddy_icon_get_scale_size
(
GdkPixbuf
*
buf
,
PurpleBuddyIconSpec
*
spec
,
PurpleBuddyIconScaleFlags
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_spec_get_scaled_size
(
spec
,
width
,
height
);
/* and now for some arbitrary sanity checks */
if
(
*
width
>
100
)
*
width
=
100
;
if
(
*
height
>
100
)
*
height
=
100
;
}
const
gchar
*
pidgin_stock_id_from_status_primitive
(
PurpleStatusPrimitive
prim
)
{
const
gchar
*
stock
=
NULL
;
switch
(
prim
)
{
case
PURPLE_STATUS_UNSET
:
stock
=
NULL
;
break
;
case
PURPLE_STATUS_UNAVAILABLE
:
stock
=
PIDGIN_STOCK_STATUS_BUSY
;
break
;
case
PURPLE_STATUS_AWAY
:
stock
=
PIDGIN_STOCK_STATUS_AWAY
;
break
;
case
PURPLE_STATUS_EXTENDED_AWAY
:
stock
=
PIDGIN_STOCK_STATUS_XA
;
break
;
case
PURPLE_STATUS_INVISIBLE
:
stock
=
PIDGIN_STOCK_STATUS_INVISIBLE
;
break
;
case
PURPLE_STATUS_OFFLINE
:
stock
=
PIDGIN_STOCK_STATUS_OFFLINE
;
break
;
default
:
stock
=
PIDGIN_STOCK_STATUS_AVAILABLE
;
break
;
}
return
stock
;
}
GdkPixbuf
*
pidgin_create_protocol_icon
(
PurpleAccount
*
account
,
PidginProtocolIconSize
size
)
{
PurpleProtocol
*
protocol
;
g_return_val_if_fail
(
account
!=
NULL
,
NULL
);
protocol
=
purple_account_get_protocol
(
account
);
if
(
protocol
==
NULL
)
return
NULL
;
return
pidgin_create_icon_from_protocol
(
protocol
,
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
);
}
static
GtkWidget
*
do_pidgin_append_menu_action
(
GtkWidget
*
menu
,
PurpleActionMenu
*
act
,
gpointer
object
)
{
GtkWidget
*
menuitem
;
GList
*
list
;
if
(
act
==
NULL
)
{
return
pidgin_separator
(
menu
);
}
menuitem
=
gtk_menu_item_new_with_mnemonic
(
purple_action_menu_get_label
(
act
));
list
=
purple_action_menu_get_children
(
act
);
if
(
list
==
NULL
)
{
PurpleCallback
callback
;
callback
=
purple_action_menu_get_callback
(
act
);
if
(
callback
!=
NULL
)
{
g_object_set_data
(
G_OBJECT
(
menuitem
),
"purplecallback"
,
callback
);
g_object_set_data
(
G_OBJECT
(
menuitem
),
"purplecallbackdata"
,
purple_action_menu_get_data
(
act
));
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
;
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_get_accel_path
(
GTK_MENU_ITEM
(
menuitem
)),
purple_action_menu_get_label
(
act
));
gtk_menu_set_accel_path
(
GTK_MENU
(
submenu
),
path
);
g_free
(
path
);
gtk_menu_set_accel_group
(
GTK_MENU
(
submenu
),
group
);
}
for
(
l
=
list
;
l
;
l
=
l
->
next
)
{
PurpleActionMenu
*
act
=
(
PurpleActionMenu
*
)
l
->
data
;
do_pidgin_append_menu_action
(
submenu
,
act
,
object
);
}
}
return
menuitem
;
}
GtkWidget
*
pidgin_append_menu_action
(
GtkWidget
*
menu
,
PurpleActionMenu
*
act
,
gpointer
object
)
{
GtkWidget
*
menuitem
=
do_pidgin_append_menu_action
(
menu
,
act
,
object
);
purple_action_menu_free
(
act
);
return
menuitem
;
}
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
,
COMPLETION_NORMALIZED_COLUMN
,
&
val1
);
tmp
=
g_value_get_string
(
&
val1
);
if
(
tmp
!=
NULL
&&
g_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
,
COMPLETION_COMPARISON_COLUMN
,
&
val2
);
tmp
=
g_value_get_string
(
&
val2
);
if
(
tmp
!=
NULL
&&
g_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
,
COMPLETION_BUDDY_COLUMN
,
&
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
,
COMPLETION_ACCOUNT_COLUMN
,
&
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
,
COMPLETION_DISPLAYED_COLUMN
,
completion_entry
,
COMPLETION_BUDDY_COLUMN
,
buddyname
,
COMPLETION_NORMALIZED_COLUMN
,
normalized_buddyname
,
COMPLETION_COMPARISON_COLUMN
,
tmp
,
COMPLETION_ACCOUNT_COLUMN
,
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
,
COMPLETION_DISPLAYED_COLUMN
,
completion_entry
,
COMPLETION_BUDDY_COLUMN
,
buddyname
,
COMPLETION_NORMALIZED_COLUMN
,
normalized_buddyname
,
COMPLETION_COMPARISON_COLUMN
,
tmp
,
COMPLETION_ACCOUNT_COLUMN
,
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
,
COMPLETION_DISPLAYED_COLUMN
,
buddyname
,
COMPLETION_BUDDY_COLUMN
,
buddyname
,
COMPLETION_NORMALIZED_COLUMN
,
normalized_buddyname
,
COMPLETION_COMPARISON_COLUMN
,
NULL
,
COMPLETION_ACCOUNT_COLUMN
,
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
;
gchar
*
alias
;
gtk_list_store_clear
(
data
->
store
);
for
(
gnode
=
purple_blist_get_default_root
();
gnode
!=
NULL
;
gnode
=
gnode
->
next
)
{
if
(
!
PURPLE_IS_GROUP
(
gnode
))
continue
;
for
(
cnode
=
gnode
->
child
;
cnode
!=
NULL
;
cnode
=
cnode
->
next
)
{
if
(
!
PURPLE_IS_CONTACT
(
cnode
))
continue
;
g_object_get
(
cnode
,
"alias"
,
&
alias
,
NULL
);
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
,
alias
,
purple_buddy_get_contact_alias
(
entry
.
entry
.
buddy
),
purple_buddy_get_account
(
entry
.
entry
.
buddy
),
purple_buddy_get_name
(
entry
.
entry
.
buddy
)
);
}
}
g_free
(
alias
);
}
}
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
(
GtkWidget
*
entry
,
GtkWidget
*
chooser
,
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
(
COMPLETION_COLUMN_COUNT
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_STRING
,
G_TYPE_POINTER
);
data
->
entry
=
entry
;
data
->
accountopt
=
chooser
;
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
),
COMPLETION_BUDDY_COLUMN
,
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
,
COMPLETION_DISPLAYED_COLUMN
);
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
(
purple_buddy_get_account
(
completion_entry
->
entry
.
buddy
));
}
else
{
return
all
||
(
completion_entry
->
entry
.
logged_buddy
->
account
!=
NULL
&&
purple_account_is_connected
(
completion_entry
->
entry
.
logged_buddy
->
account
));
}
}
void
pidgin_set_cursor
(
GtkWidget
*
widget
,
GdkCursorType
cursor_type
)
{
GdkDisplay
*
display
;
GdkCursor
*
cursor
;
g_return_if_fail
(
widget
!=
NULL
);
if
(
gtk_widget_get_window
(
widget
)
==
NULL
)
return
;
display
=
gtk_widget_get_display
(
widget
);
cursor
=
gdk_cursor_new_for_display
(
display
,
cursor_type
);
gdk_window_set_cursor
(
gtk_widget_get_window
(
widget
),
cursor
);
g_object_unref
(
cursor
);
gdk_display_flush
(
gdk_window_get_display
(
gtk_widget_get_window
(
widget
)));
}
void
pidgin_clear_cursor
(
GtkWidget
*
widget
)
{
g_return_if_fail
(
widget
!=
NULL
);
if
(
gtk_widget_get_window
(
widget
)
==
NULL
)
return
;
gdk_window_set_cursor
(
gtk_widget_get_window
(
widget
),
NULL
);
}
static
void
icon_filesel_choose_cb
(
GtkWidget
*
widget
,
gint
response
,
struct
_icon_chooser
*
dialog
)
{
char
*
filename
,
*
current_folder
;
if
(
response
!=
GTK_RESPONSE_ACCEPT
)
{
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
);
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
;
GStatBuf
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
=
g_format_size
(
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
);
}
GtkFileChooserNative
*
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_native_new
(
_
(
"Buddy Icon"
),
parent
,
GTK_FILE_CHOOSER_ACTION_OPEN
,
_
(
"_Open"
),
_
(
"_Cancel"
));
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_box_new
(
GTK_ORIENTATION_VERTICAL
,
6
);
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
);
return
dialog
->
icon_filesel
;
}
/*
* str_array_match:
*
* Returns: %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
(
PurpleProtocol
*
protocol
,
const
char
*
path
,
size_t
*
len
)
{
PurpleBuddyIconSpec
*
spec
;
int
orig_width
,
orig_height
,
new_width
,
new_height
;
GdkPixbufFormat
*
format
;
char
**
pixbuf_formats
;
char
**
protocol_formats
;
GError
*
error
=
NULL
;
gchar
*
contents
;
gsize
length
;
GdkPixbuf
*
pixbuf
,
*
original
;
float
scale_factor
;
int
i
;
gchar
*
tmp
;
spec
=
purple_protocol_get_icon_spec
(
protocol
);
if
(
spec
->
format
==
NULL
)
{
purple_buddy_icon_spec_free
(
spec
);
return
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
);
purple_buddy_icon_spec_free
(
spec
);
return
NULL
;
}
pixbuf_formats
=
gdk_pixbuf_format_get_extensions
(
format
);
protocol_formats
=
g_strsplit
(
spec
->
format
,
","
,
0
);
if
(
str_array_match
(
pixbuf_formats
,
protocol_formats
)
&&
/* This is an acceptable format AND */
(
!
(
spec
->
scale_rules
&
PURPLE_ICON_SCALE_SEND
)
||
/* The protocol 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
(
protocol_formats
);
purple_buddy_icon_spec_free
(
spec
);
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
(
protocol_formats
);
purple_buddy_icon_spec_free
(
spec
);
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
(
protocol_formats
);
purple_buddy_icon_spec_free
(
spec
);
return
NULL
;
}
original
=
g_object_ref
(
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_spec_get_scaled_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
;
protocol_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
"
,
protocol_formats
[
i
]);
if
(
purple_strequal
(
protocol_formats
[
i
],
"png"
))
{
key
=
"compression"
;
value
=
"9"
;
}
else
if
(
purple_strequal
(
protocol_formats
[
i
],
"jpeg"
))
{
sprintf
(
tmp_buf
,
"%u"
,
quality
);
key
=
"quality"
;
value
=
tmp_buf
;
}
if
(
!
gdk_pixbuf_save_to_buffer
(
pixbuf
,
&
contents
,
&
length
,
protocol_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
"
,
protocol_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=%"
G_GSIZE_FORMAT
"
\n
"
,
orig_width
,
orig_height
,
new_width
,
new_height
,
protocol_formats
[
i
],
quality
,
length
);
if
(
len
)
*
len
=
length
;
g_strfreev
(
protocol_formats
);
g_object_unref
(
G_OBJECT
(
pixbuf
));
g_object_unref
(
G_OBJECT
(
original
));
purple_buddy_icon_spec_free
(
spec
);
return
contents
;
}
g_free
(
contents
);
if
(
!
purple_strequal
(
protocol_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
(
protocol_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
,
purple_protocol_get_name
(
protocol
));
purple_notify_error
(
NULL
,
_
(
"Icon Error"
),
_
(
"Could not set icon"
),
tmp
,
NULL
);
g_free
(
tmp
);
purple_buddy_icon_spec_free
(
spec
);
return
NULL
;
}
/*
* "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
,
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
(
g_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
&&
g_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 (g_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
;
}
const
char
*
pidgin_get_dim_grey_string
(
GtkWidget
*
widget
)
{
static
char
dim_grey_string
[
8
]
=
""
;
GtkStyleContext
*
context
;
GdkRGBA
fg
,
bg
;
if
(
!
widget
)
return
"dim grey"
;
context
=
gtk_widget_get_style_context
(
widget
);
if
(
!
context
)
return
"dim grey"
;
gtk_style_context_get_color
(
context
,
gtk_style_context_get_state
(
context
),
&
fg
);
gtk_style_context_get_background_color
(
context
,
gtk_style_context_get_state
(
context
),
&
bg
);
g_snprintf
(
dim_grey_string
,
sizeof
(
dim_grey_string
),
"#%02x%02x%02x"
,
(
unsigned
int
)((
fg
.
red
+
bg
.
red
)
*
0.5
*
255
),
(
unsigned
int
)((
fg
.
green
+
bg
.
green
)
*
0.5
*
255
),
(
unsigned
int
)((
fg
.
blue
+
bg
.
blue
)
*
0.5
*
255
));
return
dim_grey_string
;
}
static
void
combo_box_changed_cb
(
GtkComboBoxText
*
combo_box
,
GtkEntry
*
entry
)
{
char
*
text
=
gtk_combo_box_text_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
,
GtkComboBoxText
*
combo
)
{
if
(
key
->
keyval
==
GDK_KEY_Down
||
key
->
keyval
==
GDK_KEY_Up
)
{
gtk_combo_box_popup
(
GTK_COMBO_BOX
(
combo
));
return
TRUE
;
}
return
FALSE
;
}
GtkWidget
*
pidgin_text_combo_box_entry_new
(
const
char
*
default_item
,
GList
*
items
)
{
GtkComboBoxText
*
ret
=
NULL
;
GtkWidget
*
the_entry
=
NULL
;
ret
=
GTK_COMBO_BOX_TEXT
(
gtk_combo_box_text_new_with_entry
());
the_entry
=
gtk_bin_get_child
(
GTK_BIN
(
ret
));
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_text_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_get_child
(
GTK_BIN
((
widget
)))));
}
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_box_new
(
GTK_ORIENTATION_HORIZONTAL
,
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_label_set_xalign
(
GTK_LABEL
(
label
),
0
);
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
,
GTK_LABEL
(
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_get_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
;
GtkWindow
*
parent
=
NULL
;
GdkEvent
*
event
=
gtk_get_current_event
();
GdkWindow
*
menu
=
NULL
;
gpointer
parent_from
;
PurpleNotifyType
notify_type
;
parent_from
=
g_object_get_data
(
G_OBJECT
(
widget
),
"pidgin-parent-from"
);
if
(
purple_request_is_valid_ui_handle
(
parent_from
,
NULL
))
{
gtk_window_set_transient_for
(
GTK_WINDOW
(
widget
),
gtk_window_get_transient_for
(
pidgin_request_get_dialog_window
(
parent_from
)));
return
TRUE
;
}
if
(
purple_notify_is_valid_ui_handle
(
parent_from
,
&
notify_type
)
&&
notify_type
==
PURPLE_NOTIFY_MESSAGE
)
{
gtk_window_set_transient_for
(
GTK_WINDOW
(
widget
),
gtk_window_get_transient_for
(
GTK_WINDOW
(
parent_from
)));
return
TRUE
;
}
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
)
{
GtkWindow
*
window
=
GTK_WINDOW
(
windows
->
data
);
windows
=
g_list_delete_link
(
windows
,
windows
);
if
(
GPOINTER_TO_INT
(
g_object_get_data
(
G_OBJECT
(
window
),
"pidgin-window-is-closing"
)))
{
parent
=
gtk_window_get_transient_for
(
window
);
break
;
}
if
(
GTK_WIDGET
(
window
)
==
widget
||
!
gtk_widget_get_visible
(
GTK_WIDGET
(
window
)))
{
continue
;
}
if
(
gtk_window_has_toplevel_focus
(
window
)
||
(
menu
&&
menu
==
gtk_widget_get_window
(
GTK_WIDGET
(
window
))))
{
parent
=
window
;
break
;
}
}
if
(
windows
)
g_list_free
(
windows
);
if
(
parent
)
{
gtk_window_set_transient_for
(
GTK_WINDOW
(
widget
),
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=%"
G_GSIZE_FORMAT
": %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 %"
G_GSIZE_FORMAT
": %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 %"
G_GSIZE_FORMAT
"
\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_image
(
PurpleImage
*
image
)
{
return
pidgin_pixbuf_from_data
(
purple_image_get_data
(
image
),
purple_image_get_data_size
(
image
));
}
GdkPixbuf
*
pidgin_pixbuf_new_from_file
(
const
gchar
*
filename
)
{
GdkPixbuf
*
pixbuf
;
GError
*
error
=
NULL
;
g_return_val_if_fail
(
filename
!=
NULL
,
NULL
);
g_return_val_if_fail
(
filename
[
0
]
!=
'\0'
,
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
;
g_return_val_if_fail
(
filename
!=
NULL
,
NULL
);
g_return_val_if_fail
(
filename
[
0
]
!=
'\0'
,
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
;
g_return_val_if_fail
(
filename
!=
NULL
,
NULL
);
g_return_val_if_fail
(
filename
[
0
]
!=
'\0'
,
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
;
}
GdkPixbuf
*
pidgin_pixbuf_scale_down
(
GdkPixbuf
*
src
,
guint
max_width
,
guint
max_height
,
GdkInterpType
interp_type
,
gboolean
preserve_ratio
)
{
guint
cur_w
,
cur_h
;
GdkPixbuf
*
dst
;
g_return_val_if_fail
(
src
!=
NULL
,
NULL
);
if
(
max_width
==
0
||
max_height
==
0
)
{
g_object_unref
(
src
);
g_return_val_if_reached
(
NULL
);
}
cur_w
=
gdk_pixbuf_get_width
(
src
);
cur_h
=
gdk_pixbuf_get_height
(
src
);
if
(
cur_w
<=
max_width
&&
cur_h
<=
max_height
)
return
src
;
/* cur_ratio = cur_w / cur_h
* max_ratio = max_w / max_h
*/
if
(
!
preserve_ratio
)
{
cur_w
=
MIN
(
cur_w
,
max_width
);
cur_h
=
MIN
(
cur_h
,
max_height
);
}
else
if
((
guint64
)
cur_w
*
max_height
>
(
guint64
)
max_width
*
cur_h
)
{
/* cur_w / cur_h > max_width / max_height */
cur_h
=
(
guint64
)
max_width
*
cur_h
/
cur_w
;
cur_w
=
max_width
;
}
else
{
cur_w
=
(
guint64
)
max_height
*
cur_w
/
cur_h
;
cur_h
=
max_height
;
}
if
(
cur_w
<=
0
)
cur_w
=
1
;
if
(
cur_h
<=
0
)
cur_h
=
1
;
dst
=
gdk_pixbuf_scale_simple
(
src
,
cur_w
,
cur_h
,
interp_type
);
g_object_unref
(
src
);
return
dst
;
}
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
)
{
gtk_container_add
(
GTK_CONTAINER
(
sw
),
child
);
}
return
sw
;
}
return
child
;
}