pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Use Meson summary() function.
2021-07-27, Elliott Sales de Andrade
cb640ea0f315
Use Meson summary() function.
Now that we require at least 0.52, we can use Meson's builtin summary printing to display the results of configuration.
Testing Done:
Configured with defaults, and with pixmaps disabled to trigger the warning: https://asciinema.org/a/mV2oxOoVCJNdmrPwgqqUJ3mkU?t=17
Reviewed at https://reviews.imfreedom.org/r/848/
/* 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
;
}
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
);
}
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
;
}