2 * @file gtkstatusbox.c GTK+ Status Selection Widget
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 * The status box is made up of two main pieces:
29 * - The box that displays the current status, which is made
30 * of a GtkListStore ("status_box->store") and GtkCellView
31 * ("status_box->cell_view"). There is always exactly 1 row
32 * in this list store. Only the TYPE_ICON and TYPE_TEXT
33 * columns are used in this list store.
34 * - The dropdown menu that lets users select a status, which
35 * is made of a GtkComboBox ("status_box") and GtkListStore
36 * ("status_box->dropdown_store"). This dropdown is shown
37 * when the user clicks on the box that displays the current
38 * status. This list store contains one row for Available,
39 * one row for Away, etc., a few rows for popular statuses,
40 * and the "New..." and "Saved..." options.
43 #include <gdk/gdkkeysyms.h>
48 #include "buddyicon.h"
53 #include "savedstatuses.h"
58 #include "gtksavedstatuses.h"
59 #include "pidginstock.h"
60 #include "gtkstatusbox.h"
64 # include <gtkspell/gtkspell.h>
70 /* Timeout for typing notifications in seconds */
71 #define TYPING_TIMEOUT 4
73 static void imhtml_changed_cb(GtkTextBuffer
*buffer
, void *data
);
74 static void imhtml_format_changed_cb(GtkIMHtml
*imhtml
, GtkIMHtmlButtons buttons
, void *data
);
75 static void remove_typing_cb(PidginStatusBox
*box
);
76 static void update_size (PidginStatusBox
*box
);
77 static gint
get_statusbox_index(PidginStatusBox
*box
, PurpleSavedStatus
*saved_status
);
78 static PurpleAccount
* check_active_accounts_for_identical_statuses(void);
80 static void pidgin_status_box_pulse_typing(PidginStatusBox
*status_box
);
81 static void pidgin_status_box_refresh(PidginStatusBox
*status_box
);
82 static void status_menu_refresh_iter(PidginStatusBox
*status_box
, gboolean status_changed
);
83 static void pidgin_status_box_regenerate(PidginStatusBox
*status_box
, gboolean status_changed
);
84 static void pidgin_status_box_changed(PidginStatusBox
*box
);
85 static void pidgin_status_box_size_request (GtkWidget
*widget
, GtkRequisition
*requisition
);
86 static void pidgin_status_box_size_allocate (GtkWidget
*widget
, GtkAllocation
*allocation
);
87 static gboolean
pidgin_status_box_expose_event (GtkWidget
*widget
, GdkEventExpose
*event
);
88 static void pidgin_status_box_redisplay_buddy_icon(PidginStatusBox
*status_box
);
89 static void pidgin_status_box_forall (GtkContainer
*container
, gboolean include_internals
, GtkCallback callback
, gpointer callback_data
);
90 static void pidgin_status_box_popup(PidginStatusBox
*box
);
91 static void pidgin_status_box_popdown(PidginStatusBox
*box
);
93 static void do_colorshift (GdkPixbuf
*dest
, GdkPixbuf
*src
, int shift
);
94 static void icon_choose_cb(const char *filename
, gpointer data
);
95 static void remove_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
);
96 static void choose_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
);
99 /** A PidginStatusBoxItemType */
102 /** This is the stock-id for the icon. */
106 * This is a GdkPixbuf (the other columns are strings).
107 * This column is visible.
111 /** The text displayed on the status box. This column is visible. */
114 /** The plain-English title of this item */
117 /** A plain-English description of this item */
121 * This value depends on TYPE_COLUMN. For POPULAR types,
122 * this is the creation time. For PRIMITIVE types,
123 * this is the PurpleStatusPrimitive.
128 * This column stores the GdkPixbuf for the status emblem. Currently only 'saved' is stored.
129 * In the GtkTreeModel for the dropdown, this is the stock-id (gchararray), and for the
130 * GtkTreeModel for the cell_view (for the account-specific statusbox), this is the prpl-icon
131 * (GdkPixbuf) of the account.
136 * This column stores whether to show the emblem.
138 EMBLEM_VISIBLE_COLUMN
,
149 static char *typing_stock_ids
[7] = {
150 PIDGIN_STOCK_ANIMATION_TYPING0
,
151 PIDGIN_STOCK_ANIMATION_TYPING1
,
152 PIDGIN_STOCK_ANIMATION_TYPING2
,
153 PIDGIN_STOCK_ANIMATION_TYPING3
,
154 PIDGIN_STOCK_ANIMATION_TYPING4
,
158 static char *connecting_stock_ids
[] = {
159 PIDGIN_STOCK_ANIMATION_CONNECT0
,
160 PIDGIN_STOCK_ANIMATION_CONNECT1
,
161 PIDGIN_STOCK_ANIMATION_CONNECT2
,
162 PIDGIN_STOCK_ANIMATION_CONNECT3
,
163 PIDGIN_STOCK_ANIMATION_CONNECT4
,
164 PIDGIN_STOCK_ANIMATION_CONNECT5
,
165 PIDGIN_STOCK_ANIMATION_CONNECT6
,
166 PIDGIN_STOCK_ANIMATION_CONNECT7
,
167 PIDGIN_STOCK_ANIMATION_CONNECT8
,
168 PIDGIN_STOCK_ANIMATION_CONNECT9
,
169 PIDGIN_STOCK_ANIMATION_CONNECT10
,
170 PIDGIN_STOCK_ANIMATION_CONNECT11
,
171 PIDGIN_STOCK_ANIMATION_CONNECT12
,
172 PIDGIN_STOCK_ANIMATION_CONNECT13
,
173 PIDGIN_STOCK_ANIMATION_CONNECT14
,
174 PIDGIN_STOCK_ANIMATION_CONNECT15
,
175 PIDGIN_STOCK_ANIMATION_CONNECT16
,
176 PIDGIN_STOCK_ANIMATION_CONNECT17
,
177 PIDGIN_STOCK_ANIMATION_CONNECT18
,
178 PIDGIN_STOCK_ANIMATION_CONNECT19
,
179 PIDGIN_STOCK_ANIMATION_CONNECT20
,
180 PIDGIN_STOCK_ANIMATION_CONNECT21
,
181 PIDGIN_STOCK_ANIMATION_CONNECT22
,
182 PIDGIN_STOCK_ANIMATION_CONNECT23
,
183 PIDGIN_STOCK_ANIMATION_CONNECT24
,
184 PIDGIN_STOCK_ANIMATION_CONNECT25
,
185 PIDGIN_STOCK_ANIMATION_CONNECT26
,
186 PIDGIN_STOCK_ANIMATION_CONNECT27
,
187 PIDGIN_STOCK_ANIMATION_CONNECT28
,
188 PIDGIN_STOCK_ANIMATION_CONNECT29
,
189 PIDGIN_STOCK_ANIMATION_CONNECT30
,
193 static GtkContainerClass
*parent_class
= NULL
;
195 static void pidgin_status_box_class_init (PidginStatusBoxClass
*klass
);
196 static void pidgin_status_box_init (PidginStatusBox
*status_box
);
199 pidgin_status_box_get_type (void)
201 static GType status_box_type
= 0;
203 if (!status_box_type
)
205 static const GTypeInfo status_box_info
=
207 sizeof (PidginStatusBoxClass
),
208 NULL
, /* base_init */
209 NULL
, /* base_finalize */
210 (GClassInitFunc
) pidgin_status_box_class_init
,
211 NULL
, /* class_finalize */
212 NULL
, /* class_data */
213 sizeof (PidginStatusBox
),
215 (GInstanceInitFunc
) pidgin_status_box_init
,
216 NULL
/* value_table */
219 status_box_type
= g_type_register_static(GTK_TYPE_CONTAINER
,
225 return status_box_type
;
229 pidgin_status_box_get_property(GObject
*object
, guint param_id
,
230 GValue
*value
, GParamSpec
*psec
)
232 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(object
);
236 g_value_set_pointer(value
, statusbox
->account
);
239 g_value_set_boolean(value
, statusbox
->icon_box
!= NULL
);
242 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, psec
);
248 update_to_reflect_account_status(PidginStatusBox
*status_box
, PurpleAccount
*account
, PurpleStatus
*newstatus
)
252 const PurpleStatusType
*statustype
= NULL
;
255 statustype
= purple_status_type_find_with_id((GList
*)purple_account_get_status_types(account
),
256 (char *)purple_status_type_get_id(purple_status_get_type(newstatus
)));
258 for (l
= purple_account_get_status_types(account
); l
!= NULL
; l
= l
->next
) {
259 PurpleStatusType
*status_type
= (PurpleStatusType
*)l
->data
;
261 if (!purple_status_type_is_user_settable(status_type
) ||
262 purple_status_type_is_independent(status_type
))
265 if (statustype
== status_type
)
269 gtk_imhtml_set_populate_primary_clipboard(
270 GTK_IMHTML(status_box
->imhtml
), TRUE
);
272 if (status_no
!= -1) {
274 gtk_widget_set_sensitive(GTK_WIDGET(status_box
), FALSE
);
275 path
= gtk_tree_path_new_from_indices(status_no
, -1);
276 if (status_box
->active_row
)
277 gtk_tree_row_reference_free(status_box
->active_row
);
278 status_box
->active_row
= gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box
->dropdown_store
), path
);
279 gtk_tree_path_free(path
);
281 message
= purple_status_get_attr_string(newstatus
, "message");
283 if (!message
|| !*message
)
285 gtk_widget_hide_all(status_box
->vbox
);
286 status_box
->imhtml_visible
= FALSE
;
290 gtk_widget_show_all(status_box
->vbox
);
291 status_box
->imhtml_visible
= TRUE
;
292 gtk_imhtml_clear(GTK_IMHTML(status_box
->imhtml
));
293 gtk_imhtml_clear_formatting(GTK_IMHTML(status_box
->imhtml
));
294 gtk_imhtml_append_text(GTK_IMHTML(status_box
->imhtml
), message
, 0);
296 gtk_widget_set_sensitive(GTK_WIDGET(status_box
), TRUE
);
297 pidgin_status_box_refresh(status_box
);
302 account_status_changed_cb(PurpleAccount
*account
, PurpleStatus
*oldstatus
, PurpleStatus
*newstatus
, PidginStatusBox
*status_box
)
304 if (status_box
->account
== account
)
305 update_to_reflect_account_status(status_box
, account
, newstatus
);
306 else if (status_box
->token_status_account
== account
)
307 status_menu_refresh_iter(status_box
, TRUE
);
311 icon_box_press_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
313 if (event
->button
== 3) {
314 GtkWidget
*menu_item
;
317 if (box
->icon_box_menu
)
318 gtk_widget_destroy(box
->icon_box_menu
);
320 box
->icon_box_menu
= gtk_menu_new();
322 menu_item
= pidgin_new_item_from_stock(box
->icon_box_menu
, _("Select Buddy Icon"), GTK_STOCK_ADD
,
323 G_CALLBACK(choose_buddy_icon_cb
),
326 menu_item
= pidgin_new_item_from_stock(box
->icon_box_menu
, _("Remove"), GTK_STOCK_REMOVE
,
327 G_CALLBACK(remove_buddy_icon_cb
),
329 if (!(path
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon"))
331 gtk_widget_set_sensitive(menu_item
, FALSE
);
333 gtk_menu_popup(GTK_MENU(box
->icon_box_menu
), NULL
, NULL
, NULL
, NULL
,
334 event
->button
, event
->time
);
337 choose_buddy_icon_cb(widget
, box
);
343 icon_box_dnd_cb(GtkWidget
*widget
, GdkDragContext
*dc
, gint x
, gint y
,
344 GtkSelectionData
*sd
, guint info
, guint t
, PidginStatusBox
*box
)
346 gchar
*name
= (gchar
*)sd
->data
;
348 if ((sd
->length
>= 0) && (sd
->format
== 8)) {
349 /* Well, it looks like the drag event was cool.
350 * Let's do something with it */
351 if (!g_ascii_strncasecmp(name
, "file://", 7)) {
352 GError
*converr
= NULL
;
355 if(!(tmp
= g_filename_from_uri(name
, NULL
, &converr
))) {
356 purple_debug(PURPLE_DEBUG_ERROR
, "buddyicon", "%s\n",
357 (converr
? converr
->message
:
358 "g_filename_from_uri error"));
361 if ((rtmp
= strchr(tmp
, '\r')) || (rtmp
= strchr(tmp
, '\n')))
363 icon_choose_cb(tmp
, box
);
366 gtk_drag_finish(dc
, TRUE
, FALSE
, t
);
368 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
372 statusbox_got_url(PurpleUtilFetchUrlData
*url_data
, gpointer user_data
,
373 const gchar
*themedata
, size_t len
, const gchar
*error_message
)
379 if ((error_message
!= NULL
) || (len
== 0))
382 f
= purple_mkstemp(&path
, TRUE
);
383 wc
= fwrite(themedata
, len
, 1, f
);
385 purple_debug_warning("theme_got_url", "Unable to write theme data.\n");
393 icon_choose_cb(path
, user_data
);
401 statusbox_uri_handler(const char *proto
, const char *cmd
, GHashTable
*params
, void *data
)
405 if (g_ascii_strcasecmp(proto
, "aim"))
408 if (g_ascii_strcasecmp(cmd
, "buddyicon"))
411 src
= g_hash_table_lookup(params
, "account");
415 purple_util_fetch_url(src
, TRUE
, NULL
, FALSE
, statusbox_got_url
, data
);
420 icon_box_enter_cb(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginStatusBox
*box
)
422 gdk_window_set_cursor(widget
->window
, box
->hand_cursor
);
423 gtk_image_set_from_pixbuf(GTK_IMAGE(box
->icon
), box
->buddy_icon_hover
);
428 icon_box_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginStatusBox
*box
)
430 gdk_window_set_cursor(widget
->window
, box
->arrow_cursor
);
431 gtk_image_set_from_pixbuf(GTK_IMAGE(box
->icon
), box
->buddy_icon
) ;
436 static const GtkTargetEntry dnd_targets
[] = {
437 {"text/plain", 0, 0},
438 {"text/uri-list", 0, 1},
443 setup_icon_box(PidginStatusBox
*status_box
)
445 if (status_box
->icon_box
!= NULL
)
448 status_box
->icon
= gtk_image_new();
449 status_box
->icon_box
= gtk_event_box_new();
450 gtk_widget_set_parent(status_box
->icon_box
, GTK_WIDGET(status_box
));
451 gtk_widget_show(status_box
->icon_box
);
453 #if GTK_CHECK_VERSION(2,12,0)
454 gtk_widget_set_tooltip_text(status_box
->icon_box
,
455 status_box
->account
? _("Click to change your buddyicon for this account.") :
456 _("Click to change your buddyicon for all accounts."));
459 if (status_box
->account
&&
460 !purple_account_get_bool(status_box
->account
, "use-global-buddyicon", TRUE
))
462 PurpleStoredImage
*img
= purple_buddy_icons_find_account_icon(status_box
->account
);
463 pidgin_status_box_set_buddy_icon(status_box
, img
);
464 purple_imgstore_unref(img
);
468 const char *filename
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon");
469 PurpleStoredImage
*img
= NULL
;
471 if (filename
&& *filename
)
472 img
= purple_imgstore_new_from_file(filename
);
474 pidgin_status_box_set_buddy_icon(status_box
, img
);
477 * purple_imgstore_new gives us a reference and
478 * pidgin_status_box_set_buddy_icon also takes one.
480 purple_imgstore_unref(img
);
483 status_box
->hand_cursor
= gdk_cursor_new (GDK_HAND2
);
484 status_box
->arrow_cursor
= gdk_cursor_new (GDK_LEFT_PTR
);
487 gtk_drag_dest_set(status_box
->icon_box
,
488 GTK_DEST_DEFAULT_MOTION
|
489 GTK_DEST_DEFAULT_DROP
,
491 sizeof(dnd_targets
) / sizeof(GtkTargetEntry
),
494 g_signal_connect(G_OBJECT(status_box
->icon_box
), "drag_data_received", G_CALLBACK(icon_box_dnd_cb
), status_box
);
495 g_signal_connect(G_OBJECT(status_box
->icon_box
), "enter-notify-event", G_CALLBACK(icon_box_enter_cb
), status_box
);
496 g_signal_connect(G_OBJECT(status_box
->icon_box
), "leave-notify-event", G_CALLBACK(icon_box_leave_cb
), status_box
);
497 g_signal_connect(G_OBJECT(status_box
->icon_box
), "button-press-event", G_CALLBACK(icon_box_press_cb
), status_box
);
499 gtk_container_add(GTK_CONTAINER(status_box
->icon_box
), status_box
->icon
);
500 gtk_widget_show(status_box
->icon
);
504 destroy_icon_box(PidginStatusBox
*statusbox
)
506 if (statusbox
->icon_box
== NULL
)
509 gtk_widget_destroy(statusbox
->icon_box
);
510 gdk_cursor_unref(statusbox
->hand_cursor
);
511 gdk_cursor_unref(statusbox
->arrow_cursor
);
513 purple_imgstore_unref(statusbox
->buddy_icon_img
);
515 g_object_unref(G_OBJECT(statusbox
->buddy_icon
));
516 g_object_unref(G_OBJECT(statusbox
->buddy_icon_hover
));
518 if (statusbox
->buddy_icon_sel
)
519 gtk_widget_destroy(statusbox
->buddy_icon_sel
);
521 if (statusbox
->icon_box_menu
)
522 gtk_widget_destroy(statusbox
->icon_box_menu
);
524 statusbox
->icon
= NULL
;
525 statusbox
->icon_box
= NULL
;
526 statusbox
->icon_box_menu
= NULL
;
527 statusbox
->buddy_icon_img
= NULL
;
528 statusbox
->buddy_icon
= NULL
;
529 statusbox
->buddy_icon_hover
= NULL
;
530 statusbox
->hand_cursor
= NULL
;
531 statusbox
->arrow_cursor
= NULL
;
535 pidgin_status_box_set_property(GObject
*object
, guint param_id
,
536 const GValue
*value
, GParamSpec
*pspec
)
538 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(object
);
542 if (g_value_get_boolean(value
)) {
543 if (statusbox
->account
) {
544 PurplePlugin
*plug
= purple_plugins_find_with_id(purple_account_get_protocol_id(statusbox
->account
));
546 PurplePluginProtocolInfo
*prplinfo
= PURPLE_PLUGIN_PROTOCOL_INFO(plug
);
547 if (prplinfo
&& prplinfo
->icon_spec
.format
!= NULL
)
548 setup_icon_box(statusbox
);
551 setup_icon_box(statusbox
);
554 destroy_icon_box(statusbox
);
558 statusbox
->account
= g_value_get_pointer(value
);
559 if (statusbox
->account
)
560 statusbox
->token_status_account
= NULL
;
562 statusbox
->token_status_account
= check_active_accounts_for_identical_statuses();
564 pidgin_status_box_regenerate(statusbox
, TRUE
);
568 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, pspec
);
574 pidgin_status_box_finalize(GObject
*obj
)
576 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(obj
);
579 purple_signals_disconnect_by_handle(statusbox
);
580 purple_prefs_disconnect_by_handle(statusbox
);
582 destroy_icon_box(statusbox
);
584 if (statusbox
->active_row
)
585 gtk_tree_row_reference_free(statusbox
->active_row
);
587 for (i
= 0; i
< G_N_ELEMENTS(statusbox
->connecting_pixbufs
); i
++) {
588 if (statusbox
->connecting_pixbufs
[i
] != NULL
)
589 g_object_unref(G_OBJECT(statusbox
->connecting_pixbufs
[i
]));
592 for (i
= 0; i
< G_N_ELEMENTS(statusbox
->typing_pixbufs
); i
++) {
593 if (statusbox
->typing_pixbufs
[i
] != NULL
)
594 g_object_unref(G_OBJECT(statusbox
->typing_pixbufs
[i
]));
597 g_object_unref(G_OBJECT(statusbox
->store
));
598 g_object_unref(G_OBJECT(statusbox
->dropdown_store
));
599 G_OBJECT_CLASS(parent_class
)->finalize(obj
);
603 pidgin_status_box_child_type (GtkContainer
*container
)
605 return GTK_TYPE_WIDGET
;
609 pidgin_status_box_class_init (PidginStatusBoxClass
*klass
)
611 GObjectClass
*object_class
;
612 GtkWidgetClass
*widget_class
;
613 GtkContainerClass
*container_class
= (GtkContainerClass
*)klass
;
615 parent_class
= g_type_class_peek_parent(klass
);
617 widget_class
= (GtkWidgetClass
*)klass
;
618 widget_class
->size_request
= pidgin_status_box_size_request
;
619 widget_class
->size_allocate
= pidgin_status_box_size_allocate
;
620 widget_class
->expose_event
= pidgin_status_box_expose_event
;
622 container_class
->child_type
= pidgin_status_box_child_type
;
623 container_class
->forall
= pidgin_status_box_forall
;
624 container_class
->remove
= NULL
;
626 object_class
= (GObjectClass
*)klass
;
628 object_class
->finalize
= pidgin_status_box_finalize
;
630 object_class
->get_property
= pidgin_status_box_get_property
;
631 object_class
->set_property
= pidgin_status_box_set_property
;
633 g_object_class_install_property(object_class
,
635 g_param_spec_pointer("account",
637 "The account, or NULL for all accounts",
641 g_object_class_install_property(object_class
,
643 g_param_spec_boolean("iconsel",
645 "Whether the icon selector should be displayed or not.",
653 * This updates the text displayed on the status box so that it shows
654 * the current status. This is the only function in this file that
655 * should modify status_box->store
658 pidgin_status_box_refresh(PidginStatusBox
*status_box
)
662 PurpleSavedStatus
*saved_status
;
663 char *primary
, *secondary
, *text
;
664 const char *stock
= NULL
;
665 GdkPixbuf
*emblem
= NULL
;
667 gboolean account_status
= FALSE
;
668 PurpleAccount
*acct
= (status_box
->account
) ? status_box
->account
: status_box
->token_status_account
;
670 style
= gtk_widget_get_style(GTK_WIDGET(status_box
));
671 snprintf(aa_color
, sizeof(aa_color
), "#%02x%02x%02x",
672 style
->text_aa
[GTK_STATE_NORMAL
].red
>> 8,
673 style
->text_aa
[GTK_STATE_NORMAL
].green
>> 8,
674 style
->text_aa
[GTK_STATE_NORMAL
].blue
>> 8);
676 saved_status
= purple_savedstatus_get_current();
678 if (status_box
->account
|| (status_box
->token_status_account
679 && purple_savedstatus_is_transient(saved_status
)))
680 account_status
= TRUE
;
683 if (status_box
->typing
!= 0)
686 PidginStatusBoxItemType type
;
689 /* Primary (get the status selected in the dropdown) */
690 path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
691 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
693 gtk_tree_path_free(path
);
695 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
699 if (type
== PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
)
700 primary
= g_strdup(purple_primitive_get_name_from_type(GPOINTER_TO_INT(data
)));
702 /* This should never happen, but just in case... */
703 primary
= g_strdup("New status");
705 else if (account_status
)
706 primary
= g_strdup(purple_status_get_name(purple_account_get_active_status(acct
)));
707 else if (purple_savedstatus_is_transient(saved_status
))
708 primary
= g_strdup(purple_primitive_get_name_from_type(purple_savedstatus_get_type(saved_status
)));
710 primary
= g_markup_escape_text(purple_savedstatus_get_title(saved_status
), -1);
713 if (status_box
->typing
!= 0)
714 secondary
= g_strdup(_("Typing"));
715 else if (status_box
->connecting
)
716 secondary
= g_strdup(_("Connecting"));
717 else if (!status_box
->network_available
)
718 secondary
= g_strdup(_("Waiting for network connection"));
719 else if (purple_savedstatus_is_transient(saved_status
))
725 message
= purple_savedstatus_get_message(saved_status
);
728 tmp
= purple_markup_strip_html(message
);
729 purple_util_chrreplace(tmp
, '\n', ' ');
730 secondary
= g_markup_escape_text(tmp
, -1);
738 if (status_box
->typing
!= 0)
739 stock
= typing_stock_ids
[status_box
->typing_index
];
740 else if (status_box
->connecting
)
741 stock
= connecting_stock_ids
[status_box
->connecting_index
];
744 PurpleStatusType
*status_type
;
745 PurpleStatusPrimitive prim
;
746 if (account_status
) {
747 status_type
= purple_status_get_type(purple_account_get_active_status(acct
));
748 prim
= purple_status_type_get_primitive(status_type
);
750 prim
= purple_savedstatus_get_type(saved_status
);
753 stock
= pidgin_stock_id_from_status_primitive(prim
);
756 if (status_box
->account
!= NULL
) {
757 text
= g_strdup_printf("%s - <span size=\"smaller\" color=\"%s\">%s</span>",
758 purple_account_get_username(status_box
->account
),
759 aa_color
, secondary
? secondary
: primary
);
760 emblem
= pidgin_create_prpl_icon(status_box
->account
, PIDGIN_PRPL_ICON_SMALL
);
761 } else if (secondary
!= NULL
) {
762 text
= g_strdup_printf("%s<span size=\"smaller\" color=\"%s\"> - %s</span>",
763 primary
, aa_color
, secondary
);
765 text
= g_strdup(primary
);
771 * Only two columns are used in this list store (does it
772 * really need to be a list store?)
774 gtk_list_store_set(status_box
->store
, &(status_box
->iter
),
775 ICON_STOCK_COLUMN
, (gpointer
)stock
,
777 EMBLEM_COLUMN
, emblem
,
778 EMBLEM_VISIBLE_COLUMN
, (emblem
!= NULL
),
782 g_object_unref(emblem
);
784 /* Make sure to activate the only row in the tree view */
785 path
= gtk_tree_path_new_from_string("0");
786 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(status_box
->cell_view
), path
);
787 gtk_tree_path_free(path
);
789 update_size(status_box
);
792 static PurpleStatusType
*
793 find_status_type_by_index(const PurpleAccount
*account
, gint active
)
795 GList
*l
= purple_account_get_status_types(account
);
798 for (i
= 0; l
; l
= l
->next
) {
799 PurpleStatusType
*status_type
= l
->data
;
800 if (!purple_status_type_is_user_settable(status_type
) ||
801 purple_status_type_is_independent(status_type
))
813 * This updates the GtkTreeView so that it correctly shows the state
814 * we are currently using. It is used when the current state is
815 * updated from somewhere other than the GtkStatusBox (from a plugin,
816 * or when signing on with the "-n" option, for example). It is
817 * also used when the user selects the "New..." option.
819 * Maybe we could accomplish this by triggering off the mouse and
820 * keyboard signals instead of the changed signal?
823 status_menu_refresh_iter(PidginStatusBox
*status_box
, gboolean status_changed
)
825 PurpleSavedStatus
*saved_status
;
826 PurpleStatusPrimitive primitive
;
829 GtkTreePath
*path
= NULL
;
831 /* this function is inappropriate for ones with accounts */
832 if (status_box
->account
)
835 saved_status
= purple_savedstatus_get_current();
838 * Suppress the "changed" signal because the status
839 * was changed programmatically.
841 gtk_widget_set_sensitive(GTK_WIDGET(status_box
), FALSE
);
844 * If there is a token-account, then select the primitive from the
845 * dropdown using a loop. Otherwise select from the default list.
847 primitive
= purple_savedstatus_get_type(saved_status
);
848 if (!status_box
->token_status_account
&& purple_savedstatus_is_transient(saved_status
) &&
849 ((primitive
== PURPLE_STATUS_AVAILABLE
) || (primitive
== PURPLE_STATUS_AWAY
) ||
850 (primitive
== PURPLE_STATUS_INVISIBLE
) || (primitive
== PURPLE_STATUS_OFFLINE
) ||
851 (primitive
== PURPLE_STATUS_UNAVAILABLE
)) &&
852 (!purple_savedstatus_has_substatuses(saved_status
)))
854 index
= get_statusbox_index(status_box
, saved_status
);
855 path
= gtk_tree_path_new_from_indices(index
, -1);
860 PidginStatusBoxItemType type
;
863 /* If this saved status is in the list store, then set it as the active item */
864 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
))
868 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
873 /* This is a special case because Primitives for the token_status_account are actually
874 * saved statuses with substatuses for the enabled accounts */
875 if (status_box
->token_status_account
&& purple_savedstatus_is_transient(saved_status
)
876 && type
== PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
&& primitive
== GPOINTER_TO_INT(data
))
879 const char *acct_status_name
= purple_status_get_name(
880 purple_account_get_active_status(status_box
->token_status_account
));
882 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
883 TEXT_COLUMN
, &name
, -1);
885 if (!purple_savedstatus_has_substatuses(saved_status
)
886 || !strcmp(name
, acct_status_name
))
889 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
);
895 } else if ((type
== PIDGIN_STATUS_BOX_TYPE_POPULAR
) &&
896 (GPOINTER_TO_INT(data
) == purple_savedstatus_get_creation_time(saved_status
)))
899 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
);
902 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
));
906 if (status_box
->active_row
)
907 gtk_tree_row_reference_free(status_box
->active_row
);
908 if (path
) { /* path should never be NULL */
909 status_box
->active_row
= gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box
->dropdown_store
), path
);
910 gtk_tree_path_free(path
);
912 status_box
->active_row
= NULL
;
914 if (status_changed
) {
915 message
= purple_savedstatus_get_message(saved_status
);
918 * If we are going to hide the imhtml, don't retain the
919 * message because showing the old message later is
920 * confusing. If we are going to set the message to a pre-set,
921 * then we need to do this anyway
923 * Suppress the "changed" signal because the status
924 * was changed programmatically.
926 gtk_widget_set_sensitive(GTK_WIDGET(status_box
->imhtml
), FALSE
);
928 gtk_imhtml_clear(GTK_IMHTML(status_box
->imhtml
));
929 gtk_imhtml_clear_formatting(GTK_IMHTML(status_box
->imhtml
));
931 if (!purple_savedstatus_is_transient(saved_status
) || !message
|| !*message
)
933 status_box
->imhtml_visible
= FALSE
;
934 gtk_widget_hide_all(status_box
->vbox
);
938 status_box
->imhtml_visible
= TRUE
;
939 gtk_widget_show_all(status_box
->vbox
);
941 gtk_imhtml_append_text(GTK_IMHTML(status_box
->imhtml
), message
, 0);
944 gtk_widget_set_sensitive(GTK_WIDGET(status_box
->imhtml
), TRUE
);
945 update_size(status_box
);
948 /* Stop suppressing the "changed" signal. */
949 gtk_widget_set_sensitive(GTK_WIDGET(status_box
), TRUE
);
953 add_popular_statuses(PidginStatusBox
*statusbox
)
957 list
= purple_savedstatuses_get_popular(6);
959 /* Odd... oh well, nothing we can do about it. */
962 pidgin_status_box_add_separator(statusbox
);
964 for (cur
= list
; cur
!= NULL
; cur
= cur
->next
)
966 PurpleSavedStatus
*saved
= cur
->data
;
967 const gchar
*message
;
968 gchar
*stripped
= NULL
;
969 PidginStatusBoxItemType type
;
971 if (purple_savedstatus_is_transient(saved
))
974 * Transient statuses do not have a title, so the savedstatus
975 * API returns the message when purple_savedstatus_get_title() is
976 * called, so we don't need to get the message a second time.
978 type
= PIDGIN_STATUS_BOX_TYPE_POPULAR
;
982 type
= PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
;
984 message
= purple_savedstatus_get_message(saved
);
987 stripped
= purple_markup_strip_html(message
);
988 purple_util_chrreplace(stripped
, '\n', ' ');
992 pidgin_status_box_add(statusbox
, type
,
993 NULL
, purple_savedstatus_get_title(saved
), stripped
,
994 GINT_TO_POINTER(purple_savedstatus_get_creation_time(saved
)));
1001 /* This returns NULL if the active accounts don't have identical
1002 * statuses and a token account if they do */
1003 static PurpleAccount
* check_active_accounts_for_identical_statuses(void)
1005 GList
*iter
, *active_accts
= purple_accounts_get_all_active();
1006 PurpleAccount
*acct1
= NULL
;
1007 const char *prpl1
= NULL
;
1010 acct1
= active_accts
->data
;
1011 prpl1
= purple_account_get_protocol_id(acct1
);
1013 /* there's no enabled account */
1017 /* start at the second account */
1018 for (iter
= active_accts
->next
; iter
; iter
= iter
->next
) {
1019 PurpleAccount
*acct2
= iter
->data
;
1022 if (!g_str_equal(prpl1
, purple_account_get_protocol_id(acct2
))) {
1027 for (s1
= purple_account_get_status_types(acct1
),
1028 s2
= purple_account_get_status_types(acct2
); s1
&& s2
;
1029 s1
= s1
->next
, s2
= s2
->next
) {
1030 PurpleStatusType
*st1
= s1
->data
, *st2
= s2
->data
;
1031 /* TODO: Are these enough to consider the statuses identical? */
1032 if (purple_status_type_get_primitive(st1
) != purple_status_type_get_primitive(st2
)
1033 || strcmp(purple_status_type_get_id(st1
), purple_status_type_get_id(st2
))
1034 || strcmp(purple_status_type_get_name(st1
), purple_status_type_get_name(st2
))) {
1040 if (s1
!= s2
) {/* Will both be NULL if matched */
1046 g_list_free(active_accts
);
1052 add_account_statuses(PidginStatusBox
*status_box
, PurpleAccount
*account
)
1057 for (l
= purple_account_get_status_types(account
); l
!= NULL
; l
= l
->next
)
1059 PurpleStatusType
*status_type
= (PurpleStatusType
*)l
->data
;
1060 PurpleStatusPrimitive prim
;
1062 if (!purple_status_type_is_user_settable(status_type
) ||
1063 purple_status_type_is_independent(status_type
))
1066 prim
= purple_status_type_get_primitive(status_type
);
1068 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
),
1069 PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
,
1070 purple_status_type_get_name(status_type
),
1072 GINT_TO_POINTER(prim
));
1077 pidgin_status_box_regenerate(PidginStatusBox
*status_box
, gboolean status_changed
)
1079 /* Unset the model while clearing it */
1080 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box
->tree_view
), NULL
);
1081 gtk_list_store_clear(status_box
->dropdown_store
);
1082 /* Don't set the model until the new statuses have been added to the box.
1083 * What is presumably a bug in Gtk < 2.4 causes things to get all confused
1084 * if we do this here. */
1085 /* gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store)); */
1087 if (status_box
->account
== NULL
)
1089 /* Do all the currently enabled accounts have the same statuses?
1090 * If so, display them instead of our global list.
1092 if (status_box
->token_status_account
) {
1093 add_account_statuses(status_box
, status_box
->token_status_account
);
1096 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
, _("Available"), NULL
, GINT_TO_POINTER(PURPLE_STATUS_AVAILABLE
));
1097 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
, _("Away"), NULL
, GINT_TO_POINTER(PURPLE_STATUS_AWAY
));
1098 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
, _("Do not disturb"), NULL
, GINT_TO_POINTER(PURPLE_STATUS_UNAVAILABLE
));
1099 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
, _("Invisible"), NULL
, GINT_TO_POINTER(PURPLE_STATUS_INVISIBLE
));
1100 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
, _("Offline"), NULL
, GINT_TO_POINTER(PURPLE_STATUS_OFFLINE
));
1103 add_popular_statuses(status_box
);
1105 pidgin_status_box_add_separator(PIDGIN_STATUS_BOX(status_box
));
1106 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_CUSTOM
, NULL
, _("New status..."), NULL
, NULL
);
1107 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_SAVED
, NULL
, _("Saved statuses..."), NULL
, NULL
);
1109 status_menu_refresh_iter(status_box
, status_changed
);
1110 pidgin_status_box_refresh(status_box
);
1113 add_account_statuses(status_box
, status_box
->account
);
1114 update_to_reflect_account_status(status_box
, status_box
->account
,
1115 purple_account_get_active_status(status_box
->account
));
1117 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box
->tree_view
), GTK_TREE_MODEL(status_box
->dropdown_store
));
1118 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box
->tree_view
), TEXT_COLUMN
);
1121 static gboolean
combo_box_scroll_event_cb(GtkWidget
*w
, GdkEventScroll
*event
, GtkIMHtml
*imhtml
)
1123 pidgin_status_box_popup(PIDGIN_STATUS_BOX(w
));
1127 static gboolean
imhtml_scroll_event_cb(GtkWidget
*w
, GdkEventScroll
*event
, GtkIMHtml
*imhtml
)
1129 if (event
->direction
== GDK_SCROLL_UP
)
1130 gtk_imhtml_page_up(imhtml
);
1131 else if (event
->direction
== GDK_SCROLL_DOWN
)
1132 gtk_imhtml_page_down(imhtml
);
1136 static gboolean
imhtml_remove_focus(GtkWidget
*w
, GdkEventKey
*event
, PidginStatusBox
*status_box
)
1138 if (event
->keyval
== GDK_Tab
|| event
->keyval
== GDK_KP_Tab
|| event
->keyval
== GDK_ISO_Left_Tab
)
1140 /* If last inserted character is a tab, then remove the focus from here */
1141 GtkWidget
*top
= gtk_widget_get_toplevel(w
);
1142 g_signal_emit_by_name(G_OBJECT(top
), "move_focus",
1143 (event
->state
& GDK_SHIFT_MASK
) ?
1144 GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
);
1147 if (status_box
->typing
== 0)
1150 /* Reset the status if Escape was pressed */
1151 if (event
->keyval
== GDK_Escape
)
1153 purple_timeout_remove(status_box
->typing
);
1154 status_box
->typing
= 0;
1155 gtk_imhtml_set_populate_primary_clipboard(
1156 GTK_IMHTML(status_box
->imhtml
), TRUE
);
1157 if (status_box
->account
!= NULL
)
1158 update_to_reflect_account_status(status_box
, status_box
->account
,
1159 purple_account_get_active_status(status_box
->account
));
1161 status_menu_refresh_iter(status_box
, TRUE
);
1162 pidgin_status_box_refresh(status_box
);
1167 pidgin_status_box_pulse_typing(status_box
);
1168 purple_timeout_remove(status_box
->typing
);
1169 status_box
->typing
= purple_timeout_add_seconds(TYPING_TIMEOUT
, (GSourceFunc
)remove_typing_cb
, status_box
);
1175 dropdown_store_row_separator_func(GtkTreeModel
*model
,
1176 GtkTreeIter
*iter
, gpointer data
)
1178 PidginStatusBoxItemType type
;
1180 gtk_tree_model_get(model
, iter
, TYPE_COLUMN
, &type
, -1);
1182 if (type
== PIDGIN_STATUS_BOX_TYPE_SEPARATOR
)
1189 cache_pixbufs(PidginStatusBox
*status_box
)
1191 GtkIconSize icon_size
;
1194 g_object_set(G_OBJECT(status_box
->icon_rend
), "xpad", 3, NULL
);
1195 icon_size
= gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
);
1197 for (i
= 0; i
< G_N_ELEMENTS(status_box
->connecting_pixbufs
); i
++) {
1198 if (status_box
->connecting_pixbufs
[i
] != NULL
)
1199 g_object_unref(G_OBJECT(status_box
->connecting_pixbufs
[i
]));
1200 if (connecting_stock_ids
[i
])
1201 status_box
->connecting_pixbufs
[i
] = gtk_widget_render_icon (GTK_WIDGET(status_box
->vbox
),
1202 connecting_stock_ids
[i
], icon_size
, "PidginStatusBox");
1204 status_box
->connecting_pixbufs
[i
] = NULL
;
1206 status_box
->connecting_index
= 0;
1209 for (i
= 0; i
< G_N_ELEMENTS(status_box
->typing_pixbufs
); i
++) {
1210 if (status_box
->typing_pixbufs
[i
] != NULL
)
1211 g_object_unref(G_OBJECT(status_box
->typing_pixbufs
[i
]));
1212 if (typing_stock_ids
[i
])
1213 status_box
->typing_pixbufs
[i
] = gtk_widget_render_icon (GTK_WIDGET(status_box
->vbox
),
1214 typing_stock_ids
[i
], icon_size
, "PidginStatusBox");
1216 status_box
->typing_pixbufs
[i
] = NULL
;
1218 status_box
->typing_index
= 0;
1221 static void account_enabled_cb(PurpleAccount
*acct
, PidginStatusBox
*status_box
)
1223 PurpleAccount
*initial_token_acct
= status_box
->token_status_account
;
1225 if (status_box
->account
)
1228 status_box
->token_status_account
= check_active_accounts_for_identical_statuses();
1230 /* Regenerate the list if it has changed */
1231 if (initial_token_acct
!= status_box
->token_status_account
) {
1232 pidgin_status_box_regenerate(status_box
, TRUE
);
1238 current_savedstatus_changed_cb(PurpleSavedStatus
*now
, PurpleSavedStatus
*old
, PidginStatusBox
*status_box
)
1240 /* Make sure our current status is added to the list of popular statuses */
1241 pidgin_status_box_regenerate(status_box
, TRUE
);
1245 saved_status_updated_cb(PurpleSavedStatus
*status
, PidginStatusBox
*status_box
)
1247 pidgin_status_box_regenerate(status_box
,
1248 purple_savedstatus_get_current() == status
);
1252 spellcheck_prefs_cb(const char *name
, PurplePrefType type
,
1253 gconstpointer value
, gpointer data
)
1256 PidginStatusBox
*status_box
= (PidginStatusBox
*)data
;
1259 pidgin_setup_gtkspell(GTK_TEXT_VIEW(status_box
->imhtml
));
1263 spell
= gtkspell_get_from_text_view(GTK_TEXT_VIEW(status_box
->imhtml
));
1264 gtkspell_detach(spell
);
1270 static gboolean
button_released_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
1273 if (event
->button
!= 1)
1275 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box
->toggle_button
), FALSE
);
1276 if (!box
->imhtml_visible
)
1277 g_signal_emit_by_name(G_OBJECT(box
), "changed", NULL
, NULL
);
1281 static gboolean
button_pressed_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
1283 if (event
->button
!= 1)
1285 gtk_combo_box_popup(GTK_COMBO_BOX(box
));
1286 /* Disabled until button_released_cb works */
1288 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box
->toggle_button
), TRUE
);
1295 pidgin_status_box_list_position (PidginStatusBox
*status_box
, int *x
, int *y
, int *width
, int *height
)
1299 GdkRectangle monitor
;
1300 GtkRequisition popup_req
;
1301 GtkPolicyType hpolicy
, vpolicy
;
1303 gdk_window_get_origin (GTK_WIDGET(status_box
)->window
, x
, y
);
1305 *x
+= GTK_WIDGET(status_box
)->allocation
.x
;
1306 *y
+= GTK_WIDGET(status_box
)->allocation
.y
;
1308 *width
= GTK_WIDGET(status_box
)->allocation
.width
;
1310 hpolicy
= vpolicy
= GTK_POLICY_NEVER
;
1311 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (status_box
->scrolled_window
),
1313 gtk_widget_size_request (status_box
->popup_frame
, &popup_req
);
1315 if (popup_req
.width
> *width
)
1317 hpolicy
= GTK_POLICY_ALWAYS
;
1318 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (status_box
->scrolled_window
),
1320 gtk_widget_size_request (status_box
->popup_frame
, &popup_req
);
1323 *height
= popup_req
.height
;
1325 screen
= gtk_widget_get_screen (GTK_WIDGET (status_box
));
1326 monitor_num
= gdk_screen_get_monitor_at_window (screen
,
1327 GTK_WIDGET (status_box
)->window
);
1328 gdk_screen_get_monitor_geometry (screen
, monitor_num
, &monitor
);
1332 else if (*x
+ *width
> monitor
.x
+ monitor
.width
)
1333 *x
= monitor
.x
+ monitor
.width
- *width
;
1335 if (*y
+ GTK_WIDGET(status_box
)->allocation
.height
+ *height
<= monitor
.y
+ monitor
.height
)
1336 *y
+= GTK_WIDGET(status_box
)->allocation
.height
;
1337 else if (*y
- *height
>= monitor
.y
)
1339 else if (monitor
.y
+ monitor
.height
- (*y
+ GTK_WIDGET(status_box
)->allocation
.height
) > *y
- monitor
.y
)
1341 *y
+= GTK_WIDGET(status_box
)->allocation
.height
;
1342 *height
= monitor
.y
+ monitor
.height
- *y
;
1346 *height
= *y
- monitor
.y
;
1350 if (popup_req
.height
> *height
)
1352 vpolicy
= GTK_POLICY_ALWAYS
;
1354 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (status_box
->scrolled_window
),
1360 popup_grab_on_window (GdkWindow
*window
,
1361 guint32 activate_time
,
1362 gboolean grab_keyboard
)
1364 if ((gdk_pointer_grab (window
, TRUE
,
1365 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
1366 GDK_POINTER_MOTION_MASK
,
1367 NULL
, NULL
, activate_time
) == 0))
1369 if (!grab_keyboard
|| gdk_keyboard_grab (window
, TRUE
, activate_time
) == 0)
1372 gdk_display_pointer_ungrab (gdk_drawable_get_display (window
), activate_time
);
1382 pidgin_status_box_popup(PidginStatusBox
*box
)
1384 int width
, height
, x
, y
;
1385 pidgin_status_box_list_position (box
, &x
, &y
, &width
, &height
);
1387 gtk_widget_set_size_request (box
->popup_window
, width
, height
);
1388 gtk_window_move (GTK_WINDOW (box
->popup_window
), x
, y
);
1389 gtk_widget_show(box
->popup_window
);
1390 gtk_widget_grab_focus (box
->tree_view
);
1391 if (!popup_grab_on_window (box
->popup_window
->window
,
1392 GDK_CURRENT_TIME
, TRUE
)) {
1393 gtk_widget_hide (box
->popup_window
);
1396 gtk_grab_add (box
->popup_window
);
1397 /*box->popup_in_progress = TRUE;*/
1398 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box
->toggle_button
),
1401 if (box
->active_row
) {
1402 GtkTreePath
*path
= gtk_tree_row_reference_get_path(box
->active_row
);
1403 gtk_tree_view_set_cursor(GTK_TREE_VIEW(box
->tree_view
), path
, NULL
, FALSE
);
1404 gtk_tree_path_free(path
);
1409 pidgin_status_box_popdown(PidginStatusBox
*box
)
1411 gtk_widget_hide(box
->popup_window
);
1412 box
->popup_in_progress
= FALSE
;
1413 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box
->toggle_button
),
1415 gtk_grab_remove (box
->popup_window
);
1419 toggle_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginStatusBox
*box
)
1421 switch (event
->keyval
) {
1426 if (!box
->popup_in_progress
) {
1427 pidgin_status_box_popup (box
);
1428 box
->popup_in_progress
= TRUE
;
1430 pidgin_status_box_popdown(box
);
1439 toggled_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
1441 if (!box
->popup_in_progress
)
1442 pidgin_status_box_popup (box
);
1444 pidgin_status_box_popdown(box
);
1449 buddy_icon_set_cb(const char *filename
, PidginStatusBox
*box
)
1451 PurpleStoredImage
*img
= NULL
;
1454 PurplePlugin
*plug
= purple_find_prpl(purple_account_get_protocol_id(box
->account
));
1456 PurplePluginProtocolInfo
*prplinfo
= PURPLE_PLUGIN_PROTOCOL_INFO(plug
);
1457 if (prplinfo
&& prplinfo
->icon_spec
.format
) {
1458 gpointer data
= NULL
;
1461 data
= pidgin_convert_buddy_icon(plug
, filename
, &len
);
1462 img
= purple_buddy_icons_set_account_icon(box
->account
, data
, len
);
1465 * set_account_icon doesn't give us a reference, but we
1466 * unref one below (for the other code path)
1468 purple_imgstore_ref(img
);
1470 purple_account_set_buddy_icon_path(box
->account
, filename
);
1472 purple_account_set_bool(box
->account
, "use-global-buddyicon", (filename
!= NULL
));
1477 for (accounts
= purple_accounts_get_all(); accounts
!= NULL
; accounts
= accounts
->next
) {
1478 PurpleAccount
*account
= accounts
->data
;
1479 PurplePlugin
*plug
= purple_find_prpl(purple_account_get_protocol_id(account
));
1481 PurplePluginProtocolInfo
*prplinfo
= PURPLE_PLUGIN_PROTOCOL_INFO(plug
);
1482 if (prplinfo
!= NULL
&&
1483 purple_account_get_bool(account
, "use-global-buddyicon", TRUE
) &&
1484 prplinfo
->icon_spec
.format
) {
1485 gpointer data
= NULL
;
1488 data
= pidgin_convert_buddy_icon(plug
, filename
, &len
);
1489 purple_buddy_icons_set_account_icon(account
, data
, len
);
1490 purple_account_set_buddy_icon_path(account
, filename
);
1495 /* Even if no accounts were processed, load the icon that was set. */
1496 if (filename
!= NULL
)
1497 img
= purple_imgstore_new_from_file(filename
);
1500 pidgin_status_box_set_buddy_icon(box
, img
);
1502 purple_imgstore_unref(img
);
1506 remove_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
)
1508 if (box
->account
== NULL
)
1509 /* The pref-connect callback does the actual work */
1510 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon", NULL
);
1512 buddy_icon_set_cb(NULL
, box
);
1514 gtk_widget_destroy(box
->icon_box_menu
);
1515 box
->icon_box_menu
= NULL
;
1519 choose_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
)
1521 if (box
->buddy_icon_sel
) {
1522 gtk_window_present(GTK_WINDOW(box
->buddy_icon_sel
));
1524 box
->buddy_icon_sel
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtk_widget_get_toplevel(w
)), icon_choose_cb
, box
);
1525 gtk_widget_show_all(box
->buddy_icon_sel
);
1530 icon_choose_cb(const char *filename
, gpointer data
)
1532 PidginStatusBox
*box
= data
;
1534 if (box
->account
== NULL
)
1535 /* The pref-connect callback does the actual work */
1536 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon", filename
);
1538 buddy_icon_set_cb(filename
, box
);
1541 box
->buddy_icon_sel
= NULL
;
1545 update_buddyicon_cb(const char *name
, PurplePrefType type
,
1546 gconstpointer value
, gpointer data
)
1548 buddy_icon_set_cb(value
, (PidginStatusBox
*) data
);
1552 treeview_activate_current_selection(PidginStatusBox
*status_box
, GtkTreePath
*path
)
1554 if (status_box
->active_row
)
1555 gtk_tree_row_reference_free(status_box
->active_row
);
1557 status_box
->active_row
= gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box
->dropdown_store
), path
);
1558 pidgin_status_box_popdown (status_box
);
1559 pidgin_status_box_changed(status_box
);
1562 static void tree_view_delete_current_selection_cb(gpointer data
)
1564 PurpleSavedStatus
*saved
;
1566 saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
1567 g_return_if_fail(saved
!= NULL
);
1569 if (purple_savedstatus_get_current() != saved
)
1570 purple_savedstatus_delete_by_status(saved
);
1574 tree_view_delete_current_selection(PidginStatusBox
*status_box
, GtkTreePath
*path
)
1578 PurpleSavedStatus
*saved
;
1581 if (status_box
->active_row
) {
1582 /* don't delete active status */
1583 if (gtk_tree_path_compare(path
, gtk_tree_row_reference_get_path(status_box
->active_row
)) == 0)
1587 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
1590 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
1594 saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
1595 g_return_if_fail(saved
!= NULL
);
1596 if (saved
== purple_savedstatus_get_current())
1599 msg
= g_strdup_printf(_("Are you sure you want to delete %s?"), purple_savedstatus_get_title(saved
));
1601 purple_request_action(saved
, NULL
, msg
, NULL
, 0,
1604 _("Delete"), tree_view_delete_current_selection_cb
,
1609 pidgin_status_box_popdown(status_box
);
1613 treeview_button_release_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*status_box
)
1615 GtkTreePath
*path
= NULL
;
1617 GtkWidget
*ewidget
= gtk_get_event_widget ((GdkEvent
*)event
);
1619 if (ewidget
!= status_box
->tree_view
) {
1620 if (ewidget
== status_box
->toggle_button
&&
1621 status_box
->popup_in_progress
&&
1622 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (status_box
->toggle_button
))) {
1623 pidgin_status_box_popdown (status_box
);
1625 } else if (ewidget
== status_box
->toggle_button
) {
1626 status_box
->popup_in_progress
= TRUE
;
1629 /* released outside treeview */
1630 if (ewidget
!= status_box
->toggle_button
) {
1631 pidgin_status_box_popdown (status_box
);
1638 ret
= gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (status_box
->tree_view
),
1644 return TRUE
; /* clicked outside window? */
1646 treeview_activate_current_selection(status_box
, path
);
1647 gtk_tree_path_free (path
);
1653 treeview_key_press_event(GtkWidget
*widget
,
1654 GdkEventKey
*event
, PidginStatusBox
*box
)
1656 if (box
->popup_in_progress
) {
1657 if (event
->keyval
== GDK_Escape
) {
1658 pidgin_status_box_popdown(box
);
1661 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(box
->tree_view
));
1665 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
)) {
1666 gboolean ret
= TRUE
;
1667 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(box
->dropdown_store
), &iter
);
1668 if (event
->keyval
== GDK_Return
) {
1669 treeview_activate_current_selection(box
, path
);
1670 } else if (event
->keyval
== GDK_Delete
) {
1671 tree_view_delete_current_selection(box
, path
);
1675 gtk_tree_path_free (path
);
1684 imhtml_cursor_moved_cb(gpointer data
, GtkMovementStep step
, gint count
, gboolean extend
,
1687 /* Restart the typing timeout if arrow keys are pressed while editing the message */
1688 PidginStatusBox
*status_box
= data
;
1689 if (status_box
->typing
== 0)
1691 imhtml_changed_cb(NULL
, status_box
);
1695 treeview_cursor_changed_cb(GtkTreeView
*treeview
, gpointer data
)
1697 GtkTreeSelection
*sel
= gtk_tree_view_get_selection (treeview
);
1698 GtkTreeModel
*model
= GTK_TREE_MODEL (data
);
1700 GtkTreePath
*cursor
;
1701 GtkTreePath
*selection
;
1704 if (gtk_tree_selection_get_selected (sel
, NULL
, &iter
)) {
1705 if ((selection
= gtk_tree_model_get_path (model
, &iter
)) == NULL
) {
1706 /* Shouldn't happen, but ignore anyway */
1710 /* I don't think this can happen, but we'll just ignore it */
1714 gtk_tree_view_get_cursor (treeview
, &cursor
, NULL
);
1715 if (cursor
== NULL
) {
1716 /* Probably won't happen in a 'cursor-changed' event? */
1717 gtk_tree_path_free (selection
);
1721 cmp
= gtk_tree_path_compare (cursor
, selection
);
1723 /* The cursor moved up without moving the selection, so move it up again */
1724 gtk_tree_path_prev (cursor
);
1725 gtk_tree_view_set_cursor (treeview
, cursor
, NULL
, FALSE
);
1726 } else if (cmp
> 0) {
1727 /* The cursor moved down without moving the selection, so move it down again */
1728 gtk_tree_path_next (cursor
);
1729 gtk_tree_view_set_cursor (treeview
, cursor
, NULL
, FALSE
);
1732 gtk_tree_path_free (selection
);
1733 gtk_tree_path_free (cursor
);
1737 pidgin_status_box_init (PidginStatusBox
*status_box
)
1739 GtkCellRenderer
*text_rend
;
1740 GtkCellRenderer
*icon_rend
;
1741 GtkCellRenderer
*emblem_rend
;
1742 GtkTextBuffer
*buffer
;
1743 GtkWidget
*toplevel
;
1744 GtkTreeSelection
*sel
;
1746 GTK_WIDGET_SET_FLAGS (status_box
, GTK_NO_WINDOW
);
1747 status_box
->imhtml_visible
= FALSE
;
1748 status_box
->network_available
= purple_network_is_available();
1749 status_box
->connecting
= FALSE
;
1750 status_box
->typing
= 0;
1751 status_box
->toggle_button
= gtk_toggle_button_new();
1752 status_box
->hbox
= gtk_hbox_new(FALSE
, 6);
1753 status_box
->cell_view
= gtk_cell_view_new();
1754 status_box
->vsep
= gtk_vseparator_new();
1755 status_box
->arrow
= gtk_arrow_new (GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
1757 status_box
->store
= gtk_list_store_new(NUM_COLUMNS
, G_TYPE_INT
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
, G_TYPE_STRING
,
1758 G_TYPE_STRING
, G_TYPE_POINTER
, GDK_TYPE_PIXBUF
, G_TYPE_BOOLEAN
);
1759 status_box
->dropdown_store
= gtk_list_store_new(NUM_COLUMNS
, G_TYPE_INT
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
1760 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_POINTER
, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
1762 gtk_cell_view_set_model(GTK_CELL_VIEW(status_box
->cell_view
), GTK_TREE_MODEL(status_box
->store
));
1763 gtk_list_store_append(status_box
->store
, &(status_box
->iter
));
1765 atk_object_set_name(gtk_widget_get_accessible(status_box
->toggle_button
), _("Status Selector"));
1767 gtk_container_add(GTK_CONTAINER(status_box
->toggle_button
), status_box
->hbox
);
1768 gtk_box_pack_start(GTK_BOX(status_box
->hbox
), status_box
->cell_view
, TRUE
, TRUE
, 0);
1769 gtk_box_pack_start(GTK_BOX(status_box
->hbox
), status_box
->vsep
, FALSE
, FALSE
, 0);
1770 gtk_box_pack_start(GTK_BOX(status_box
->hbox
), status_box
->arrow
, FALSE
, FALSE
, 0);
1771 gtk_widget_show_all(status_box
->toggle_button
);
1772 gtk_button_set_focus_on_click(GTK_BUTTON(status_box
->toggle_button
), FALSE
);
1774 text_rend
= gtk_cell_renderer_text_new();
1775 icon_rend
= gtk_cell_renderer_pixbuf_new();
1776 emblem_rend
= gtk_cell_renderer_pixbuf_new();
1777 status_box
->popup_window
= gtk_window_new (GTK_WINDOW_POPUP
);
1779 toplevel
= gtk_widget_get_toplevel (GTK_WIDGET (status_box
));
1780 if (GTK_IS_WINDOW (toplevel
)) {
1781 gtk_window_set_transient_for (GTK_WINDOW (status_box
->popup_window
),
1782 GTK_WINDOW (toplevel
));
1785 gtk_window_set_resizable (GTK_WINDOW (status_box
->popup_window
), FALSE
);
1786 gtk_window_set_type_hint (GTK_WINDOW (status_box
->popup_window
),
1787 GDK_WINDOW_TYPE_HINT_POPUP_MENU
);
1788 gtk_window_set_screen (GTK_WINDOW (status_box
->popup_window
),
1789 gtk_widget_get_screen (GTK_WIDGET (status_box
)));
1790 status_box
->popup_frame
= gtk_frame_new (NULL
);
1791 gtk_frame_set_shadow_type (GTK_FRAME (status_box
->popup_frame
),
1792 GTK_SHADOW_ETCHED_IN
);
1793 gtk_container_add (GTK_CONTAINER (status_box
->popup_window
),
1794 status_box
->popup_frame
);
1796 gtk_widget_show (status_box
->popup_frame
);
1798 status_box
->scrolled_window
= gtk_scrolled_window_new (NULL
, NULL
);
1800 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (status_box
->scrolled_window
),
1803 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (status_box
->scrolled_window
),
1806 gtk_widget_show (status_box
->scrolled_window
);
1808 gtk_container_add (GTK_CONTAINER (status_box
->popup_frame
),
1809 status_box
->scrolled_window
);
1811 status_box
->tree_view
= gtk_tree_view_new ();
1812 sel
= gtk_tree_view_get_selection (GTK_TREE_VIEW (status_box
->tree_view
));
1813 gtk_tree_selection_set_mode (sel
, GTK_SELECTION_BROWSE
);
1814 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (status_box
->tree_view
),
1816 gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (status_box
->tree_view
),
1818 gtk_tree_view_set_model (GTK_TREE_VIEW (status_box
->tree_view
),
1819 GTK_TREE_MODEL(status_box
->dropdown_store
));
1820 status_box
->column
= gtk_tree_view_column_new ();
1821 gtk_tree_view_append_column (GTK_TREE_VIEW (status_box
->tree_view
),
1822 status_box
->column
);
1823 gtk_tree_view_column_pack_start(status_box
->column
, icon_rend
, FALSE
);
1824 gtk_tree_view_column_pack_start(status_box
->column
, text_rend
, TRUE
);
1825 gtk_tree_view_column_pack_start(status_box
->column
, emblem_rend
, FALSE
);
1826 gtk_tree_view_column_set_attributes(status_box
->column
, icon_rend
, "stock-id", ICON_STOCK_COLUMN
, NULL
);
1827 gtk_tree_view_column_set_attributes(status_box
->column
, text_rend
, "markup", TEXT_COLUMN
, NULL
);
1828 gtk_tree_view_column_set_attributes(status_box
->column
, emblem_rend
, "stock-id", EMBLEM_COLUMN
, "visible", EMBLEM_VISIBLE_COLUMN
, NULL
);
1829 gtk_container_add(GTK_CONTAINER(status_box
->scrolled_window
), status_box
->tree_view
);
1830 gtk_widget_show(status_box
->tree_view
);
1831 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box
->tree_view
), TEXT_COLUMN
);
1832 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(status_box
->tree_view
),
1833 pidgin_tree_view_search_equal_func
, NULL
, NULL
);
1835 g_object_set(text_rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1837 status_box
->icon_rend
= gtk_cell_renderer_pixbuf_new();
1838 status_box
->text_rend
= gtk_cell_renderer_text_new();
1839 emblem_rend
= gtk_cell_renderer_pixbuf_new();
1840 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->icon_rend
, FALSE
);
1841 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->text_rend
, TRUE
);
1842 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box
->cell_view
), emblem_rend
, FALSE
);
1843 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->icon_rend
, "stock-id", ICON_STOCK_COLUMN
, NULL
);
1844 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->text_rend
, "markup", TEXT_COLUMN
, NULL
);
1845 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box
->cell_view
), emblem_rend
, "pixbuf", EMBLEM_COLUMN
, "visible", EMBLEM_VISIBLE_COLUMN
, NULL
);
1846 g_object_set(status_box
->text_rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1848 status_box
->vbox
= gtk_vbox_new(0, FALSE
);
1849 status_box
->sw
= pidgin_create_imhtml(FALSE
, &status_box
->imhtml
, NULL
, NULL
);
1850 gtk_imhtml_set_editable(GTK_IMHTML(status_box
->imhtml
), TRUE
);
1852 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box
->imhtml
));
1854 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "button-press-event",
1855 G_CALLBACK(button_pressed_cb
), status_box
);
1856 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "button-release-event",
1857 G_CALLBACK(button_released_cb
), status_box
);
1859 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "key-press-event",
1860 G_CALLBACK(toggle_key_press_cb
), status_box
);
1861 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "button-press-event",
1862 G_CALLBACK(toggled_cb
), status_box
);
1863 g_signal_connect(G_OBJECT(buffer
), "changed", G_CALLBACK(imhtml_changed_cb
), status_box
);
1864 g_signal_connect(G_OBJECT(status_box
->imhtml
), "format_function_toggle",
1865 G_CALLBACK(imhtml_format_changed_cb
), status_box
);
1866 g_signal_connect_swapped(G_OBJECT(status_box
->imhtml
), "move_cursor",
1867 G_CALLBACK(imhtml_cursor_moved_cb
), status_box
);
1868 g_signal_connect(G_OBJECT(status_box
->imhtml
), "key_press_event",
1869 G_CALLBACK(imhtml_remove_focus
), status_box
);
1870 g_signal_connect_swapped(G_OBJECT(status_box
->imhtml
), "message_send", G_CALLBACK(remove_typing_cb
), status_box
);
1873 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck"))
1874 pidgin_setup_gtkspell(GTK_TEXT_VIEW(status_box
->imhtml
));
1876 gtk_widget_set_parent(status_box
->vbox
, GTK_WIDGET(status_box
));
1877 gtk_widget_show_all(status_box
->vbox
);
1879 gtk_widget_set_parent(status_box
->toggle_button
, GTK_WIDGET(status_box
));
1881 gtk_box_pack_start(GTK_BOX(status_box
->vbox
), status_box
->sw
, TRUE
, TRUE
, 0);
1883 g_signal_connect(G_OBJECT(status_box
), "scroll_event", G_CALLBACK(combo_box_scroll_event_cb
), NULL
);
1884 g_signal_connect(G_OBJECT(status_box
->imhtml
), "scroll_event",
1885 G_CALLBACK(imhtml_scroll_event_cb
), status_box
->imhtml
);
1886 g_signal_connect(G_OBJECT(status_box
->popup_window
), "button_release_event", G_CALLBACK(treeview_button_release_cb
), status_box
);
1887 g_signal_connect(G_OBJECT(status_box
->popup_window
), "key_press_event", G_CALLBACK(treeview_key_press_event
), status_box
);
1888 g_signal_connect(G_OBJECT(status_box
->tree_view
), "cursor-changed",
1889 G_CALLBACK(treeview_cursor_changed_cb
), status_box
->dropdown_store
);
1891 gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(status_box
->tree_view
), dropdown_store_row_separator_func
, NULL
, NULL
);
1893 status_box
->token_status_account
= check_active_accounts_for_identical_statuses();
1895 cache_pixbufs(status_box
);
1896 pidgin_status_box_regenerate(status_box
, TRUE
);
1898 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
1900 PURPLE_CALLBACK(current_savedstatus_changed_cb
),
1902 purple_signal_connect(purple_savedstatuses_get_handle(),
1903 "savedstatus-added", status_box
,
1904 PURPLE_CALLBACK(saved_status_updated_cb
), status_box
);
1905 purple_signal_connect(purple_savedstatuses_get_handle(),
1906 "savedstatus-deleted", status_box
,
1907 PURPLE_CALLBACK(saved_status_updated_cb
), status_box
);
1908 purple_signal_connect(purple_savedstatuses_get_handle(),
1909 "savedstatus-modified", status_box
,
1910 PURPLE_CALLBACK(saved_status_updated_cb
), status_box
);
1911 purple_signal_connect(purple_accounts_get_handle(), "account-enabled", status_box
,
1912 PURPLE_CALLBACK(account_enabled_cb
),
1914 purple_signal_connect(purple_accounts_get_handle(), "account-disabled", status_box
,
1915 PURPLE_CALLBACK(account_enabled_cb
),
1917 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", status_box
,
1918 PURPLE_CALLBACK(account_status_changed_cb
),
1921 purple_prefs_connect_callback(status_box
, PIDGIN_PREFS_ROOT
"/conversations/spellcheck",
1922 spellcheck_prefs_cb
, status_box
);
1923 purple_prefs_connect_callback(status_box
, PIDGIN_PREFS_ROOT
"/accounts/buddyicon",
1924 update_buddyicon_cb
, status_box
);
1925 purple_signal_connect(purple_get_core(), "uri-handler", status_box
,
1926 PURPLE_CALLBACK(statusbox_uri_handler
), status_box
);
1931 pidgin_status_box_size_request(GtkWidget
*widget
,
1932 GtkRequisition
*requisition
)
1934 GtkRequisition box_req
;
1935 gint border_width
= GTK_CONTAINER (widget
)->border_width
;
1937 gtk_widget_size_request(PIDGIN_STATUS_BOX(widget
)->toggle_button
, requisition
);
1939 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
1940 requisition
->height
= MAX(requisition
->height
, 34);
1941 requisition
->height
+= border_width
* 2;
1943 /* If the gtkimhtml is visible, then add some additional padding */
1944 gtk_widget_size_request(PIDGIN_STATUS_BOX(widget
)->vbox
, &box_req
);
1945 if (box_req
.height
> 1)
1946 requisition
->height
+= box_req
.height
+ border_width
* 2;
1948 requisition
->width
= 1;
1951 /* From gnome-panel */
1953 do_colorshift (GdkPixbuf
*dest
, GdkPixbuf
*src
, int shift
)
1956 gint width
, height
, has_alpha
, srcrowstride
, destrowstride
;
1957 guchar
*target_pixels
;
1958 guchar
*original_pixels
;
1964 has_alpha
= gdk_pixbuf_get_has_alpha (src
);
1965 width
= gdk_pixbuf_get_width (src
);
1966 height
= gdk_pixbuf_get_height (src
);
1967 srcrowstride
= gdk_pixbuf_get_rowstride (src
);
1968 destrowstride
= gdk_pixbuf_get_rowstride (dest
);
1969 target_pixels
= gdk_pixbuf_get_pixels (dest
);
1970 original_pixels
= gdk_pixbuf_get_pixels (src
);
1972 for (i
= 0; i
< height
; i
++) {
1973 pixdest
= target_pixels
+ i
*destrowstride
;
1974 pixsrc
= original_pixels
+ i
*srcrowstride
;
1975 for (j
= 0; j
< width
; j
++) {
1980 *(pixdest
++) = CLAMP(val
, 0, 255);
1982 *(pixdest
++) = CLAMP(val
, 0, 255);
1984 *(pixdest
++) = CLAMP(val
, 0, 255);
1986 *(pixdest
++) = *(pixsrc
++);
1992 pidgin_status_box_size_allocate(GtkWidget
*widget
,
1993 GtkAllocation
*allocation
)
1995 PidginStatusBox
*status_box
= PIDGIN_STATUS_BOX(widget
);
1996 GtkRequisition req
= {0,0};
1997 GtkAllocation parent_alc
, box_alc
, icon_alc
;
1998 gint border_width
= GTK_CONTAINER (widget
)->border_width
;
2000 gtk_widget_size_request(status_box
->toggle_button
, &req
);
2001 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
2003 req
.height
= MAX(req
.height
, 34);
2004 req
.height
+= border_width
* 2;
2006 box_alc
= *allocation
;
2008 box_alc
.width
-= (border_width
* 2);
2009 box_alc
.height
= MAX(1, ((allocation
->height
- req
.height
) - (border_width
*2)));
2010 box_alc
.x
+= border_width
;
2011 box_alc
.y
+= req
.height
+ border_width
;
2012 gtk_widget_size_allocate(status_box
->vbox
, &box_alc
);
2014 parent_alc
= *allocation
;
2015 parent_alc
.height
= MAX(1,req
.height
- (border_width
*2));
2016 parent_alc
.width
-= (border_width
* 2);
2017 parent_alc
.x
+= border_width
;
2018 parent_alc
.y
+= border_width
;
2020 if (status_box
->icon_box
)
2022 parent_alc
.width
-= (parent_alc
.height
+ border_width
);
2023 icon_alc
= parent_alc
;
2024 icon_alc
.height
= MAX(1, icon_alc
.height
) - 2;
2025 icon_alc
.width
= icon_alc
.height
;
2026 icon_alc
.x
= allocation
->width
- (icon_alc
.width
+ border_width
+ 1);
2029 if (status_box
->icon_size
!= icon_alc
.height
)
2031 status_box
->icon_size
= icon_alc
.height
;
2032 pidgin_status_box_redisplay_buddy_icon(status_box
);
2034 gtk_widget_size_allocate(status_box
->icon_box
, &icon_alc
);
2036 gtk_widget_size_allocate(status_box
->toggle_button
, &parent_alc
);
2037 widget
->allocation
= *allocation
;
2041 pidgin_status_box_expose_event(GtkWidget
*widget
,
2042 GdkEventExpose
*event
)
2044 PidginStatusBox
*status_box
= PIDGIN_STATUS_BOX(widget
);
2045 gtk_container_propagate_expose(GTK_CONTAINER(widget
), status_box
->vbox
, event
);
2046 gtk_container_propagate_expose(GTK_CONTAINER(widget
), status_box
->toggle_button
, event
);
2047 if (status_box
->icon_box
&& status_box
->icon_opaque
) {
2048 gtk_paint_box(widget
->style
, widget
->window
, GTK_STATE_NORMAL
, GTK_SHADOW_OUT
, NULL
,
2049 status_box
->icon_box
, "button", status_box
->icon_box
->allocation
.x
-1, status_box
->icon_box
->allocation
.y
-1,
2056 pidgin_status_box_forall(GtkContainer
*container
,
2057 gboolean include_internals
,
2058 GtkCallback callback
,
2059 gpointer callback_data
)
2061 PidginStatusBox
*status_box
= PIDGIN_STATUS_BOX (container
);
2063 if (include_internals
)
2065 (* callback
) (status_box
->vbox
, callback_data
);
2066 (* callback
) (status_box
->toggle_button
, callback_data
);
2067 (* callback
) (status_box
->arrow
, callback_data
);
2068 if (status_box
->icon_box
)
2069 (* callback
) (status_box
->icon_box
, callback_data
);
2074 pidgin_status_box_new()
2076 return g_object_new(PIDGIN_TYPE_STATUS_BOX
, "account", NULL
,
2077 "iconsel", TRUE
, NULL
);
2081 pidgin_status_box_new_with_account(PurpleAccount
*account
)
2083 return g_object_new(PIDGIN_TYPE_STATUS_BOX
, "account", account
,
2084 "iconsel", TRUE
, NULL
);
2088 * Add a row to the dropdown menu.
2090 * @param status_box The status box itself.
2091 * @param type A PidginStatusBoxItemType.
2092 * @param pixbuf The icon to associate with this row in the menu. The
2093 * function will try to decide a pixbuf if none is given.
2094 * @param title The title of this item. For the primitive entries,
2095 * this is something like "Available" or "Away." For
2096 * the saved statuses, this is something like
2097 * "My favorite away message!" This should be
2098 * plaintext (non-markedup) (this function escapes it).
2099 * @param desc The secondary text for this item. This will be
2100 * placed on the row below the title, in a dimmer
2101 * font (generally gray). This text should be plaintext
2102 * (non-markedup) (this function escapes it).
2103 * @param data Data to be associated with this row in the dropdown
2104 * menu. For primitives this is the value of the
2105 * PurpleStatusPrimitive. For saved statuses this is the
2106 * creation timestamp.
2109 pidgin_status_box_add(PidginStatusBox
*status_box
, PidginStatusBoxItemType type
, GdkPixbuf
*pixbuf
,
2110 const char *title
, const char *desc
, gpointer data
)
2114 const char *stock
= NULL
;
2118 text
= g_markup_escape_text(title
, -1);
2124 gchar
*escaped_title
, *escaped_desc
;
2126 style
= gtk_widget_get_style(GTK_WIDGET(status_box
));
2127 snprintf(aa_color
, sizeof(aa_color
), "#%02x%02x%02x",
2128 style
->text_aa
[GTK_STATE_NORMAL
].red
>> 8,
2129 style
->text_aa
[GTK_STATE_NORMAL
].green
>> 8,
2130 style
->text_aa
[GTK_STATE_NORMAL
].blue
>> 8);
2132 escaped_title
= g_markup_escape_text(title
, -1);
2133 escaped_desc
= g_markup_escape_text(desc
, -1);
2134 text
= g_strdup_printf("%s - <span color=\"%s\" size=\"smaller\">%s</span>",
2136 aa_color
, escaped_desc
);
2137 g_free(escaped_title
);
2138 g_free(escaped_desc
);
2142 PurpleStatusPrimitive prim
= PURPLE_STATUS_UNSET
;
2143 if (type
== PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
) {
2144 prim
= GPOINTER_TO_INT(data
);
2145 } else if (type
== PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
||
2146 type
== PIDGIN_STATUS_BOX_TYPE_POPULAR
) {
2147 PurpleSavedStatus
*saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
2149 prim
= purple_savedstatus_get_type(saved
);
2153 stock
= pidgin_stock_id_from_status_primitive(prim
);
2156 gtk_list_store_append(status_box
->dropdown_store
, &iter
);
2157 gtk_list_store_set(status_box
->dropdown_store
, &iter
,
2159 ICON_STOCK_COLUMN
, stock
,
2161 TITLE_COLUMN
, title
,
2164 EMBLEM_VISIBLE_COLUMN
, type
== PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
,
2165 EMBLEM_COLUMN
, GTK_STOCK_SAVE
,
2171 pidgin_status_box_add_separator(PidginStatusBox
*status_box
)
2173 /* Don't do anything unless GTK actually supports
2174 * gtk_combo_box_set_row_separator_func */
2177 gtk_list_store_append(status_box
->dropdown_store
, &iter
);
2178 gtk_list_store_set(status_box
->dropdown_store
, &iter
,
2179 TYPE_COLUMN
, PIDGIN_STATUS_BOX_TYPE_SEPARATOR
,
2184 pidgin_status_box_set_network_available(PidginStatusBox
*status_box
, gboolean available
)
2188 status_box
->network_available
= available
;
2189 pidgin_status_box_refresh(status_box
);
2193 pidgin_status_box_set_connecting(PidginStatusBox
*status_box
, gboolean connecting
)
2197 status_box
->connecting
= connecting
;
2198 pidgin_status_box_refresh(status_box
);
2202 pixbuf_size_prepared_cb(GdkPixbufLoader
*loader
, int width
, int height
, gpointer data
)
2205 GtkIconSize icon_size
= gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
);
2206 gtk_icon_size_lookup(icon_size
, &w
, &h
);
2208 w
= width
* h
/ height
;
2209 else if (width
> height
)
2210 h
= height
* w
/ width
;
2211 gdk_pixbuf_loader_set_size(loader
, w
, h
);
2215 pidgin_status_box_redisplay_buddy_icon(PidginStatusBox
*status_box
)
2218 /* This is sometimes called before the box is shown, and we will not have a size */
2219 if (status_box
->icon_size
<= 0)
2222 if (status_box
->buddy_icon
)
2223 g_object_unref(status_box
->buddy_icon
);
2224 if (status_box
->buddy_icon_hover
)
2225 g_object_unref(status_box
->buddy_icon_hover
);
2226 status_box
->buddy_icon
= NULL
;
2227 status_box
->buddy_icon_hover
= NULL
;
2229 if (status_box
->buddy_icon_img
!= NULL
)
2231 GdkPixbuf
*buf
, *scale
;
2232 int scale_width
, scale_height
;
2233 GdkPixbufLoader
*loader
= gdk_pixbuf_loader_new();
2234 g_signal_connect(G_OBJECT(loader
), "size-prepared", G_CALLBACK(pixbuf_size_prepared_cb
), NULL
);
2235 gdk_pixbuf_loader_write(loader
, purple_imgstore_get_data(status_box
->buddy_icon_img
),
2236 purple_imgstore_get_size(status_box
->buddy_icon_img
), NULL
);
2237 gdk_pixbuf_loader_close(loader
, NULL
);
2238 buf
= gdk_pixbuf_loader_get_pixbuf(loader
);
2239 scale_width
= gdk_pixbuf_get_width(buf
);
2240 scale_height
= gdk_pixbuf_get_height(buf
);
2241 scale
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, scale_width
, scale_height
);
2242 gdk_pixbuf_fill(scale
, 0x00000000);
2243 gdk_pixbuf_copy_area(buf
, 0, 0, scale_width
, scale_height
, scale
, 0, 0);
2244 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2245 pidgin_gdk_pixbuf_make_round(scale
);
2246 status_box
->buddy_icon
= scale
;
2247 g_object_unref(loader
);
2250 if (status_box
->buddy_icon
== NULL
)
2252 /* Show a placeholder icon */
2253 GtkIconSize icon_size
= gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
);
2254 status_box
->buddy_icon
= gtk_widget_render_icon(GTK_WIDGET(status_box
),
2255 PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR
,
2256 icon_size
, "PidginStatusBox");
2259 if (status_box
->buddy_icon
!= NULL
) {
2260 status_box
->icon_opaque
= pidgin_gdk_pixbuf_is_opaque(status_box
->buddy_icon
);
2261 gtk_image_set_from_pixbuf(GTK_IMAGE(status_box
->icon
), status_box
->buddy_icon
);
2262 status_box
->buddy_icon_hover
= gdk_pixbuf_copy(status_box
->buddy_icon
);
2263 do_colorshift(status_box
->buddy_icon_hover
, status_box
->buddy_icon_hover
, 32);
2264 gtk_widget_queue_resize(GTK_WIDGET(status_box
));
2269 pidgin_status_box_set_buddy_icon(PidginStatusBox
*status_box
, PurpleStoredImage
*img
)
2271 purple_imgstore_unref(status_box
->buddy_icon_img
);
2272 status_box
->buddy_icon_img
= img
;
2273 if (status_box
->buddy_icon_img
!= NULL
)
2274 purple_imgstore_ref(status_box
->buddy_icon_img
);
2276 pidgin_status_box_redisplay_buddy_icon(status_box
);
2280 pidgin_status_box_pulse_connecting(PidginStatusBox
*status_box
)
2284 if (!connecting_stock_ids
[++status_box
->connecting_index
])
2285 status_box
->connecting_index
= 0;
2286 pidgin_status_box_refresh(status_box
);
2290 pidgin_status_box_pulse_typing(PidginStatusBox
*status_box
)
2292 if (!typing_stock_ids
[++status_box
->typing_index
])
2293 status_box
->typing_index
= 0;
2294 pidgin_status_box_refresh(status_box
);
2298 activate_currently_selected_status(PidginStatusBox
*status_box
)
2300 PidginStatusBoxItemType type
;
2306 PurpleSavedStatus
*saved_status
= NULL
;
2307 gboolean changed
= TRUE
;
2309 path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
2310 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
2312 gtk_tree_path_free(path
);
2314 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
2320 * If the currently selected status is "New..." or
2321 * "Saved..." or a popular status then do nothing.
2322 * Popular statuses are
2323 * activated elsewhere, and we update the status_box
2324 * accordingly by connecting to the savedstatus-changed
2325 * signal and then calling status_menu_refresh_iter()
2327 if (type
!= PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
)
2330 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
2331 TITLE_COLUMN
, &title
, -1);
2333 message
= pidgin_status_box_get_message(status_box
);
2334 if (!message
|| !*message
)
2336 gtk_widget_hide_all(status_box
->vbox
);
2337 status_box
->imhtml_visible
= FALSE
;
2338 if (message
!= NULL
)
2345 if (status_box
->account
== NULL
) {
2346 PurpleStatusType
*acct_status_type
= NULL
;
2347 const char *id
= NULL
; /* id of acct_status_type */
2348 PurpleStatusPrimitive primitive
= GPOINTER_TO_INT(data
);
2350 /* Save the newly selected status to prefs.xml and status.xml */
2352 /* Has the status really been changed? */
2353 if (status_box
->token_status_account
) {
2355 PurpleStatus
*status
;
2356 GtkTreePath
*path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
2357 active
= gtk_tree_path_get_indices(path
)[0];
2359 gtk_tree_path_free(path
);
2361 status
= purple_account_get_active_status(status_box
->token_status_account
);
2363 acct_status_type
= find_status_type_by_index(status_box
->token_status_account
, active
);
2364 id
= purple_status_type_get_id(acct_status_type
);
2366 if (g_str_equal(id
, purple_status_get_id(status
)) &&
2367 purple_strequal(message
, purple_status_get_attr_string(status
, "message")))
2369 /* Selected status and previous status is the same */
2370 PurpleSavedStatus
*ss
= purple_savedstatus_get_current();
2371 /* Make sure that statusbox displays the correct thing.
2372 * It can get messed up if the previous selection was a
2373 * saved status that wasn't supported by this account */
2374 if ((purple_savedstatus_get_type(ss
) == primitive
)
2375 && purple_savedstatus_is_transient(ss
)
2376 && purple_savedstatus_has_substatuses(ss
))
2380 saved_status
= purple_savedstatus_get_current();
2381 if (purple_savedstatus_get_type(saved_status
) == primitive
&&
2382 !purple_savedstatus_has_substatuses(saved_status
) &&
2383 purple_strequal(purple_savedstatus_get_message(saved_status
), message
))
2391 /* Manually find the appropriate transient status */
2392 if (status_box
->token_status_account
) {
2393 GList
*iter
= purple_savedstatuses_get_all();
2394 GList
*tmp
, *active_accts
= purple_accounts_get_all_active();
2396 for (; iter
!= NULL
; iter
= iter
->next
) {
2397 PurpleSavedStatus
*ss
= iter
->data
;
2398 const char *ss_msg
= purple_savedstatus_get_message(ss
);
2399 /* find a known transient status that is the same as the
2400 * new selected one */
2401 if ((purple_savedstatus_get_type(ss
) == primitive
) && purple_savedstatus_is_transient(ss
) &&
2402 purple_savedstatus_has_substatuses(ss
) && /* Must have substatuses */
2403 purple_strequal(ss_msg
, message
))
2405 gboolean found
= FALSE
;
2406 /* this status must have substatuses for all the active accts */
2407 for(tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
2408 PurpleAccount
*acct
= tmp
->data
;
2409 PurpleSavedStatusSub
*sub
= purple_savedstatus_get_substatus(ss
, acct
);
2411 const PurpleStatusType
*sub_type
= purple_savedstatus_substatus_get_type(sub
);
2412 const char *subtype_status_id
= purple_status_type_get_id(sub_type
);
2413 if (purple_strequal(subtype_status_id
, id
)) {
2427 g_list_free(active_accts
);
2430 /* If we've used this type+message before, lookup the transient status */
2431 saved_status
= purple_savedstatus_find_transient_by_type_and_message(primitive
, message
);
2434 /* If this type+message is unique then create a new transient saved status */
2435 if (saved_status
== NULL
)
2437 saved_status
= purple_savedstatus_new(NULL
, primitive
);
2438 purple_savedstatus_set_message(saved_status
, message
);
2439 if (status_box
->token_status_account
) {
2440 GList
*tmp
, *active_accts
= purple_accounts_get_all_active();
2441 for (tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
2442 purple_savedstatus_set_substatus(saved_status
,
2443 (PurpleAccount
*) tmp
->data
, acct_status_type
, message
);
2445 g_list_free(active_accts
);
2449 /* Set the status for each account */
2450 purple_savedstatus_activate(saved_status
);
2455 PurpleStatusType
*status_type
;
2456 PurpleStatus
*status
;
2457 const char *id
= NULL
;
2459 status
= purple_account_get_active_status(status_box
->account
);
2461 active
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(status_box
), "active"));
2463 status_type
= find_status_type_by_index(status_box
->account
, active
);
2464 id
= purple_status_type_get_id(status_type
);
2466 if (g_str_equal(id
, purple_status_get_id(status
)) &&
2467 purple_strequal(message
, purple_status_get_attr_string(status
, "message")))
2469 /* Selected status and previous status is the same */
2476 purple_account_set_status(status_box
->account
, id
,
2477 TRUE
, "message", message
, NULL
);
2479 purple_account_set_status(status_box
->account
, id
,
2482 saved_status
= purple_savedstatus_get_current();
2483 if (purple_savedstatus_is_transient(saved_status
))
2484 purple_savedstatus_set_substatus(saved_status
, status_box
->account
,
2485 status_type
, message
);
2493 static void update_size(PidginStatusBox
*status_box
)
2495 GtkTextBuffer
*buffer
;
2499 GdkRectangle oneline
;
2501 int pad_top
, pad_inside
, pad_bottom
;
2502 gboolean interior_focus
;
2505 if (!status_box
->imhtml_visible
)
2507 if (status_box
->vbox
!= NULL
)
2508 gtk_widget_set_size_request(status_box
->vbox
, -1, -1);
2512 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box
->imhtml
));
2516 gtk_text_buffer_get_start_iter(buffer
, &iter
);
2518 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box
->imhtml
), &iter
, &oneline
);
2519 height
+= oneline
.height
;
2521 } while (display_lines
<= 4 &&
2522 gtk_text_view_forward_display_line(GTK_TEXT_VIEW(status_box
->imhtml
), &iter
));
2525 * This check fixes the case where the last character entered is a
2526 * newline (shift+return). For some reason the
2527 * gtk_text_view_forward_display_line() function doesn't treat this
2528 * like a new line, and so we think the input box only needs to be
2529 * two lines instead of three, for example. So we check if the
2530 * last character was a newline and add some extra height if so.
2532 if (display_lines
<= 4
2533 && gtk_text_iter_backward_char(&iter
)
2534 && gtk_text_iter_get_char(&iter
) == '\n')
2536 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box
->imhtml
), &iter
, &oneline
);
2537 height
+= oneline
.height
;
2541 lines
= gtk_text_buffer_get_line_count(buffer
);
2543 /* Show a maximum of 4 lines */
2544 lines
= MIN(lines
, 4);
2545 display_lines
= MIN(display_lines
, 4);
2547 pad_top
= gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(status_box
->imhtml
));
2548 pad_bottom
= gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(status_box
->imhtml
));
2549 pad_inside
= gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(status_box
->imhtml
));
2551 height
+= (pad_top
+ pad_bottom
) * lines
;
2552 height
+= (pad_inside
) * (display_lines
- lines
);
2554 gtk_widget_style_get(status_box
->imhtml
,
2555 "interior-focus", &interior_focus
,
2556 "focus-line-width", &focus_width
,
2558 if (!interior_focus
)
2559 height
+= 2 * focus_width
;
2561 gtk_widget_set_size_request(status_box
->vbox
, -1, height
+ PIDGIN_HIG_BOX_SPACE
);
2564 static void remove_typing_cb(PidginStatusBox
*status_box
)
2566 if (status_box
->typing
== 0)
2568 /* Nothing has changed, so we don't need to do anything */
2569 status_menu_refresh_iter(status_box
, FALSE
);
2573 gtk_imhtml_set_populate_primary_clipboard(
2574 GTK_IMHTML(status_box
->imhtml
), TRUE
);
2576 purple_timeout_remove(status_box
->typing
);
2577 status_box
->typing
= 0;
2579 activate_currently_selected_status(status_box
);
2580 pidgin_status_box_refresh(status_box
);
2583 static void pidgin_status_box_changed(PidginStatusBox
*status_box
)
2585 GtkTreePath
*path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
2587 PidginStatusBoxItemType type
;
2589 GList
*accounts
= NULL
, *node
;
2591 gboolean wastyping
= FALSE
;
2594 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
2596 active
= gtk_tree_path_get_indices(path
)[0];
2597 gtk_tree_path_free(path
);
2598 g_object_set_data(G_OBJECT(status_box
), "active", GINT_TO_POINTER(active
));
2600 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
2604 if ((wastyping
= (status_box
->typing
!= 0)))
2605 purple_timeout_remove(status_box
->typing
);
2606 status_box
->typing
= 0;
2608 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box
)))
2610 if (type
== PIDGIN_STATUS_BOX_TYPE_POPULAR
|| type
== PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
)
2612 PurpleSavedStatus
*saved
;
2613 saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
2614 g_return_if_fail(saved
!= NULL
);
2615 purple_savedstatus_activate(saved
);
2619 if (type
== PIDGIN_STATUS_BOX_TYPE_CUSTOM
)
2621 PurpleSavedStatus
*saved_status
;
2622 saved_status
= purple_savedstatus_get_current();
2623 if (purple_savedstatus_get_type(saved_status
) == PURPLE_STATUS_AVAILABLE
)
2624 saved_status
= purple_savedstatus_new(NULL
, PURPLE_STATUS_AWAY
);
2625 pidgin_status_editor_show(FALSE
,
2626 purple_savedstatus_is_transient(saved_status
)
2627 ? saved_status
: NULL
);
2628 status_menu_refresh_iter(status_box
, wastyping
);
2630 pidgin_status_box_refresh(status_box
);
2634 if (type
== PIDGIN_STATUS_BOX_TYPE_SAVED
)
2636 pidgin_status_window_show();
2637 status_menu_refresh_iter(status_box
, wastyping
);
2639 pidgin_status_box_refresh(status_box
);
2645 * Show the message box whenever the primitive allows for a
2646 * message attribute on any protocol that is enabled,
2647 * or our protocol, if we have account set
2649 if (status_box
->account
)
2650 accounts
= g_list_prepend(accounts
, status_box
->account
);
2652 accounts
= purple_accounts_get_all_active();
2653 status_box
->imhtml_visible
= FALSE
;
2654 for (node
= accounts
; node
!= NULL
; node
= node
->next
)
2656 PurpleAccount
*account
;
2657 PurpleStatusType
*status_type
;
2659 account
= node
->data
;
2660 status_type
= purple_account_get_status_type_with_primitive(account
, GPOINTER_TO_INT(data
));
2661 if ((status_type
!= NULL
) &&
2662 (purple_status_type_get_attr(status_type
, "message") != NULL
))
2664 status_box
->imhtml_visible
= TRUE
;
2668 g_list_free(accounts
);
2670 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box
)))
2672 if (status_box
->imhtml_visible
)
2674 GtkTextIter start
, end
;
2675 GtkTextBuffer
*buffer
;
2676 gtk_widget_show_all(status_box
->vbox
);
2677 status_box
->typing
= purple_timeout_add_seconds(TYPING_TIMEOUT
, (GSourceFunc
)remove_typing_cb
, status_box
);
2678 gtk_widget_grab_focus(status_box
->imhtml
);
2679 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box
->imhtml
));
2681 gtk_imhtml_set_populate_primary_clipboard(
2682 GTK_IMHTML(status_box
->imhtml
), FALSE
);
2684 gtk_text_buffer_get_bounds(buffer
, &start
, &end
);
2685 gtk_text_buffer_move_mark(buffer
, gtk_text_buffer_get_mark(buffer
, "insert"), &end
);
2686 gtk_text_buffer_move_mark(buffer
, gtk_text_buffer_get_mark(buffer
, "selection_bound"), &start
);
2690 gtk_widget_hide_all(status_box
->vbox
);
2691 activate_currently_selected_status(status_box
); /* This is where we actually set the status */
2694 pidgin_status_box_refresh(status_box
);
2698 get_statusbox_index(PidginStatusBox
*box
, PurpleSavedStatus
*saved_status
)
2702 switch (purple_savedstatus_get_type(saved_status
))
2704 /* In reverse order */
2705 case PURPLE_STATUS_OFFLINE
:
2707 case PURPLE_STATUS_INVISIBLE
:
2709 case PURPLE_STATUS_UNAVAILABLE
:
2711 case PURPLE_STATUS_AWAY
:
2713 case PURPLE_STATUS_AVAILABLE
:
2723 static void imhtml_changed_cb(GtkTextBuffer
*buffer
, void *data
)
2725 PidginStatusBox
*status_box
= (PidginStatusBox
*)data
;
2726 if (GTK_WIDGET_IS_SENSITIVE(GTK_WIDGET(status_box
)))
2728 if (status_box
->typing
!= 0) {
2729 pidgin_status_box_pulse_typing(status_box
);
2730 purple_timeout_remove(status_box
->typing
);
2732 status_box
->typing
= purple_timeout_add_seconds(TYPING_TIMEOUT
, (GSourceFunc
)remove_typing_cb
, status_box
);
2734 pidgin_status_box_refresh(status_box
);
2737 static void imhtml_format_changed_cb(GtkIMHtml
*imhtml
, GtkIMHtmlButtons buttons
, void *data
)
2739 imhtml_changed_cb(NULL
, data
);
2742 char *pidgin_status_box_get_message(PidginStatusBox
*status_box
)
2744 if (status_box
->imhtml_visible
)
2745 return gtk_imhtml_get_markup(GTK_IMHTML(status_box
->imhtml
));