3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 * The status box is made up of two main pieces:
24 * - The box that displays the current status, which is made
25 * of a GtkListStore ("status_box->store") and GtkCellView
26 * ("status_box->cell_view"). There is always exactly 1 row
27 * in this list store. Only the TYPE_ICON and TYPE_TEXT
28 * columns are used in this list store.
29 * - The dropdown menu that lets users select a status, which
30 * is made of a GtkComboBox ("status_box") and GtkListStore
31 * ("status_box->dropdown_store"). This dropdown is shown
32 * when the user clicks on the box that displays the current
33 * status. This list store contains one row for Available,
34 * one row for Away, etc., a few rows for popular statuses,
35 * and the "New..." and "Saved..." options.
38 #include <gdk/gdkkeysyms.h>
43 #include "buddyicon.h"
47 #include "savedstatuses.h"
52 #include "gtksavedstatuses.h"
53 #include "pidginstock.h"
54 #include "gtkstatusbox.h"
57 #include "gtk3compat.h"
59 /* Timeout for typing notifications in seconds */
60 #define TYPING_TIMEOUT 4
62 static void webview_changed_cb(PidginWebView
*webview
, void *data
);
63 static void webview_format_changed_cb(PidginWebView
*webview
, PidginWebViewButtons buttons
, void *data
);
64 static void remove_typing_cb(PidginStatusBox
*box
);
65 static void update_size (PidginStatusBox
*box
);
66 static gint
get_statusbox_index(PidginStatusBox
*box
, PurpleSavedStatus
*saved_status
);
67 static PurpleAccount
* check_active_accounts_for_identical_statuses(void);
69 static void pidgin_status_box_pulse_typing(PidginStatusBox
*status_box
);
70 static void pidgin_status_box_refresh(PidginStatusBox
*status_box
);
71 static void status_menu_refresh_iter(PidginStatusBox
*status_box
, gboolean status_changed
);
72 static void pidgin_status_box_regenerate(PidginStatusBox
*status_box
, gboolean status_changed
);
73 static void pidgin_status_box_changed(PidginStatusBox
*box
);
74 static void pidgin_status_box_get_preferred_height (GtkWidget
*widget
,
75 gint
*minimum_height
, gint
*natural_height
);
76 static gboolean
pidgin_status_box_draw (GtkWidget
*widget
, cairo_t
*cr
);
77 static void pidgin_status_box_size_allocate (GtkWidget
*widget
, GtkAllocation
*allocation
);
78 static void pidgin_status_box_redisplay_buddy_icon(PidginStatusBox
*status_box
);
79 static void pidgin_status_box_forall (GtkContainer
*container
, gboolean include_internals
, GtkCallback callback
, gpointer callback_data
);
80 static void pidgin_status_box_popup(PidginStatusBox
*box
, GdkEvent
*event
);
81 static void pidgin_status_box_popdown(PidginStatusBox
*box
, GdkEvent
*event
);
83 static void do_colorshift (GdkPixbuf
*dest
, GdkPixbuf
*src
, int shift
);
84 static void icon_choose_cb(const char *filename
, gpointer data
);
85 static void remove_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
);
86 static void choose_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
);
89 /* A PidginStatusBoxItemType */
92 /* This is the stock-id for the icon. */
96 * This is a GdkPixbuf (the other columns are strings).
97 * This column is visible.
101 /* The text displayed on the status box. This column is visible. */
104 /* The plain-English title of this item */
107 /* A plain-English description of this item */
111 * This value depends on TYPE_COLUMN. For POPULAR types,
112 * this is the creation time. For PRIMITIVE types,
113 * this is the PurpleStatusPrimitive.
118 * This column stores the GdkPixbuf for the status emblem. Currently only 'saved' is stored.
119 * In the GtkTreeModel for the dropdown, this is the stock-id (gchararray), and for the
120 * GtkTreeModel for the cell_view (for the account-specific statusbox), this is the protocol icon
121 * (GdkPixbuf) of the account.
126 * This column stores whether to show the emblem.
128 EMBLEM_VISIBLE_COLUMN
,
139 static char *typing_stock_ids
[7] = {
140 PIDGIN_STOCK_ANIMATION_TYPING0
,
141 PIDGIN_STOCK_ANIMATION_TYPING1
,
142 PIDGIN_STOCK_ANIMATION_TYPING2
,
143 PIDGIN_STOCK_ANIMATION_TYPING3
,
144 PIDGIN_STOCK_ANIMATION_TYPING4
,
148 static char *connecting_stock_ids
[] = {
149 PIDGIN_STOCK_ANIMATION_CONNECT0
,
150 PIDGIN_STOCK_ANIMATION_CONNECT1
,
151 PIDGIN_STOCK_ANIMATION_CONNECT2
,
152 PIDGIN_STOCK_ANIMATION_CONNECT3
,
153 PIDGIN_STOCK_ANIMATION_CONNECT4
,
154 PIDGIN_STOCK_ANIMATION_CONNECT5
,
155 PIDGIN_STOCK_ANIMATION_CONNECT6
,
156 PIDGIN_STOCK_ANIMATION_CONNECT7
,
157 PIDGIN_STOCK_ANIMATION_CONNECT8
,
158 PIDGIN_STOCK_ANIMATION_CONNECT9
,
159 PIDGIN_STOCK_ANIMATION_CONNECT10
,
160 PIDGIN_STOCK_ANIMATION_CONNECT11
,
161 PIDGIN_STOCK_ANIMATION_CONNECT12
,
162 PIDGIN_STOCK_ANIMATION_CONNECT13
,
163 PIDGIN_STOCK_ANIMATION_CONNECT14
,
164 PIDGIN_STOCK_ANIMATION_CONNECT15
,
165 PIDGIN_STOCK_ANIMATION_CONNECT16
,
166 PIDGIN_STOCK_ANIMATION_CONNECT17
,
167 PIDGIN_STOCK_ANIMATION_CONNECT18
,
168 PIDGIN_STOCK_ANIMATION_CONNECT19
,
169 PIDGIN_STOCK_ANIMATION_CONNECT20
,
170 PIDGIN_STOCK_ANIMATION_CONNECT21
,
171 PIDGIN_STOCK_ANIMATION_CONNECT22
,
172 PIDGIN_STOCK_ANIMATION_CONNECT23
,
173 PIDGIN_STOCK_ANIMATION_CONNECT24
,
174 PIDGIN_STOCK_ANIMATION_CONNECT25
,
175 PIDGIN_STOCK_ANIMATION_CONNECT26
,
176 PIDGIN_STOCK_ANIMATION_CONNECT27
,
177 PIDGIN_STOCK_ANIMATION_CONNECT28
,
178 PIDGIN_STOCK_ANIMATION_CONNECT29
,
179 PIDGIN_STOCK_ANIMATION_CONNECT30
,
183 static GtkContainerClass
*parent_class
= NULL
;
185 static void pidgin_status_box_class_init (PidginStatusBoxClass
*klass
);
186 static void pidgin_status_box_init (PidginStatusBox
*status_box
);
189 pidgin_status_box_get_type (void)
191 static GType status_box_type
= 0;
193 if (!status_box_type
)
195 static const GTypeInfo status_box_info
=
197 sizeof (PidginStatusBoxClass
),
198 NULL
, /* base_init */
199 NULL
, /* base_finalize */
200 (GClassInitFunc
) pidgin_status_box_class_init
,
201 NULL
, /* class_finalize */
202 NULL
, /* class_data */
203 sizeof (PidginStatusBox
),
205 (GInstanceInitFunc
) pidgin_status_box_init
,
206 NULL
/* value_table */
209 status_box_type
= g_type_register_static(GTK_TYPE_CONTAINER
,
215 return status_box_type
;
219 pidgin_status_box_get_property(GObject
*object
, guint param_id
,
220 GValue
*value
, GParamSpec
*psec
)
222 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(object
);
226 g_value_set_pointer(value
, statusbox
->account
);
229 g_value_set_boolean(value
, statusbox
->icon_box
!= NULL
);
232 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, psec
);
238 update_to_reflect_account_status(PidginStatusBox
*status_box
, PurpleAccount
*account
, PurpleStatus
*newstatus
)
242 const PurpleStatusType
*statustype
= NULL
;
245 statustype
= purple_status_type_find_with_id((GList
*)purple_account_get_status_types(account
),
246 (char *)purple_status_type_get_id(purple_status_get_status_type(newstatus
)));
248 for (l
= purple_account_get_status_types(account
); l
!= NULL
; l
= l
->next
) {
249 PurpleStatusType
*status_type
= (PurpleStatusType
*)l
->data
;
251 if (!purple_status_type_is_user_settable(status_type
) ||
252 purple_status_type_is_independent(status_type
))
255 if (statustype
== status_type
)
260 /* TODO WebKit: Doesn't do this? */
261 pidgin_webview_set_populate_primary_clipboard(
262 PIDGIN_WEBVIEW(status_box
->webview
), TRUE
);
265 if (status_no
!= -1) {
267 gtk_widget_set_sensitive(GTK_WIDGET(status_box
), FALSE
);
268 path
= gtk_tree_path_new_from_indices(status_no
, -1);
269 if (status_box
->active_row
)
270 gtk_tree_row_reference_free(status_box
->active_row
);
271 status_box
->active_row
= gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box
->dropdown_store
), path
);
272 gtk_tree_path_free(path
);
274 message
= purple_status_get_attr_string(newstatus
, "message");
276 if (!message
|| !*message
)
278 gtk_widget_hide(status_box
->vbox
);
279 status_box
->webview_visible
= FALSE
;
283 gtk_widget_show_all(status_box
->vbox
);
284 status_box
->webview_visible
= TRUE
;
285 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(status_box
->webview
), message
);
287 gtk_widget_set_sensitive(GTK_WIDGET(status_box
), TRUE
);
288 pidgin_status_box_refresh(status_box
);
293 account_status_changed_cb(PurpleAccount
*account
, PurpleStatus
*oldstatus
, PurpleStatus
*newstatus
, PidginStatusBox
*status_box
)
295 if (status_box
->account
== account
)
296 update_to_reflect_account_status(status_box
, account
, newstatus
);
297 else if (status_box
->token_status_account
== account
)
298 status_menu_refresh_iter(status_box
, TRUE
);
302 icon_box_press_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
304 if (event
->button
== 3) {
305 GtkWidget
*menu_item
;
308 if (box
->icon_box_menu
)
309 gtk_widget_destroy(box
->icon_box_menu
);
311 box
->icon_box_menu
= gtk_menu_new();
313 pidgin_new_menu_item(box
->icon_box_menu
,
314 _("Select Buddy Icon"), GTK_STOCK_ADD
,
315 G_CALLBACK(choose_buddy_icon_cb
), box
);
317 menu_item
= pidgin_new_menu_item(box
->icon_box_menu
, _("Remove"), GTK_STOCK_REMOVE
,
318 G_CALLBACK(remove_buddy_icon_cb
), box
);
319 if (!(path
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon"))
321 gtk_widget_set_sensitive(menu_item
, FALSE
);
323 gtk_menu_popup(GTK_MENU(box
->icon_box_menu
), NULL
, NULL
, NULL
, NULL
,
324 event
->button
, event
->time
);
327 choose_buddy_icon_cb(widget
, box
);
333 icon_box_dnd_cb(GtkWidget
*widget
, GdkDragContext
*dc
, gint x
, gint y
,
334 GtkSelectionData
*sd
, guint info
, guint t
, PidginStatusBox
*box
)
336 gchar
*name
= (gchar
*) gtk_selection_data_get_data(sd
);
338 if ((gtk_selection_data_get_length(sd
) >= 0)
339 && (gtk_selection_data_get_format(sd
) == 8)) {
340 /* Well, it looks like the drag event was cool.
341 * Let's do something with it */
342 if (!g_ascii_strncasecmp(name
, "file://", 7)) {
343 GError
*converr
= NULL
;
346 if(!(tmp
= g_filename_from_uri(name
, NULL
, &converr
))) {
347 purple_debug(PURPLE_DEBUG_ERROR
, "buddyicon", "%s\n",
348 (converr
? converr
->message
:
349 "g_filename_from_uri error"));
352 if ((rtmp
= strchr(tmp
, '\r')) || (rtmp
= strchr(tmp
, '\n')))
354 icon_choose_cb(tmp
, box
);
357 gtk_drag_finish(dc
, TRUE
, FALSE
, t
);
359 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
363 icon_box_enter_cb(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginStatusBox
*box
)
365 gdk_window_set_cursor(gtk_widget_get_window(widget
), box
->hand_cursor
);
366 gtk_image_set_from_pixbuf(GTK_IMAGE(box
->icon
), box
->buddy_icon_hover
);
371 icon_box_leave_cb(GtkWidget
*widget
, GdkEventCrossing
*event
, PidginStatusBox
*box
)
373 gdk_window_set_cursor(gtk_widget_get_window(widget
), box
->arrow_cursor
);
374 gtk_image_set_from_pixbuf(GTK_IMAGE(box
->icon
), box
->buddy_icon
) ;
379 static const GtkTargetEntry dnd_targets
[] = {
380 {"text/plain", 0, 0},
381 {"text/uri-list", 0, 1},
386 setup_icon_box(PidginStatusBox
*status_box
)
390 if (status_box
->icon_box
!= NULL
)
393 status_box
->icon
= gtk_image_new();
394 status_box
->icon_box
= gtk_event_box_new();
395 gtk_widget_set_parent(status_box
->icon_box
, GTK_WIDGET(status_box
));
396 gtk_widget_show(status_box
->icon_box
);
398 gtk_widget_set_tooltip_text(status_box
->icon_box
,
399 status_box
->account
? _("Click to change your buddyicon for this account.") :
400 _("Click to change your buddyicon for all accounts."));
402 if (status_box
->account
&&
403 !purple_account_get_bool(status_box
->account
, "use-global-buddyicon", TRUE
))
405 PurpleImage
*img
= purple_buddy_icons_find_account_icon(status_box
->account
);
406 pidgin_status_box_set_buddy_icon(status_box
, img
);
411 const char *filename
= purple_prefs_get_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon");
412 PurpleImage
*img
= NULL
;
414 if (filename
&& *filename
)
415 img
= purple_image_new_from_file(filename
, TRUE
);
417 pidgin_status_box_set_buddy_icon(status_box
, img
);
422 display
= gtk_widget_get_display(status_box
->icon_box
);
423 status_box
->hand_cursor
= gdk_cursor_new_for_display(display
, GDK_HAND2
);
424 status_box
->arrow_cursor
= gdk_cursor_new_for_display(display
, GDK_LEFT_PTR
);
427 gtk_drag_dest_set(status_box
->icon_box
,
428 GTK_DEST_DEFAULT_MOTION
|
429 GTK_DEST_DEFAULT_DROP
,
431 sizeof(dnd_targets
) / sizeof(GtkTargetEntry
),
434 g_signal_connect(G_OBJECT(status_box
->icon_box
), "drag_data_received", G_CALLBACK(icon_box_dnd_cb
), status_box
);
435 g_signal_connect(G_OBJECT(status_box
->icon_box
), "enter-notify-event", G_CALLBACK(icon_box_enter_cb
), status_box
);
436 g_signal_connect(G_OBJECT(status_box
->icon_box
), "leave-notify-event", G_CALLBACK(icon_box_leave_cb
), status_box
);
437 g_signal_connect(G_OBJECT(status_box
->icon_box
), "button-press-event", G_CALLBACK(icon_box_press_cb
), status_box
);
439 gtk_container_add(GTK_CONTAINER(status_box
->icon_box
), status_box
->icon
);
440 gtk_widget_show(status_box
->icon
);
444 destroy_icon_box(PidginStatusBox
*statusbox
)
446 if (statusbox
->icon_box
== NULL
)
449 gtk_widget_destroy(statusbox
->icon_box
);
451 g_object_unref(statusbox
->hand_cursor
);
452 g_object_unref(statusbox
->arrow_cursor
);
454 if (statusbox
->buddy_icon_img
)
455 g_object_unref(statusbox
->buddy_icon_img
);
457 g_object_unref(G_OBJECT(statusbox
->buddy_icon
));
458 g_object_unref(G_OBJECT(statusbox
->buddy_icon_hover
));
460 if (statusbox
->buddy_icon_sel
)
461 gtk_widget_destroy(statusbox
->buddy_icon_sel
);
463 if (statusbox
->icon_box_menu
)
464 gtk_widget_destroy(statusbox
->icon_box_menu
);
466 statusbox
->icon
= NULL
;
467 statusbox
->icon_box
= NULL
;
468 statusbox
->icon_box_menu
= NULL
;
469 statusbox
->buddy_icon_img
= NULL
;
470 statusbox
->buddy_icon
= NULL
;
471 statusbox
->buddy_icon_hover
= NULL
;
472 statusbox
->hand_cursor
= NULL
;
473 statusbox
->arrow_cursor
= NULL
;
477 pidgin_status_box_set_property(GObject
*object
, guint param_id
,
478 const GValue
*value
, GParamSpec
*pspec
)
480 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(object
);
484 if (g_value_get_boolean(value
)) {
485 if (statusbox
->account
) {
486 PurpleBuddyIconSpec
*icon_spec
= NULL
;
487 PurpleProtocol
*protocol
=
488 purple_protocols_find(purple_account_get_protocol_id(statusbox
->account
));
490 icon_spec
= purple_protocol_get_icon_spec(protocol
);
491 if (icon_spec
&& icon_spec
->format
!= NULL
)
492 setup_icon_box(statusbox
);
494 setup_icon_box(statusbox
);
497 destroy_icon_box(statusbox
);
501 statusbox
->account
= g_value_get_pointer(value
);
502 if (statusbox
->account
)
503 statusbox
->token_status_account
= NULL
;
505 statusbox
->token_status_account
= check_active_accounts_for_identical_statuses();
507 pidgin_status_box_regenerate(statusbox
, TRUE
);
511 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, pspec
);
517 pidgin_status_box_finalize(GObject
*obj
)
519 PidginStatusBox
*statusbox
= PIDGIN_STATUS_BOX(obj
);
522 purple_signals_disconnect_by_handle(statusbox
);
523 purple_prefs_disconnect_by_handle(statusbox
);
525 destroy_icon_box(statusbox
);
527 if (statusbox
->active_row
)
528 gtk_tree_row_reference_free(statusbox
->active_row
);
530 for (i
= 0; i
< G_N_ELEMENTS(statusbox
->connecting_pixbufs
); i
++) {
531 if (statusbox
->connecting_pixbufs
[i
] != NULL
)
532 g_object_unref(G_OBJECT(statusbox
->connecting_pixbufs
[i
]));
535 for (i
= 0; i
< G_N_ELEMENTS(statusbox
->typing_pixbufs
); i
++) {
536 if (statusbox
->typing_pixbufs
[i
] != NULL
)
537 g_object_unref(G_OBJECT(statusbox
->typing_pixbufs
[i
]));
540 g_object_unref(G_OBJECT(statusbox
->store
));
541 g_object_unref(G_OBJECT(statusbox
->dropdown_store
));
542 G_OBJECT_CLASS(parent_class
)->finalize(obj
);
546 pidgin_status_box_child_type (GtkContainer
*container
)
548 return GTK_TYPE_WIDGET
;
552 pidgin_status_box_class_init (PidginStatusBoxClass
*klass
)
554 GObjectClass
*object_class
;
555 GtkWidgetClass
*widget_class
;
556 GtkContainerClass
*container_class
= (GtkContainerClass
*)klass
;
558 parent_class
= g_type_class_peek_parent(klass
);
560 widget_class
= (GtkWidgetClass
*)klass
;
561 widget_class
->get_preferred_height
= pidgin_status_box_get_preferred_height
;
562 widget_class
->draw
= pidgin_status_box_draw
;
563 widget_class
->size_allocate
= pidgin_status_box_size_allocate
;
565 container_class
->child_type
= pidgin_status_box_child_type
;
566 container_class
->forall
= pidgin_status_box_forall
;
567 container_class
->remove
= NULL
;
569 object_class
= (GObjectClass
*)klass
;
571 object_class
->finalize
= pidgin_status_box_finalize
;
573 object_class
->get_property
= pidgin_status_box_get_property
;
574 object_class
->set_property
= pidgin_status_box_set_property
;
576 g_object_class_install_property(object_class
,
578 g_param_spec_pointer("account",
580 "The account, or NULL for all accounts",
581 G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS
584 g_object_class_install_property(object_class
,
586 g_param_spec_boolean("iconsel",
588 "Whether the icon selector should be displayed or not.",
590 G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS
596 * This updates the text displayed on the status box so that it shows
597 * the current status. This is the only function in this file that
598 * should modify status_box->store
601 pidgin_status_box_refresh(PidginStatusBox
*status_box
)
603 const char *aa_color
;
604 PurpleSavedStatus
*saved_status
;
605 char *primary
, *secondary
, *text
;
606 const char *stock
= NULL
;
607 GdkPixbuf
*emblem
= NULL
;
609 gboolean account_status
= FALSE
;
610 PurpleAccount
*acct
= (status_box
->account
) ? status_box
->account
: status_box
->token_status_account
;
612 saved_status
= purple_savedstatus_get_current();
614 if (status_box
->account
|| (status_box
->token_status_account
615 && purple_savedstatus_is_transient(saved_status
)))
616 account_status
= TRUE
;
619 if (status_box
->typing
!= 0)
622 PidginStatusBoxItemType type
;
625 /* Primary (get the status selected in the dropdown) */
626 path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
627 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
629 gtk_tree_path_free(path
);
631 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
635 if (type
== PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
)
636 primary
= g_strdup(purple_primitive_get_name_from_type(GPOINTER_TO_INT(data
)));
638 /* This should never happen, but just in case... */
639 primary
= g_strdup("New status");
641 else if (account_status
)
642 primary
= g_strdup(purple_status_get_name(purple_account_get_active_status(acct
)));
643 else if (purple_savedstatus_is_transient(saved_status
))
644 primary
= g_strdup(purple_primitive_get_name_from_type(purple_savedstatus_get_primitive_type(saved_status
)));
646 primary
= g_markup_escape_text(purple_savedstatus_get_title(saved_status
), -1);
649 if (status_box
->typing
!= 0)
650 secondary
= g_strdup(_("Typing"));
651 else if (status_box
->connecting
)
652 secondary
= g_strdup(_("Connecting"));
653 else if (!status_box
->network_available
)
654 secondary
= g_strdup(_("Waiting for network connection"));
655 else if (purple_savedstatus_is_transient(saved_status
))
661 message
= purple_savedstatus_get_message(saved_status
);
664 tmp
= purple_markup_strip_html(message
);
665 purple_util_chrreplace(tmp
, '\n', ' ');
666 secondary
= g_markup_escape_text(tmp
, -1);
674 if (status_box
->typing
!= 0)
675 stock
= typing_stock_ids
[status_box
->typing_index
];
676 else if (status_box
->connecting
)
677 stock
= connecting_stock_ids
[status_box
->connecting_index
];
680 PurpleStatusType
*status_type
;
681 PurpleStatusPrimitive prim
;
682 if (account_status
) {
683 status_type
= purple_status_get_status_type(purple_account_get_active_status(acct
));
684 prim
= purple_status_type_get_primitive(status_type
);
686 prim
= purple_savedstatus_get_primitive_type(saved_status
);
689 stock
= pidgin_stock_id_from_status_primitive(prim
);
692 aa_color
= pidgin_get_dim_grey_string(GTK_WIDGET(status_box
));
693 if (status_box
->account
!= NULL
) {
694 text
= g_strdup_printf("%s - <span size=\"smaller\" color=\"%s\">%s</span>",
695 purple_account_get_username(status_box
->account
),
696 aa_color
, secondary
? secondary
: primary
);
697 emblem
= pidgin_create_protocol_icon(status_box
->account
, PIDGIN_PROTOCOL_ICON_SMALL
);
698 } else if (secondary
!= NULL
) {
699 text
= g_strdup_printf("%s<span size=\"smaller\" color=\"%s\"> - %s</span>",
700 primary
, aa_color
, secondary
);
702 text
= g_strdup(primary
);
708 * Only two columns are used in this list store (does it
709 * really need to be a list store?)
711 gtk_list_store_set(status_box
->store
, &(status_box
->iter
),
712 ICON_STOCK_COLUMN
, (gpointer
)stock
,
714 EMBLEM_COLUMN
, emblem
,
715 EMBLEM_VISIBLE_COLUMN
, (emblem
!= NULL
),
719 g_object_unref(emblem
);
721 /* Make sure to activate the only row in the tree view */
722 path
= gtk_tree_path_new_from_string("0");
723 gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(status_box
->cell_view
), path
);
724 gtk_tree_path_free(path
);
726 update_size(status_box
);
729 static PurpleStatusType
*
730 find_status_type_by_index(const PurpleAccount
*account
, gint active
)
732 GList
*l
= purple_account_get_status_types(account
);
735 for (i
= 0; l
; l
= l
->next
) {
736 PurpleStatusType
*status_type
= l
->data
;
737 if (!purple_status_type_is_user_settable(status_type
) ||
738 purple_status_type_is_independent(status_type
))
750 * This updates the GtkTreeView so that it correctly shows the state
751 * we are currently using. It is used when the current state is
752 * updated from somewhere other than the GtkStatusBox (from a plugin,
753 * or when signing on with the "-n" option, for example). It is
754 * also used when the user selects the "New..." option.
756 * Maybe we could accomplish this by triggering off the mouse and
757 * keyboard signals instead of the changed signal?
760 status_menu_refresh_iter(PidginStatusBox
*status_box
, gboolean status_changed
)
762 PurpleSavedStatus
*saved_status
;
763 PurpleStatusPrimitive primitive
;
766 GtkTreePath
*path
= NULL
;
768 /* this function is inappropriate for ones with accounts */
769 if (status_box
->account
)
772 saved_status
= purple_savedstatus_get_current();
775 * Suppress the "changed" signal because the status
776 * was changed programmatically.
778 gtk_widget_set_sensitive(GTK_WIDGET(status_box
), FALSE
);
781 * If there is a token-account, then select the primitive from the
782 * dropdown using a loop. Otherwise select from the default list.
784 primitive
= purple_savedstatus_get_primitive_type(saved_status
);
785 if (!status_box
->token_status_account
&& purple_savedstatus_is_transient(saved_status
) &&
786 ((primitive
== PURPLE_STATUS_AVAILABLE
) || (primitive
== PURPLE_STATUS_AWAY
) ||
787 (primitive
== PURPLE_STATUS_INVISIBLE
) || (primitive
== PURPLE_STATUS_OFFLINE
) ||
788 (primitive
== PURPLE_STATUS_UNAVAILABLE
)) &&
789 (!purple_savedstatus_has_substatuses(saved_status
)))
791 index
= get_statusbox_index(status_box
, saved_status
);
792 path
= gtk_tree_path_new_from_indices(index
, -1);
797 PidginStatusBoxItemType type
;
800 /* If this saved status is in the list store, then set it as the active item */
801 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
))
805 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
810 /* This is a special case because Primitives for the token_status_account are actually
811 * saved statuses with substatuses for the enabled accounts */
812 if (status_box
->token_status_account
&& purple_savedstatus_is_transient(saved_status
)
813 && type
== PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
&& primitive
== (PurpleStatusPrimitive
)GPOINTER_TO_INT(data
))
816 const char *acct_status_name
= purple_status_get_name(
817 purple_account_get_active_status(status_box
->token_status_account
));
819 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
820 TEXT_COLUMN
, &name
, -1);
822 if (!purple_savedstatus_has_substatuses(saved_status
)
823 || !strcmp(name
, acct_status_name
))
826 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
);
832 } else if ((type
== PIDGIN_STATUS_BOX_TYPE_POPULAR
) &&
833 (GPOINTER_TO_INT(data
) == purple_savedstatus_get_creation_time(saved_status
)))
836 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
);
839 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
));
843 if (status_box
->active_row
)
844 gtk_tree_row_reference_free(status_box
->active_row
);
845 if (path
) { /* path should never be NULL */
846 status_box
->active_row
= gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box
->dropdown_store
), path
);
847 gtk_tree_path_free(path
);
849 status_box
->active_row
= NULL
;
851 if (status_changed
) {
852 message
= purple_savedstatus_get_message(saved_status
);
855 * If we are going to hide the webview, don't retain the
856 * message because showing the old message later is
857 * confusing. If we are going to set the message to a pre-set,
858 * then we need to do this anyway
860 * Suppress the "changed" signal because the status
861 * was changed programmatically.
863 gtk_widget_set_sensitive(GTK_WIDGET(status_box
->webview
), FALSE
);
865 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(status_box
->webview
), "");
866 pidgin_webview_clear_formatting(PIDGIN_WEBVIEW(status_box
->webview
));
868 if (!purple_savedstatus_is_transient(saved_status
) || !message
|| !*message
)
870 status_box
->webview_visible
= FALSE
;
871 gtk_widget_hide(status_box
->vbox
);
875 status_box
->webview_visible
= TRUE
;
876 gtk_widget_show_all(status_box
->vbox
);
878 pidgin_webview_load_html_string(PIDGIN_WEBVIEW(status_box
->webview
), message
);
881 gtk_widget_set_sensitive(GTK_WIDGET(status_box
->webview
), TRUE
);
882 update_size(status_box
);
885 /* Stop suppressing the "changed" signal. */
886 gtk_widget_set_sensitive(GTK_WIDGET(status_box
), TRUE
);
890 add_popular_statuses(PidginStatusBox
*statusbox
)
894 list
= purple_savedstatuses_get_popular(6);
896 /* Odd... oh well, nothing we can do about it. */
899 pidgin_status_box_add_separator(statusbox
);
901 for (cur
= list
; cur
!= NULL
; cur
= cur
->next
)
903 PurpleSavedStatus
*saved
= cur
->data
;
904 const gchar
*message
;
905 gchar
*stripped
= NULL
;
906 PidginStatusBoxItemType type
;
908 if (purple_savedstatus_is_transient(saved
))
911 * Transient statuses do not have a title, so the savedstatus
912 * API returns the message when purple_savedstatus_get_title() is
913 * called, so we don't need to get the message a second time.
915 type
= PIDGIN_STATUS_BOX_TYPE_POPULAR
;
919 type
= PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
;
921 message
= purple_savedstatus_get_message(saved
);
924 stripped
= purple_markup_strip_html(message
);
925 purple_util_chrreplace(stripped
, '\n', ' ');
929 pidgin_status_box_add(statusbox
, type
,
930 NULL
, purple_savedstatus_get_title(saved
), stripped
,
931 GINT_TO_POINTER(purple_savedstatus_get_creation_time(saved
)));
938 /* This returns NULL if the active accounts don't have identical
939 * statuses and a token account if they do */
940 static PurpleAccount
* check_active_accounts_for_identical_statuses(void)
942 GList
*iter
, *active_accts
= purple_accounts_get_all_active();
943 PurpleAccount
*acct1
= NULL
;
944 const char *proto1
= NULL
;
947 acct1
= active_accts
->data
;
948 proto1
= purple_account_get_protocol_id(acct1
);
950 /* there's no enabled account */
954 /* start at the second account */
955 for (iter
= active_accts
->next
; iter
; iter
= iter
->next
) {
956 PurpleAccount
*acct2
= iter
->data
;
959 if (!g_str_equal(proto1
, purple_account_get_protocol_id(acct2
))) {
964 for (s1
= purple_account_get_status_types(acct1
),
965 s2
= purple_account_get_status_types(acct2
); s1
&& s2
;
966 s1
= s1
->next
, s2
= s2
->next
) {
967 PurpleStatusType
*st1
= s1
->data
, *st2
= s2
->data
;
968 /* TODO: Are these enough to consider the statuses identical? */
969 if (purple_status_type_get_primitive(st1
) != purple_status_type_get_primitive(st2
)
970 || strcmp(purple_status_type_get_id(st1
), purple_status_type_get_id(st2
))
971 || strcmp(purple_status_type_get_name(st1
), purple_status_type_get_name(st2
))) {
977 if (s1
!= s2
) {/* Will both be NULL if matched */
983 g_list_free(active_accts
);
989 add_account_statuses(PidginStatusBox
*status_box
, PurpleAccount
*account
)
994 for (l
= purple_account_get_status_types(account
); l
!= NULL
; l
= l
->next
)
996 PurpleStatusType
*status_type
= (PurpleStatusType
*)l
->data
;
997 PurpleStatusPrimitive prim
;
999 if (!purple_status_type_is_user_settable(status_type
) ||
1000 purple_status_type_is_independent(status_type
))
1003 prim
= purple_status_type_get_primitive(status_type
);
1005 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
),
1006 PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
,
1007 purple_status_type_get_name(status_type
),
1009 GINT_TO_POINTER(prim
));
1014 pidgin_status_box_regenerate(PidginStatusBox
*status_box
, gboolean status_changed
)
1016 /* Unset the model while clearing it */
1017 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box
->tree_view
), NULL
);
1018 gtk_list_store_clear(status_box
->dropdown_store
);
1019 /* Don't set the model until the new statuses have been added to the box.
1020 * What is presumably a bug in Gtk < 2.4 causes things to get all confused
1021 * if we do this here. */
1022 /* gtk_combo_box_set_model(GTK_COMBO_BOX(status_box), GTK_TREE_MODEL(status_box->dropdown_store)); */
1024 if (status_box
->account
== NULL
)
1026 /* Do all the currently enabled accounts have the same statuses?
1027 * If so, display them instead of our global list.
1029 if (status_box
->token_status_account
) {
1030 add_account_statuses(status_box
, status_box
->token_status_account
);
1033 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
, _("Available"), NULL
, GINT_TO_POINTER(PURPLE_STATUS_AVAILABLE
));
1034 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
, _("Away"), NULL
, GINT_TO_POINTER(PURPLE_STATUS_AWAY
));
1035 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
));
1036 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
, _("Invisible"), NULL
, GINT_TO_POINTER(PURPLE_STATUS_INVISIBLE
));
1037 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
, NULL
, _("Offline"), NULL
, GINT_TO_POINTER(PURPLE_STATUS_OFFLINE
));
1040 add_popular_statuses(status_box
);
1042 pidgin_status_box_add_separator(PIDGIN_STATUS_BOX(status_box
));
1043 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_CUSTOM
, NULL
, _("New status..."), NULL
, NULL
);
1044 pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box
), PIDGIN_STATUS_BOX_TYPE_SAVED
, NULL
, _("Saved statuses..."), NULL
, NULL
);
1046 status_menu_refresh_iter(status_box
, status_changed
);
1047 pidgin_status_box_refresh(status_box
);
1050 add_account_statuses(status_box
, status_box
->account
);
1051 update_to_reflect_account_status(status_box
, status_box
->account
,
1052 purple_account_get_active_status(status_box
->account
));
1054 gtk_tree_view_set_model(GTK_TREE_VIEW(status_box
->tree_view
), GTK_TREE_MODEL(status_box
->dropdown_store
));
1055 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box
->tree_view
), TEXT_COLUMN
);
1059 combo_box_scroll_event_cb(GtkWidget
*w
, GdkEventScroll
*event
, PidginWebView
*webview
)
1061 pidgin_status_box_popup(PIDGIN_STATUS_BOX(w
), (GdkEvent
*)event
);
1066 webview_scroll_event_cb(GtkWidget
*w
, GdkEventScroll
*event
, PidginWebView
*webview
)
1068 if (event
->direction
== GDK_SCROLL_UP
)
1069 pidgin_webview_page_up(webview
);
1070 else if (event
->direction
== GDK_SCROLL_DOWN
)
1071 pidgin_webview_page_down(webview
);
1076 webview_remove_focus(GtkWidget
*w
, GdkEventKey
*event
, PidginStatusBox
*status_box
)
1078 if (event
->keyval
== GDK_KEY_Return
|| event
->keyval
== GDK_KEY_KP_Enter
) {
1079 remove_typing_cb(status_box
);
1082 else if (event
->keyval
== GDK_KEY_Tab
|| event
->keyval
== GDK_KEY_KP_Tab
|| event
->keyval
== GDK_KEY_ISO_Left_Tab
)
1084 /* If last inserted character is a tab, then remove the focus from here */
1085 GtkWidget
*top
= gtk_widget_get_toplevel(w
);
1086 g_signal_emit_by_name(G_OBJECT(top
), "move-focus",
1087 (event
->state
& GDK_SHIFT_MASK
) ?
1088 GTK_DIR_TAB_BACKWARD
: GTK_DIR_TAB_FORWARD
);
1091 if (status_box
->typing
== 0)
1094 /* Reset the status if Escape was pressed */
1095 if (event
->keyval
== GDK_KEY_Escape
)
1097 purple_timeout_remove(status_box
->typing
);
1098 status_box
->typing
= 0;
1100 /* TODO WebKit: Doesn't do this? */
1101 pidgin_webview_set_populate_primary_clipboard(
1102 PIDGIN_WEBVIEW(status_box
->webview
), TRUE
);
1104 if (status_box
->account
!= NULL
)
1105 update_to_reflect_account_status(status_box
, status_box
->account
,
1106 purple_account_get_active_status(status_box
->account
));
1108 status_menu_refresh_iter(status_box
, TRUE
);
1109 pidgin_status_box_refresh(status_box
);
1114 pidgin_status_box_pulse_typing(status_box
);
1115 purple_timeout_remove(status_box
->typing
);
1116 status_box
->typing
= purple_timeout_add_seconds(TYPING_TIMEOUT
, (GSourceFunc
)remove_typing_cb
, status_box
);
1122 dropdown_store_row_separator_func(GtkTreeModel
*model
,
1123 GtkTreeIter
*iter
, gpointer data
)
1125 PidginStatusBoxItemType type
;
1127 gtk_tree_model_get(model
, iter
, TYPE_COLUMN
, &type
, -1);
1129 if (type
== PIDGIN_STATUS_BOX_TYPE_SEPARATOR
)
1136 cache_pixbufs(PidginStatusBox
*status_box
)
1138 GtkIconSize icon_size
;
1141 g_object_set(G_OBJECT(status_box
->icon_rend
), "xpad", 3, NULL
);
1142 icon_size
= gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL
);
1144 for (i
= 0; i
< G_N_ELEMENTS(status_box
->connecting_pixbufs
); i
++) {
1145 if (status_box
->connecting_pixbufs
[i
] != NULL
)
1146 g_object_unref(G_OBJECT(status_box
->connecting_pixbufs
[i
]));
1147 if (connecting_stock_ids
[i
])
1148 status_box
->connecting_pixbufs
[i
] = gtk_widget_render_icon (GTK_WIDGET(status_box
->vbox
),
1149 connecting_stock_ids
[i
], icon_size
, "PidginStatusBox");
1151 status_box
->connecting_pixbufs
[i
] = NULL
;
1153 status_box
->connecting_index
= 0;
1156 for (i
= 0; i
< G_N_ELEMENTS(status_box
->typing_pixbufs
); i
++) {
1157 if (status_box
->typing_pixbufs
[i
] != NULL
)
1158 g_object_unref(G_OBJECT(status_box
->typing_pixbufs
[i
]));
1159 if (typing_stock_ids
[i
])
1160 status_box
->typing_pixbufs
[i
] = gtk_widget_render_icon (GTK_WIDGET(status_box
->vbox
),
1161 typing_stock_ids
[i
], icon_size
, "PidginStatusBox");
1163 status_box
->typing_pixbufs
[i
] = NULL
;
1165 status_box
->typing_index
= 0;
1168 static void account_enabled_cb(PurpleAccount
*acct
, PidginStatusBox
*status_box
)
1170 PurpleAccount
*initial_token_acct
= status_box
->token_status_account
;
1172 if (status_box
->account
)
1175 status_box
->token_status_account
= check_active_accounts_for_identical_statuses();
1177 /* Regenerate the list if it has changed */
1178 if (initial_token_acct
!= status_box
->token_status_account
) {
1179 pidgin_status_box_regenerate(status_box
, TRUE
);
1185 current_savedstatus_changed_cb(PurpleSavedStatus
*now
, PurpleSavedStatus
*old
, PidginStatusBox
*status_box
)
1187 /* Make sure our current status is added to the list of popular statuses */
1188 pidgin_status_box_regenerate(status_box
, TRUE
);
1192 saved_status_updated_cb(PurpleSavedStatus
*status
, PidginStatusBox
*status_box
)
1194 pidgin_status_box_regenerate(status_box
,
1195 purple_savedstatus_get_current() == status
);
1199 spellcheck_prefs_cb(const char *name
, PurplePrefType type
,
1200 gconstpointer value
, gpointer data
)
1202 PidginStatusBox
*status_box
= (PidginStatusBox
*)data
;
1204 pidgin_webview_set_spellcheck(PIDGIN_WEBVIEW(status_box
->webview
),
1205 (gboolean
)GPOINTER_TO_INT(value
));
1209 static gboolean
button_released_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
1212 if (event
->button
!= 1)
1214 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box
->toggle_button
), FALSE
);
1215 if (!box
->webview_visible
)
1216 g_signal_emit_by_name(G_OBJECT(box
), "changed", NULL
, NULL
);
1220 static gboolean
button_pressed_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
1222 if (event
->button
!= 1)
1224 gtk_combo_box_popup(GTK_COMBO_BOX(box
));
1225 /* Disabled until button_released_cb works */
1227 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box
->toggle_button
), TRUE
);
1234 pidgin_status_box_list_position (PidginStatusBox
*status_box
, int *x
, int *y
, int *width
, int *height
)
1238 GdkRectangle monitor
;
1239 GtkRequisition popup_req
;
1240 GtkPolicyType hpolicy
, vpolicy
;
1241 GtkAllocation allocation
;
1243 gtk_widget_get_allocation(GTK_WIDGET(status_box
), &allocation
);
1244 gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(status_box
)), x
, y
);
1249 *width
= allocation
.width
;
1251 hpolicy
= vpolicy
= GTK_POLICY_NEVER
;
1252 g_object_set(G_OBJECT(status_box
->scrolled_window
),
1253 "hscrollbar-policy", hpolicy
,
1254 "vscrollbar-policy", vpolicy
,
1256 gtk_widget_get_preferred_size(status_box
->popup_frame
, NULL
, &popup_req
);
1258 if (popup_req
.width
> *width
) {
1259 hpolicy
= GTK_POLICY_ALWAYS
;
1260 g_object_set(G_OBJECT(status_box
->scrolled_window
),
1261 "hscrollbar-policy", hpolicy
,
1262 "vscrollbar-policy", vpolicy
,
1264 gtk_widget_get_preferred_size(status_box
->popup_frame
, NULL
, &popup_req
);
1267 *height
= popup_req
.height
;
1269 screen
= gtk_widget_get_screen(GTK_WIDGET(status_box
));
1270 monitor_num
= gdk_screen_get_monitor_at_window(screen
,
1271 gtk_widget_get_window(GTK_WIDGET(status_box
)));
1272 gdk_screen_get_monitor_geometry(screen
, monitor_num
, &monitor
);
1276 else if (*x
+ *width
> monitor
.x
+ monitor
.width
)
1277 *x
= monitor
.x
+ monitor
.width
- *width
;
1279 if (*y
+ allocation
.height
+ *height
<= monitor
.y
+ monitor
.height
)
1280 *y
+= allocation
.height
;
1281 else if (*y
- *height
>= monitor
.y
)
1283 else if (monitor
.y
+ monitor
.height
- (*y
+ allocation
.height
) > *y
- monitor
.y
)
1285 *y
+= allocation
.height
;
1286 *height
= monitor
.y
+ monitor
.height
- *y
;
1290 *height
= *y
- monitor
.y
;
1294 if (popup_req
.height
> *height
)
1296 vpolicy
= GTK_POLICY_ALWAYS
;
1298 g_object_set(G_OBJECT(status_box
->scrolled_window
),
1299 "hscrollbar-policy", hpolicy
,
1300 "vscrollbar-policy", vpolicy
,
1306 popup_grab_on_window(GdkWindow
*window
, GdkEvent
*event
)
1308 guint32 activate_time
= gdk_event_get_time(event
);
1309 GdkDevice
*device
= gdk_event_get_device(event
);
1310 GdkGrabStatus status
;
1312 status
= gdk_device_grab(device
, window
, GDK_OWNERSHIP_WINDOW
, TRUE
,
1313 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
1314 GDK_POINTER_MOTION_MASK
| GDK_KEY_PRESS_MASK
|
1315 GDK_KEY_RELEASE_MASK
, NULL
, activate_time
);
1316 if (status
== GDK_GRAB_SUCCESS
) {
1317 status
= gdk_device_grab(gdk_device_get_associated_device(device
),
1318 window
, GDK_OWNERSHIP_WINDOW
, TRUE
,
1319 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
1320 GDK_POINTER_MOTION_MASK
| GDK_KEY_PRESS_MASK
|
1321 GDK_KEY_RELEASE_MASK
, NULL
, activate_time
);
1322 if (status
== GDK_GRAB_SUCCESS
)
1325 gdk_device_ungrab(device
, activate_time
);
1333 pidgin_status_box_popup(PidginStatusBox
*box
, GdkEvent
*event
)
1335 int width
, height
, x
, y
;
1336 pidgin_status_box_list_position (box
, &x
, &y
, &width
, &height
);
1338 gtk_widget_set_size_request (box
->popup_window
, width
, height
);
1339 gtk_window_move (GTK_WINDOW (box
->popup_window
), x
, y
);
1340 gtk_widget_show(box
->popup_window
);
1341 gtk_widget_grab_focus (box
->tree_view
);
1342 if (!popup_grab_on_window(gtk_widget_get_window(box
->popup_window
), event
)) {
1343 gtk_widget_hide (box
->popup_window
);
1346 gtk_grab_add (box
->popup_window
);
1347 /*box->popup_in_progress = TRUE;*/
1348 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box
->toggle_button
),
1351 if (box
->active_row
) {
1352 GtkTreePath
*path
= gtk_tree_row_reference_get_path(box
->active_row
);
1353 gtk_tree_view_set_cursor(GTK_TREE_VIEW(box
->tree_view
), path
, NULL
, FALSE
);
1354 gtk_tree_path_free(path
);
1359 pidgin_status_box_popdown(PidginStatusBox
*box
, GdkEvent
*event
)
1363 gtk_widget_hide(box
->popup_window
);
1364 box
->popup_in_progress
= FALSE
;
1365 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(box
->toggle_button
), FALSE
);
1366 gtk_grab_remove(box
->popup_window
);
1367 time
= gdk_event_get_time(event
);
1368 device
= gdk_event_get_device(event
);
1369 gdk_device_ungrab(device
, time
);
1370 gdk_device_ungrab(gdk_device_get_associated_device(device
), time
);
1374 toggle_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
, PidginStatusBox
*box
)
1376 switch (event
->keyval
) {
1377 case GDK_KEY_Return
:
1378 case GDK_KEY_KP_Enter
:
1379 case GDK_KEY_KP_Space
:
1381 if (!box
->popup_in_progress
) {
1382 pidgin_status_box_popup(box
, (GdkEvent
*)event
);
1383 box
->popup_in_progress
= TRUE
;
1385 pidgin_status_box_popdown(box
, (GdkEvent
*)event
);
1394 toggled_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*box
)
1396 if (!box
->popup_in_progress
)
1397 pidgin_status_box_popup(box
, (GdkEvent
*)event
);
1399 pidgin_status_box_popdown(box
, (GdkEvent
*)event
);
1404 buddy_icon_set_cb(const char *filename
, PidginStatusBox
*box
)
1406 PurpleImage
*img
= NULL
;
1407 PurpleBuddyIconSpec
*icon_spec
= NULL
;
1410 PurpleProtocol
*protocol
=
1411 purple_protocols_find(purple_account_get_protocol_id(box
->account
));
1413 icon_spec
= purple_protocol_get_icon_spec(protocol
);
1414 if (icon_spec
&& icon_spec
->format
) {
1415 gpointer data
= NULL
;
1418 data
= pidgin_convert_buddy_icon(protocol
, filename
, &len
);
1419 img
= purple_buddy_icons_set_account_icon(box
->account
, data
, len
);
1422 * set_account_icon doesn't give us a reference, but we
1423 * unref one below (for the other code path)
1428 purple_account_set_buddy_icon_path(box
->account
, filename
);
1430 purple_account_set_bool(box
->account
, "use-global-buddyicon", (filename
!= NULL
));
1434 for (accounts
= purple_accounts_get_all(); accounts
!= NULL
; accounts
= accounts
->next
) {
1435 PurpleAccount
*account
= accounts
->data
;
1436 PurpleProtocol
*protocol
=
1437 purple_protocols_find(purple_account_get_protocol_id(account
));
1439 icon_spec
= purple_protocol_get_icon_spec(protocol
);
1440 if (icon_spec
&& icon_spec
->format
&&
1441 purple_account_get_bool(account
, "use-global-buddyicon", TRUE
)) {
1442 gpointer data
= NULL
;
1445 data
= pidgin_convert_buddy_icon(protocol
, filename
, &len
);
1446 purple_buddy_icons_set_account_icon(account
, data
, len
);
1447 purple_account_set_buddy_icon_path(account
, filename
);
1451 /* Even if no accounts were processed, load the icon that was set. */
1452 if (filename
!= NULL
)
1453 img
= purple_image_new_from_file(filename
, TRUE
);
1456 pidgin_status_box_set_buddy_icon(box
, img
);
1458 g_object_unref(img
);
1462 remove_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
)
1464 if (box
->account
== NULL
)
1465 /* The pref-connect callback does the actual work */
1466 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon", NULL
);
1468 buddy_icon_set_cb(NULL
, box
);
1470 gtk_widget_destroy(box
->icon_box_menu
);
1471 box
->icon_box_menu
= NULL
;
1475 choose_buddy_icon_cb(GtkWidget
*w
, PidginStatusBox
*box
)
1477 if (box
->buddy_icon_sel
) {
1478 gtk_window_present(GTK_WINDOW(box
->buddy_icon_sel
));
1480 box
->buddy_icon_sel
= pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtk_widget_get_toplevel(w
)), icon_choose_cb
, box
);
1481 gtk_widget_show_all(box
->buddy_icon_sel
);
1486 icon_choose_cb(const char *filename
, gpointer data
)
1488 PidginStatusBox
*box
= data
;
1490 if (box
->account
== NULL
)
1491 /* The pref-connect callback does the actual work */
1492 purple_prefs_set_path(PIDGIN_PREFS_ROOT
"/accounts/buddyicon", filename
);
1494 buddy_icon_set_cb(filename
, box
);
1497 box
->buddy_icon_sel
= NULL
;
1501 update_buddyicon_cb(const char *name
, PurplePrefType type
,
1502 gconstpointer value
, gpointer data
)
1504 buddy_icon_set_cb(value
, (PidginStatusBox
*) data
);
1508 treeview_activate_current_selection(PidginStatusBox
*status_box
, GtkTreePath
*path
, GdkEvent
*event
)
1510 if (status_box
->active_row
)
1511 gtk_tree_row_reference_free(status_box
->active_row
);
1513 status_box
->active_row
= gtk_tree_row_reference_new(GTK_TREE_MODEL(status_box
->dropdown_store
), path
);
1514 pidgin_status_box_popdown(status_box
, event
);
1515 pidgin_status_box_changed(status_box
);
1518 static void tree_view_delete_current_selection_cb(gpointer data
)
1520 PurpleSavedStatus
*saved
;
1522 saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
1523 g_return_if_fail(saved
!= NULL
);
1525 if (purple_savedstatus_get_current() != saved
)
1526 purple_savedstatus_delete_by_status(saved
);
1530 tree_view_delete_current_selection(PidginStatusBox
*status_box
, GtkTreePath
*path
, GdkEvent
*event
)
1534 PurpleSavedStatus
*saved
;
1537 if (status_box
->active_row
) {
1538 /* don't delete active status */
1539 if (gtk_tree_path_compare(path
, gtk_tree_row_reference_get_path(status_box
->active_row
)) == 0)
1543 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
1546 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
1550 saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
1551 g_return_if_fail(saved
!= NULL
);
1552 if (saved
== purple_savedstatus_get_current())
1555 msg
= g_strdup_printf(_("Are you sure you want to delete %s?"), purple_savedstatus_get_title(saved
));
1557 purple_request_action(saved
, NULL
, msg
, NULL
, 0,
1560 _("Delete"), tree_view_delete_current_selection_cb
,
1565 pidgin_status_box_popdown(status_box
, event
);
1569 treeview_button_release_cb(GtkWidget
*widget
, GdkEventButton
*event
, PidginStatusBox
*status_box
)
1571 GtkTreePath
*path
= NULL
;
1573 GtkWidget
*ewidget
= gtk_get_event_widget ((GdkEvent
*)event
);
1575 if (ewidget
!= status_box
->tree_view
) {
1576 if (ewidget
== status_box
->toggle_button
&&
1577 status_box
->popup_in_progress
&&
1578 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (status_box
->toggle_button
))) {
1579 pidgin_status_box_popdown(status_box
, (GdkEvent
*)event
);
1581 } else if (ewidget
== status_box
->toggle_button
) {
1582 status_box
->popup_in_progress
= TRUE
;
1585 /* released outside treeview */
1586 if (ewidget
!= status_box
->toggle_button
) {
1587 pidgin_status_box_popdown(status_box
, (GdkEvent
*)event
);
1594 ret
= gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (status_box
->tree_view
),
1600 return TRUE
; /* clicked outside window? */
1602 treeview_activate_current_selection(status_box
, path
, (GdkEvent
*)event
);
1603 gtk_tree_path_free (path
);
1609 treeview_key_press_event(GtkWidget
*widget
,
1610 GdkEventKey
*event
, PidginStatusBox
*box
)
1612 if (box
->popup_in_progress
) {
1613 if (event
->keyval
== GDK_KEY_Escape
) {
1614 pidgin_status_box_popdown(box
, (GdkEvent
*)event
);
1617 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(box
->tree_view
));
1621 if (gtk_tree_selection_get_selected(sel
, NULL
, &iter
)) {
1622 gboolean ret
= TRUE
;
1623 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(box
->dropdown_store
), &iter
);
1624 if (event
->keyval
== GDK_KEY_Return
) {
1625 treeview_activate_current_selection(box
, path
, (GdkEvent
*)event
);
1626 } else if (event
->keyval
== GDK_KEY_Delete
) {
1627 tree_view_delete_current_selection(box
, path
, (GdkEvent
*)event
);
1631 gtk_tree_path_free (path
);
1640 webview_cursor_moved_cb(gpointer data
, PidginWebView
*webview
)
1642 /* Restart the typing timeout if arrow keys are pressed while editing the message */
1643 PidginStatusBox
*status_box
= data
;
1644 if (status_box
->typing
== 0)
1646 webview_changed_cb(NULL
, status_box
);
1650 treeview_cursor_changed_cb(GtkTreeView
*treeview
, gpointer data
)
1652 GtkTreeSelection
*sel
= gtk_tree_view_get_selection (treeview
);
1653 GtkTreeModel
*model
= GTK_TREE_MODEL (data
);
1655 GtkTreePath
*cursor
;
1656 GtkTreePath
*selection
;
1659 if (gtk_tree_selection_get_selected (sel
, NULL
, &iter
)) {
1660 if ((selection
= gtk_tree_model_get_path (model
, &iter
)) == NULL
) {
1661 /* Shouldn't happen, but ignore anyway */
1665 /* I don't think this can happen, but we'll just ignore it */
1669 gtk_tree_view_get_cursor (treeview
, &cursor
, NULL
);
1670 if (cursor
== NULL
) {
1671 /* Probably won't happen in a 'cursor-changed' event? */
1672 gtk_tree_path_free (selection
);
1676 cmp
= gtk_tree_path_compare (cursor
, selection
);
1678 /* The cursor moved up without moving the selection, so move it up again */
1679 gtk_tree_path_prev (cursor
);
1680 gtk_tree_view_set_cursor (treeview
, cursor
, NULL
, FALSE
);
1681 } else if (cmp
> 0) {
1682 /* The cursor moved down without moving the selection, so move it down again */
1683 gtk_tree_path_next (cursor
);
1684 gtk_tree_view_set_cursor (treeview
, cursor
, NULL
, FALSE
);
1687 gtk_tree_path_free (selection
);
1688 gtk_tree_path_free (cursor
);
1692 pidgin_status_box_init (PidginStatusBox
*status_box
)
1694 GtkCellRenderer
*text_rend
;
1695 GtkCellRenderer
*icon_rend
;
1696 GtkCellRenderer
*emblem_rend
;
1697 GtkWidget
*toplevel
;
1698 GtkTreeSelection
*sel
;
1700 gtk_widget_set_has_window(GTK_WIDGET(status_box
), FALSE
);
1701 status_box
->webview_visible
= FALSE
;
1702 status_box
->network_available
= purple_network_is_available();
1703 status_box
->connecting
= FALSE
;
1704 status_box
->typing
= 0;
1705 status_box
->toggle_button
= gtk_toggle_button_new();
1706 status_box
->hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 6);
1707 status_box
->cell_view
= gtk_cell_view_new();
1708 status_box
->vsep
= gtk_separator_new(GTK_ORIENTATION_VERTICAL
);
1709 #if GTK_CHECK_VERSION(3,14,0)
1710 status_box
->arrow
= gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON
);
1712 status_box
->arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
1715 status_box
->store
= gtk_list_store_new(NUM_COLUMNS
, G_TYPE_INT
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
, G_TYPE_STRING
,
1716 G_TYPE_STRING
, G_TYPE_POINTER
, GDK_TYPE_PIXBUF
, G_TYPE_BOOLEAN
);
1717 status_box
->dropdown_store
= gtk_list_store_new(NUM_COLUMNS
, G_TYPE_INT
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
,
1718 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_POINTER
, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
1720 gtk_cell_view_set_model(GTK_CELL_VIEW(status_box
->cell_view
), GTK_TREE_MODEL(status_box
->store
));
1721 gtk_list_store_append(status_box
->store
, &(status_box
->iter
));
1723 atk_object_set_name(gtk_widget_get_accessible(status_box
->toggle_button
), _("Status Selector"));
1725 gtk_container_add(GTK_CONTAINER(status_box
->toggle_button
), status_box
->hbox
);
1726 gtk_box_pack_start(GTK_BOX(status_box
->hbox
), status_box
->cell_view
, TRUE
, TRUE
, 0);
1727 gtk_box_pack_start(GTK_BOX(status_box
->hbox
), status_box
->vsep
, FALSE
, FALSE
, 0);
1728 gtk_box_pack_start(GTK_BOX(status_box
->hbox
), status_box
->arrow
, FALSE
, FALSE
, 0);
1729 gtk_widget_show_all(status_box
->toggle_button
);
1730 gtk_button_set_focus_on_click(GTK_BUTTON(status_box
->toggle_button
), FALSE
);
1732 text_rend
= gtk_cell_renderer_text_new();
1733 icon_rend
= gtk_cell_renderer_pixbuf_new();
1734 emblem_rend
= gtk_cell_renderer_pixbuf_new();
1735 status_box
->popup_window
= gtk_window_new (GTK_WINDOW_POPUP
);
1737 toplevel
= gtk_widget_get_toplevel (GTK_WIDGET (status_box
));
1738 if (GTK_IS_WINDOW (toplevel
)) {
1739 gtk_window_set_transient_for (GTK_WINDOW (status_box
->popup_window
),
1740 GTK_WINDOW (toplevel
));
1743 gtk_window_set_resizable (GTK_WINDOW (status_box
->popup_window
), FALSE
);
1744 gtk_window_set_type_hint (GTK_WINDOW (status_box
->popup_window
),
1745 GDK_WINDOW_TYPE_HINT_POPUP_MENU
);
1746 gtk_window_set_screen (GTK_WINDOW (status_box
->popup_window
),
1747 gtk_widget_get_screen (GTK_WIDGET (status_box
)));
1748 status_box
->popup_frame
= gtk_frame_new (NULL
);
1749 gtk_frame_set_shadow_type (GTK_FRAME (status_box
->popup_frame
),
1750 GTK_SHADOW_ETCHED_IN
);
1751 gtk_container_add (GTK_CONTAINER (status_box
->popup_window
),
1752 status_box
->popup_frame
);
1754 gtk_widget_show (status_box
->popup_frame
);
1756 status_box
->tree_view
= gtk_tree_view_new ();
1757 sel
= gtk_tree_view_get_selection (GTK_TREE_VIEW (status_box
->tree_view
));
1758 gtk_tree_selection_set_mode (sel
, GTK_SELECTION_BROWSE
);
1759 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (status_box
->tree_view
),
1761 gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (status_box
->tree_view
),
1763 gtk_tree_view_set_model (GTK_TREE_VIEW (status_box
->tree_view
),
1764 GTK_TREE_MODEL(status_box
->dropdown_store
));
1765 status_box
->column
= gtk_tree_view_column_new ();
1766 gtk_tree_view_append_column (GTK_TREE_VIEW (status_box
->tree_view
),
1767 status_box
->column
);
1768 gtk_tree_view_column_pack_start(status_box
->column
, icon_rend
, FALSE
);
1769 gtk_tree_view_column_pack_start(status_box
->column
, text_rend
, TRUE
);
1770 gtk_tree_view_column_pack_start(status_box
->column
, emblem_rend
, FALSE
);
1771 gtk_tree_view_column_set_attributes(status_box
->column
, icon_rend
, "stock-id", ICON_STOCK_COLUMN
, NULL
);
1772 gtk_tree_view_column_set_attributes(status_box
->column
, text_rend
, "markup", TEXT_COLUMN
, NULL
);
1773 gtk_tree_view_column_set_attributes(status_box
->column
, emblem_rend
, "stock-id", EMBLEM_COLUMN
, "visible", EMBLEM_VISIBLE_COLUMN
, NULL
);
1775 status_box
->scrolled_window
= pidgin_make_scrollable(status_box
->tree_view
, GTK_POLICY_NEVER
, GTK_POLICY_NEVER
, GTK_SHADOW_NONE
, -1, -1);
1776 gtk_container_add (GTK_CONTAINER (status_box
->popup_frame
),
1777 status_box
->scrolled_window
);
1779 gtk_widget_show(status_box
->tree_view
);
1780 gtk_tree_view_set_search_column(GTK_TREE_VIEW(status_box
->tree_view
), TEXT_COLUMN
);
1781 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(status_box
->tree_view
),
1782 pidgin_tree_view_search_equal_func
, NULL
, NULL
);
1784 g_object_set(text_rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1786 status_box
->icon_rend
= gtk_cell_renderer_pixbuf_new();
1787 status_box
->text_rend
= gtk_cell_renderer_text_new();
1788 emblem_rend
= gtk_cell_renderer_pixbuf_new();
1789 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->icon_rend
, FALSE
);
1790 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->text_rend
, TRUE
);
1791 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(status_box
->cell_view
), emblem_rend
, FALSE
);
1792 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->icon_rend
, "stock-id", ICON_STOCK_COLUMN
, NULL
);
1793 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box
->cell_view
), status_box
->text_rend
, "markup", TEXT_COLUMN
, NULL
);
1794 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(status_box
->cell_view
), emblem_rend
, "pixbuf", EMBLEM_COLUMN
, "visible", EMBLEM_VISIBLE_COLUMN
, NULL
);
1795 g_object_set(status_box
->text_rend
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1797 status_box
->vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, FALSE
);
1798 status_box
->sw
= pidgin_create_webview(TRUE
, &status_box
->webview
, NULL
);
1799 pidgin_webview_hide_toolbar(PIDGIN_WEBVIEW(status_box
->webview
));
1802 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "button-press-event",
1803 G_CALLBACK(button_pressed_cb
), status_box
);
1804 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "button-release-event",
1805 G_CALLBACK(button_released_cb
), status_box
);
1807 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "key-press-event",
1808 G_CALLBACK(toggle_key_press_cb
), status_box
);
1809 g_signal_connect(G_OBJECT(status_box
->toggle_button
), "button-press-event",
1810 G_CALLBACK(toggled_cb
), status_box
);
1811 g_signal_connect(G_OBJECT(status_box
->webview
), "changed",
1812 G_CALLBACK(webview_changed_cb
), status_box
);
1813 g_signal_connect(G_OBJECT(status_box
->webview
), "format-toggled",
1814 G_CALLBACK(webview_format_changed_cb
), status_box
);
1815 g_signal_connect_swapped(G_OBJECT(status_box
->webview
), "selection-changed",
1816 G_CALLBACK(webview_cursor_moved_cb
), status_box
);
1817 g_signal_connect(G_OBJECT(status_box
->webview
), "key-press-event",
1818 G_CALLBACK(webview_remove_focus
), status_box
);
1820 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/spellcheck"))
1821 pidgin_webview_set_spellcheck(PIDGIN_WEBVIEW(status_box
->webview
), TRUE
);
1822 gtk_widget_set_parent(status_box
->vbox
, GTK_WIDGET(status_box
));
1823 gtk_widget_show_all(status_box
->vbox
);
1825 gtk_widget_set_parent(status_box
->toggle_button
, GTK_WIDGET(status_box
));
1827 gtk_box_pack_start(GTK_BOX(status_box
->vbox
), status_box
->sw
, TRUE
, TRUE
, 0);
1829 g_signal_connect(G_OBJECT(status_box
), "scroll-event", G_CALLBACK(combo_box_scroll_event_cb
), NULL
);
1830 g_signal_connect(G_OBJECT(status_box
->webview
), "scroll-event",
1831 G_CALLBACK(webview_scroll_event_cb
), status_box
->webview
);
1832 g_signal_connect(G_OBJECT(status_box
->popup_window
), "button_release_event", G_CALLBACK(treeview_button_release_cb
), status_box
);
1833 g_signal_connect(G_OBJECT(status_box
->popup_window
), "key_press_event", G_CALLBACK(treeview_key_press_event
), status_box
);
1834 g_signal_connect(G_OBJECT(status_box
->tree_view
), "cursor-changed",
1835 G_CALLBACK(treeview_cursor_changed_cb
), status_box
->dropdown_store
);
1837 gtk_tree_view_set_row_separator_func(GTK_TREE_VIEW(status_box
->tree_view
), dropdown_store_row_separator_func
, NULL
, NULL
);
1839 status_box
->token_status_account
= check_active_accounts_for_identical_statuses();
1841 cache_pixbufs(status_box
);
1842 pidgin_status_box_regenerate(status_box
, TRUE
);
1844 purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
1846 PURPLE_CALLBACK(current_savedstatus_changed_cb
),
1848 purple_signal_connect(purple_savedstatuses_get_handle(),
1849 "savedstatus-added", status_box
,
1850 PURPLE_CALLBACK(saved_status_updated_cb
), status_box
);
1851 purple_signal_connect(purple_savedstatuses_get_handle(),
1852 "savedstatus-deleted", status_box
,
1853 PURPLE_CALLBACK(saved_status_updated_cb
), status_box
);
1854 purple_signal_connect(purple_savedstatuses_get_handle(),
1855 "savedstatus-modified", status_box
,
1856 PURPLE_CALLBACK(saved_status_updated_cb
), status_box
);
1857 purple_signal_connect(purple_accounts_get_handle(), "account-enabled", status_box
,
1858 PURPLE_CALLBACK(account_enabled_cb
),
1860 purple_signal_connect(purple_accounts_get_handle(), "account-disabled", status_box
,
1861 PURPLE_CALLBACK(account_enabled_cb
),
1863 purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", status_box
,
1864 PURPLE_CALLBACK(account_status_changed_cb
),
1867 purple_prefs_connect_callback(status_box
, PIDGIN_PREFS_ROOT
"/conversations/spellcheck",
1868 spellcheck_prefs_cb
, status_box
);
1869 purple_prefs_connect_callback(status_box
, PIDGIN_PREFS_ROOT
"/accounts/buddyicon",
1870 update_buddyicon_cb
, status_box
);
1875 pidgin_status_box_get_preferred_height(GtkWidget
*widget
, gint
*minimum_height
,
1876 gint
*natural_height
)
1878 gint box_min_height
, box_nat_height
;
1879 gint border_width
= gtk_container_get_border_width(GTK_CONTAINER (widget
));
1881 gtk_widget_get_preferred_height(PIDGIN_STATUS_BOX(widget
)->toggle_button
,
1882 minimum_height
, natural_height
);
1884 *minimum_height
= MAX(*minimum_height
, 34) + border_width
* 2;
1885 *natural_height
= MAX(*natural_height
, 34) + border_width
* 2;
1887 /* If the gtkwebview is visible, then add some additional padding */
1888 if (PIDGIN_STATUS_BOX(widget
)->webview_visible
) {
1889 gtk_widget_get_preferred_height(PIDGIN_STATUS_BOX(widget
)->vbox
,
1890 &box_min_height
, &box_nat_height
);
1892 if (box_min_height
> 1)
1893 *minimum_height
+= box_min_height
+ border_width
* 2;
1895 if (box_nat_height
> 1)
1896 *natural_height
+= box_nat_height
+ border_width
* 2;
1900 /* From gnome-panel */
1902 do_colorshift (GdkPixbuf
*dest
, GdkPixbuf
*src
, int shift
)
1905 gint width
, height
, has_alpha
, srcrowstride
, destrowstride
;
1906 guchar
*target_pixels
;
1907 guchar
*original_pixels
;
1913 has_alpha
= gdk_pixbuf_get_has_alpha (src
);
1914 width
= gdk_pixbuf_get_width (src
);
1915 height
= gdk_pixbuf_get_height (src
);
1916 srcrowstride
= gdk_pixbuf_get_rowstride (src
);
1917 destrowstride
= gdk_pixbuf_get_rowstride (dest
);
1918 target_pixels
= gdk_pixbuf_get_pixels (dest
);
1919 original_pixels
= gdk_pixbuf_get_pixels (src
);
1921 for (i
= 0; i
< height
; i
++) {
1922 pixdest
= target_pixels
+ i
*destrowstride
;
1923 pixsrc
= original_pixels
+ i
*srcrowstride
;
1924 for (j
= 0; j
< width
; j
++) {
1929 *(pixdest
++) = CLAMP(val
, 0, 255);
1931 *(pixdest
++) = CLAMP(val
, 0, 255);
1933 *(pixdest
++) = CLAMP(val
, 0, 255);
1935 *(pixdest
++) = *(pixsrc
++);
1941 pidgin_status_box_size_allocate(GtkWidget
*widget
,
1942 GtkAllocation
*allocation
)
1944 PidginStatusBox
*status_box
= PIDGIN_STATUS_BOX(widget
);
1945 GtkRequisition req
= {0,0};
1946 GtkAllocation parent_alc
, box_alc
, icon_alc
;
1947 gint border_width
= gtk_container_get_border_width(GTK_CONTAINER (widget
));
1949 gtk_widget_get_preferred_size(status_box
->toggle_button
, NULL
, &req
);
1950 /* Make this icon the same size as other buddy icons in the list; unless it already wants to be bigger */
1952 req
.height
= MAX(req
.height
, 34);
1953 req
.height
+= border_width
* 2;
1955 box_alc
= *allocation
;
1957 box_alc
.width
-= (border_width
* 2);
1958 box_alc
.height
= MAX(1, ((allocation
->height
- req
.height
) - (border_width
*2)));
1959 box_alc
.x
+= border_width
;
1960 box_alc
.y
+= req
.height
+ border_width
;
1961 gtk_widget_size_allocate(status_box
->vbox
, &box_alc
);
1963 parent_alc
= *allocation
;
1964 parent_alc
.height
= MAX(1,req
.height
- (border_width
*2));
1965 parent_alc
.width
-= (border_width
* 2);
1966 parent_alc
.x
+= border_width
;
1967 parent_alc
.y
+= border_width
;
1969 if (status_box
->icon_box
)
1971 parent_alc
.width
-= (parent_alc
.height
+ border_width
);
1972 icon_alc
= parent_alc
;
1973 icon_alc
.height
= MAX(1, icon_alc
.height
) - 2;
1974 icon_alc
.width
= icon_alc
.height
;
1975 icon_alc
.x
+= allocation
->width
- (icon_alc
.width
+ border_width
+ 1);
1978 if (status_box
->icon_size
!= icon_alc
.height
)
1980 status_box
->icon_size
= icon_alc
.height
;
1981 pidgin_status_box_redisplay_buddy_icon(status_box
);
1983 gtk_widget_size_allocate(status_box
->icon_box
, &icon_alc
);
1985 gtk_widget_size_allocate(status_box
->toggle_button
, &parent_alc
);
1986 gtk_widget_set_allocation(GTK_WIDGET(status_box
), allocation
);
1990 pidgin_status_box_draw(GtkWidget
*widget
, cairo_t
*cr
)
1992 PidginStatusBox
*status_box
= PIDGIN_STATUS_BOX(widget
);
1993 gtk_widget_draw(status_box
->toggle_button
, cr
);
1995 gtk_container_propagate_draw(GTK_CONTAINER(widget
), status_box
->vbox
, cr
);
1997 if (status_box
->icon_box
) {
1998 gtk_container_propagate_draw(GTK_CONTAINER(widget
),
1999 status_box
->icon_box
, cr
);
2001 if (status_box
->icon_opaque
) {
2002 GtkAllocation allocation
;
2003 GtkStyleContext
*context
;
2005 gtk_widget_get_allocation(status_box
->icon_box
, &allocation
);
2006 context
= gtk_widget_get_style_context(widget
);
2007 gtk_style_context_add_class(context
, GTK_STYLE_CLASS_BUTTON
);
2008 gtk_render_frame(context
, cr
, allocation
.x
-1, allocation
.y
-1, 34, 34);
2015 pidgin_status_box_forall(GtkContainer
*container
,
2016 gboolean include_internals
,
2017 GtkCallback callback
,
2018 gpointer callback_data
)
2020 PidginStatusBox
*status_box
= PIDGIN_STATUS_BOX (container
);
2022 if (include_internals
)
2024 (* callback
) (status_box
->vbox
, callback_data
);
2025 (* callback
) (status_box
->toggle_button
, callback_data
);
2026 (* callback
) (status_box
->arrow
, callback_data
);
2027 if (status_box
->icon_box
)
2028 (* callback
) (status_box
->icon_box
, callback_data
);
2033 pidgin_status_box_new()
2035 return g_object_new(PIDGIN_TYPE_STATUS_BOX
, "account", NULL
,
2036 "iconsel", TRUE
, NULL
);
2040 pidgin_status_box_new_with_account(PurpleAccount
*account
)
2042 return g_object_new(PIDGIN_TYPE_STATUS_BOX
, "account", account
,
2043 "iconsel", TRUE
, NULL
);
2047 * pidgin_status_box_add:
2048 * @status_box: The status box itself.
2049 * @type: A PidginStatusBoxItemType.
2050 * @pixbuf: The icon to associate with this row in the menu. The
2051 * function will try to decide a pixbuf if none is given.
2052 * @title: The title of this item. For the primitive entries,
2053 * this is something like "Available" or "Away." For
2054 * the saved statuses, this is something like
2055 * "My favorite away message!" This should be
2056 * plaintext (non-markedup) (this function escapes it).
2057 * @desc: The secondary text for this item. This will be
2058 * placed on the row below the title, in a dimmer
2059 * font (generally gray). This text should be plaintext
2060 * (non-markedup) (this function escapes it).
2061 * @data: Data to be associated with this row in the dropdown
2062 * menu. For primitives this is the value of the
2063 * PurpleStatusPrimitive. For saved statuses this is the
2064 * creation timestamp.
2066 * Add a row to the dropdown menu.
2069 pidgin_status_box_add(PidginStatusBox
*status_box
, PidginStatusBoxItemType type
, GdkPixbuf
*pixbuf
,
2070 const char *title
, const char *desc
, gpointer data
)
2074 const char *stock
= NULL
;
2078 text
= g_markup_escape_text(title
, -1);
2082 const char *aa_color
;
2083 gchar
*escaped_title
, *escaped_desc
;
2085 aa_color
= pidgin_get_dim_grey_string(GTK_WIDGET(status_box
));
2087 escaped_title
= g_markup_escape_text(title
, -1);
2088 escaped_desc
= g_markup_escape_text(desc
, -1);
2089 text
= g_strdup_printf("%s - <span color=\"%s\" size=\"smaller\">%s</span>",
2091 aa_color
, escaped_desc
);
2092 g_free(escaped_title
);
2093 g_free(escaped_desc
);
2097 PurpleStatusPrimitive prim
= PURPLE_STATUS_UNSET
;
2098 if (type
== PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
) {
2099 prim
= GPOINTER_TO_INT(data
);
2100 } else if (type
== PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
||
2101 type
== PIDGIN_STATUS_BOX_TYPE_POPULAR
) {
2102 PurpleSavedStatus
*saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
2104 prim
= purple_savedstatus_get_primitive_type(saved
);
2108 stock
= pidgin_stock_id_from_status_primitive(prim
);
2111 gtk_list_store_append(status_box
->dropdown_store
, &iter
);
2112 gtk_list_store_set(status_box
->dropdown_store
, &iter
,
2114 ICON_STOCK_COLUMN
, stock
,
2116 TITLE_COLUMN
, title
,
2119 EMBLEM_VISIBLE_COLUMN
, type
== PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
,
2120 EMBLEM_COLUMN
, GTK_STOCK_SAVE
,
2126 pidgin_status_box_add_separator(PidginStatusBox
*status_box
)
2128 /* Don't do anything unless GTK actually supports
2129 * gtk_combo_box_set_row_separator_func */
2132 gtk_list_store_append(status_box
->dropdown_store
, &iter
);
2133 gtk_list_store_set(status_box
->dropdown_store
, &iter
,
2134 TYPE_COLUMN
, PIDGIN_STATUS_BOX_TYPE_SEPARATOR
,
2139 pidgin_status_box_set_network_available(PidginStatusBox
*status_box
, gboolean available
)
2143 status_box
->network_available
= available
;
2144 pidgin_status_box_refresh(status_box
);
2148 pidgin_status_box_set_connecting(PidginStatusBox
*status_box
, gboolean connecting
)
2152 status_box
->connecting
= connecting
;
2153 pidgin_status_box_refresh(status_box
);
2157 pixbuf_size_prepared_cb(GdkPixbufLoader
*loader
, int width
, int height
, gpointer data
)
2160 GtkIconSize icon_size
= gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM
);
2161 gtk_icon_size_lookup(icon_size
, &w
, &h
);
2163 w
= width
* h
/ height
;
2164 else if (width
> height
)
2165 h
= height
* w
/ width
;
2166 gdk_pixbuf_loader_set_size(loader
, w
, h
);
2170 pidgin_status_box_redisplay_buddy_icon(PidginStatusBox
*status_box
)
2173 /* This is sometimes called before the box is shown, and we will not have a size */
2174 if (status_box
->icon_size
<= 0)
2177 if (status_box
->buddy_icon
)
2178 g_object_unref(status_box
->buddy_icon
);
2179 if (status_box
->buddy_icon_hover
)
2180 g_object_unref(status_box
->buddy_icon_hover
);
2181 status_box
->buddy_icon
= NULL
;
2182 status_box
->buddy_icon_hover
= NULL
;
2184 if (status_box
->buddy_icon_img
!= NULL
)
2186 GdkPixbufLoader
*loader
;
2187 GError
*error
= NULL
;
2189 loader
= gdk_pixbuf_loader_new();
2191 g_signal_connect(G_OBJECT(loader
), "size-prepared", G_CALLBACK(pixbuf_size_prepared_cb
), NULL
);
2192 if (!gdk_pixbuf_loader_write(loader
,
2193 purple_image_get_data(status_box
->buddy_icon_img
),
2194 purple_image_get_size(status_box
->buddy_icon_img
),
2197 purple_debug_warning("gtkstatusbox",
2198 "gdk_pixbuf_loader_write() failed with size=%"
2199 G_GSIZE_FORMAT
": %s", purple_image_get_size(
2200 status_box
->buddy_icon_img
),
2201 error
? error
->message
: "(no error message)");
2203 g_error_free(error
);
2204 } else if (!gdk_pixbuf_loader_close(loader
, &error
) || error
) {
2205 purple_debug_warning("gtkstatusbox",
2206 "gdk_pixbuf_loader_close() failed for image of "
2207 "size %" G_GSIZE_FORMAT
": %s",
2208 purple_image_get_size(status_box
->buddy_icon_img
),
2209 error
? error
->message
: "(no error message)");
2211 g_error_free(error
);
2213 GdkPixbuf
*buf
, *scale
;
2214 int scale_width
, scale_height
;
2216 buf
= gdk_pixbuf_loader_get_pixbuf(loader
);
2217 scale_width
= gdk_pixbuf_get_width(buf
);
2218 scale_height
= gdk_pixbuf_get_height(buf
);
2219 scale
= gdk_pixbuf_new(GDK_COLORSPACE_RGB
, TRUE
, 8, scale_width
, scale_height
);
2220 gdk_pixbuf_fill(scale
, 0x00000000);
2221 gdk_pixbuf_copy_area(buf
, 0, 0, scale_width
, scale_height
, scale
, 0, 0);
2222 if (pidgin_gdk_pixbuf_is_opaque(scale
))
2223 pidgin_gdk_pixbuf_make_round(scale
);
2224 status_box
->buddy_icon
= scale
;
2227 g_object_unref(loader
);
2230 if (status_box
->buddy_icon
== NULL
)
2232 /* Show a placeholder icon */
2233 GtkIconSize icon_size
= gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL
);
2234 status_box
->buddy_icon
= gtk_widget_render_icon(GTK_WIDGET(status_box
),
2235 PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR
,
2236 icon_size
, "PidginStatusBox");
2239 if (status_box
->buddy_icon
!= NULL
) {
2240 status_box
->icon_opaque
= pidgin_gdk_pixbuf_is_opaque(status_box
->buddy_icon
);
2241 gtk_image_set_from_pixbuf(GTK_IMAGE(status_box
->icon
), status_box
->buddy_icon
);
2242 status_box
->buddy_icon_hover
= gdk_pixbuf_copy(status_box
->buddy_icon
);
2243 do_colorshift(status_box
->buddy_icon_hover
, status_box
->buddy_icon_hover
, 32);
2244 gtk_widget_queue_resize(GTK_WIDGET(status_box
));
2249 pidgin_status_box_set_buddy_icon(PidginStatusBox
*status_box
, PurpleImage
*img
)
2251 if (status_box
->buddy_icon_img
)
2252 g_object_unref(status_box
->buddy_icon_img
);
2253 status_box
->buddy_icon_img
= img
;
2254 if (status_box
->buddy_icon_img
!= NULL
)
2255 g_object_ref(status_box
->buddy_icon_img
);
2257 pidgin_status_box_redisplay_buddy_icon(status_box
);
2261 pidgin_status_box_pulse_connecting(PidginStatusBox
*status_box
)
2265 if (!connecting_stock_ids
[++status_box
->connecting_index
])
2266 status_box
->connecting_index
= 0;
2267 pidgin_status_box_refresh(status_box
);
2271 pidgin_status_box_pulse_typing(PidginStatusBox
*status_box
)
2273 if (!typing_stock_ids
[++status_box
->typing_index
])
2274 status_box
->typing_index
= 0;
2275 pidgin_status_box_refresh(status_box
);
2279 activate_currently_selected_status(PidginStatusBox
*status_box
)
2281 PidginStatusBoxItemType type
;
2287 PurpleSavedStatus
*saved_status
= NULL
;
2288 gboolean changed
= TRUE
;
2290 path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
2291 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
2293 gtk_tree_path_free(path
);
2295 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
2301 * If the currently selected status is "New..." or
2302 * "Saved..." or a popular status then do nothing.
2303 * Popular statuses are
2304 * activated elsewhere, and we update the status_box
2305 * accordingly by connecting to the savedstatus-changed
2306 * signal and then calling status_menu_refresh_iter()
2308 if (type
!= PIDGIN_STATUS_BOX_TYPE_PRIMITIVE
)
2311 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
2312 TITLE_COLUMN
, &title
, -1);
2314 message
= pidgin_status_box_get_message(status_box
);
2315 if (!message
|| !*message
)
2317 gtk_widget_hide(status_box
->vbox
);
2318 status_box
->webview_visible
= FALSE
;
2323 if (status_box
->account
== NULL
) {
2324 PurpleStatusType
*acct_status_type
= NULL
;
2325 const char *id
= NULL
; /* id of acct_status_type */
2326 PurpleStatusPrimitive primitive
= GPOINTER_TO_INT(data
);
2328 /* Save the newly selected status to prefs.xml and status.xml */
2330 /* Has the status really been changed? */
2331 if (status_box
->token_status_account
) {
2333 PurpleStatus
*status
;
2334 GtkTreePath
*path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
2335 active
= gtk_tree_path_get_indices(path
)[0];
2337 gtk_tree_path_free(path
);
2339 status
= purple_account_get_active_status(status_box
->token_status_account
);
2341 acct_status_type
= find_status_type_by_index(status_box
->token_status_account
, active
);
2342 id
= purple_status_type_get_id(acct_status_type
);
2344 if (g_str_equal(id
, purple_status_get_id(status
)) &&
2345 purple_strequal(message
, purple_status_get_attr_string(status
, "message")))
2347 /* Selected status and previous status is the same */
2348 PurpleSavedStatus
*ss
= purple_savedstatus_get_current();
2349 /* Make sure that statusbox displays the correct thing.
2350 * It can get messed up if the previous selection was a
2351 * saved status that wasn't supported by this account */
2352 if ((purple_savedstatus_get_primitive_type(ss
) == primitive
)
2353 && purple_savedstatus_is_transient(ss
)
2354 && purple_savedstatus_has_substatuses(ss
))
2358 saved_status
= purple_savedstatus_get_current();
2359 if (purple_savedstatus_get_primitive_type(saved_status
) == primitive
&&
2360 !purple_savedstatus_has_substatuses(saved_status
) &&
2361 purple_strequal(purple_savedstatus_get_message(saved_status
), message
))
2369 /* Manually find the appropriate transient status */
2370 if (status_box
->token_status_account
) {
2371 GList
*iter
= purple_savedstatuses_get_all();
2372 GList
*tmp
, *active_accts
= purple_accounts_get_all_active();
2374 for (; iter
!= NULL
; iter
= iter
->next
) {
2375 PurpleSavedStatus
*ss
= iter
->data
;
2376 const char *ss_msg
= purple_savedstatus_get_message(ss
);
2377 /* find a known transient status that is the same as the
2378 * new selected one */
2379 if ((purple_savedstatus_get_primitive_type(ss
) == primitive
) && purple_savedstatus_is_transient(ss
) &&
2380 purple_savedstatus_has_substatuses(ss
) && /* Must have substatuses */
2381 purple_strequal(ss_msg
, message
))
2383 gboolean found
= FALSE
;
2384 /* this status must have substatuses for all the active accts */
2385 for(tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
2386 PurpleAccount
*acct
= tmp
->data
;
2387 PurpleSavedStatusSub
*sub
= purple_savedstatus_get_substatus(ss
, acct
);
2389 const PurpleStatusType
*sub_type
=
2390 purple_savedstatus_substatus_get_status_type(sub
);
2391 const char *subtype_status_id
= purple_status_type_get_id(sub_type
);
2392 if (purple_strequal(subtype_status_id
, id
)) {
2406 g_list_free(active_accts
);
2409 /* If we've used this type+message before, lookup the transient status */
2410 saved_status
= purple_savedstatus_find_transient_by_type_and_message(primitive
, message
);
2413 /* If this type+message is unique then create a new transient saved status */
2414 if (saved_status
== NULL
)
2416 saved_status
= purple_savedstatus_new(NULL
, primitive
);
2417 purple_savedstatus_set_message(saved_status
, message
);
2418 if (status_box
->token_status_account
) {
2419 GList
*tmp
, *active_accts
= purple_accounts_get_all_active();
2420 for (tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
2421 purple_savedstatus_set_substatus(saved_status
,
2422 (PurpleAccount
*) tmp
->data
, acct_status_type
, message
);
2424 g_list_free(active_accts
);
2428 /* Set the status for each account */
2429 purple_savedstatus_activate(saved_status
);
2434 PurpleStatusType
*status_type
;
2435 PurpleStatus
*status
;
2436 const char *id
= NULL
;
2438 status
= purple_account_get_active_status(status_box
->account
);
2440 active
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(status_box
), "active"));
2442 status_type
= find_status_type_by_index(status_box
->account
, active
);
2443 id
= purple_status_type_get_id(status_type
);
2445 if (g_str_equal(id
, purple_status_get_id(status
)) &&
2446 purple_strequal(message
, purple_status_get_attr_string(status
, "message")))
2448 /* Selected status and previous status is the same */
2455 purple_account_set_status(status_box
->account
, id
,
2456 TRUE
, "message", message
, NULL
);
2458 purple_account_set_status(status_box
->account
, id
,
2461 saved_status
= purple_savedstatus_get_current();
2462 if (purple_savedstatus_is_transient(saved_status
))
2463 purple_savedstatus_set_substatus(saved_status
, status_box
->account
,
2464 status_type
, message
);
2472 static void update_size(PidginStatusBox
*status_box
)
2475 /* TODO WebKit Sizing */
2476 GtkTextBuffer
*buffer
;
2480 GdkRectangle oneline
;
2482 int pad_top
, pad_inside
, pad_bottom
;
2483 gboolean interior_focus
;
2487 if (!status_box
->webview_visible
)
2489 if (status_box
->vbox
!= NULL
)
2490 gtk_widget_set_size_request(status_box
->vbox
, -1, -1);
2495 /* TODO WebKit: Entry sizing */
2496 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(status_box
->webview
));
2500 gtk_text_buffer_get_start_iter(buffer
, &iter
);
2502 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box
->webview
), &iter
, &oneline
);
2503 height
+= oneline
.height
;
2505 } while (display_lines
<= 4 &&
2506 gtk_text_view_forward_display_line(GTK_TEXT_VIEW(status_box
->webview
), &iter
));
2509 * This check fixes the case where the last character entered is a
2510 * newline (shift+return). For some reason the
2511 * gtk_text_view_forward_display_line() function doesn't treat this
2512 * like a new line, and so we think the input box only needs to be
2513 * two lines instead of three, for example. So we check if the
2514 * last character was a newline and add some extra height if so.
2516 if (display_lines
<= 4
2517 && gtk_text_iter_backward_char(&iter
)
2518 && gtk_text_iter_get_char(&iter
) == '\n')
2520 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(status_box
->webview
), &iter
, &oneline
);
2521 height
+= oneline
.height
;
2525 lines
= gtk_text_buffer_get_line_count(buffer
);
2527 /* Show a maximum of 4 lines */
2528 lines
= MIN(lines
, 4);
2529 display_lines
= MIN(display_lines
, 4);
2531 pad_top
= gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(status_box
->webview
));
2532 pad_bottom
= gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(status_box
->webview
));
2533 pad_inside
= gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(status_box
->webview
));
2535 height
+= (pad_top
+ pad_bottom
) * lines
;
2536 height
+= (pad_inside
) * (display_lines
- lines
);
2538 gtk_widget_style_get(status_box
->webview
,
2539 "interior-focus", &interior_focus
,
2540 "focus-line-width", &focus_width
,
2542 if (!interior_focus
)
2543 height
+= 2 * focus_width
;
2545 gtk_widget_set_size_request(status_box
->vbox
, -1, height
+ PIDGIN_HIG_BOX_SPACE
);
2547 gtk_widget_set_size_request(status_box
->vbox
, -1, -1);
2550 static void remove_typing_cb(PidginStatusBox
*status_box
)
2552 if (status_box
->typing
== 0)
2554 /* Nothing has changed, so we don't need to do anything */
2555 status_menu_refresh_iter(status_box
, FALSE
);
2560 /* TODO WebKit: Doesn't do this? */
2561 pidgin_webview_set_populate_primary_clipboard(
2562 PIDGIN_WEBVIEW(status_box
->webview
), TRUE
);
2565 purple_timeout_remove(status_box
->typing
);
2566 status_box
->typing
= 0;
2568 activate_currently_selected_status(status_box
);
2569 pidgin_status_box_refresh(status_box
);
2572 static void pidgin_status_box_changed(PidginStatusBox
*status_box
)
2574 GtkTreePath
*path
= gtk_tree_row_reference_get_path(status_box
->active_row
);
2576 PidginStatusBoxItemType type
;
2578 GList
*accounts
= NULL
, *node
;
2580 gboolean wastyping
= FALSE
;
2583 if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
, path
))
2585 active
= gtk_tree_path_get_indices(path
)[0];
2586 gtk_tree_path_free(path
);
2587 g_object_set_data(G_OBJECT(status_box
), "active", GINT_TO_POINTER(active
));
2589 gtk_tree_model_get(GTK_TREE_MODEL(status_box
->dropdown_store
), &iter
,
2593 if ((wastyping
= (status_box
->typing
!= 0)))
2594 purple_timeout_remove(status_box
->typing
);
2595 status_box
->typing
= 0;
2597 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box
)))
2599 if (type
== PIDGIN_STATUS_BOX_TYPE_POPULAR
|| type
== PIDGIN_STATUS_BOX_TYPE_SAVED_POPULAR
)
2601 PurpleSavedStatus
*saved
;
2602 saved
= purple_savedstatus_find_by_creation_time(GPOINTER_TO_INT(data
));
2603 g_return_if_fail(saved
!= NULL
);
2604 purple_savedstatus_activate(saved
);
2608 if (type
== PIDGIN_STATUS_BOX_TYPE_CUSTOM
)
2610 PurpleSavedStatus
*saved_status
;
2611 saved_status
= purple_savedstatus_get_current();
2612 if (purple_savedstatus_get_primitive_type(saved_status
) == PURPLE_STATUS_AVAILABLE
)
2613 saved_status
= purple_savedstatus_new(NULL
, PURPLE_STATUS_AWAY
);
2614 pidgin_status_editor_show(FALSE
,
2615 purple_savedstatus_is_transient(saved_status
)
2616 ? saved_status
: NULL
);
2617 status_menu_refresh_iter(status_box
, wastyping
);
2619 pidgin_status_box_refresh(status_box
);
2623 if (type
== PIDGIN_STATUS_BOX_TYPE_SAVED
)
2625 pidgin_status_window_show();
2626 status_menu_refresh_iter(status_box
, wastyping
);
2628 pidgin_status_box_refresh(status_box
);
2634 * Show the message box whenever the primitive allows for a
2635 * message attribute on any protocol that is enabled,
2636 * or our protocol, if we have account set
2638 if (status_box
->account
)
2639 accounts
= g_list_prepend(accounts
, status_box
->account
);
2641 accounts
= purple_accounts_get_all_active();
2642 status_box
->webview_visible
= FALSE
;
2643 for (node
= accounts
; node
!= NULL
; node
= node
->next
)
2645 PurpleAccount
*account
;
2646 PurpleStatusType
*status_type
;
2648 account
= node
->data
;
2649 status_type
= purple_account_get_status_type_with_primitive(account
, GPOINTER_TO_INT(data
));
2650 if ((status_type
!= NULL
) &&
2651 (purple_status_type_get_attr(status_type
, "message") != NULL
))
2653 status_box
->webview_visible
= TRUE
;
2657 g_list_free(accounts
);
2659 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box
)))
2661 if (status_box
->webview_visible
)
2663 gtk_widget_show_all(status_box
->vbox
);
2664 status_box
->typing
= purple_timeout_add_seconds(TYPING_TIMEOUT
, (GSourceFunc
)remove_typing_cb
, status_box
);
2665 gtk_widget_grab_focus(status_box
->webview
);
2667 /* TODO WebKit: Doesn't do this? */
2668 pidgin_webview_set_populate_primary_clipboard(
2669 PIDGIN_WEBVIEW(status_box
->webview
), FALSE
);
2672 webkit_web_view_select_all(WEBKIT_WEB_VIEW(status_box
->webview
));
2676 gtk_widget_hide(status_box
->vbox
);
2677 activate_currently_selected_status(status_box
); /* This is where we actually set the status */
2680 pidgin_status_box_refresh(status_box
);
2684 get_statusbox_index(PidginStatusBox
*box
, PurpleSavedStatus
*saved_status
)
2688 switch (purple_savedstatus_get_primitive_type(saved_status
))
2690 /* In reverse order */
2691 case PURPLE_STATUS_OFFLINE
:
2694 case PURPLE_STATUS_INVISIBLE
:
2697 case PURPLE_STATUS_UNAVAILABLE
:
2700 case PURPLE_STATUS_AWAY
:
2703 case PURPLE_STATUS_AVAILABLE
:
2714 webview_changed_cb(PidginWebView
*webview
, void *data
)
2716 PidginStatusBox
*status_box
= (PidginStatusBox
*)data
;
2717 if (gtk_widget_get_sensitive(GTK_WIDGET(status_box
)))
2719 if (status_box
->typing
!= 0) {
2720 pidgin_status_box_pulse_typing(status_box
);
2721 purple_timeout_remove(status_box
->typing
);
2723 status_box
->typing
= purple_timeout_add_seconds(TYPING_TIMEOUT
, (GSourceFunc
)remove_typing_cb
, status_box
);
2725 pidgin_status_box_refresh(status_box
);
2729 webview_format_changed_cb(PidginWebView
*webview
, PidginWebViewButtons buttons
, void *data
)
2731 webview_changed_cb(NULL
, data
);
2734 char *pidgin_status_box_get_message(PidginStatusBox
*status_box
)
2736 if (status_box
->webview_visible
)
2737 return g_strstrip(pidgin_webview_get_body_text(PIDGIN_WEBVIEW(status_box
->webview
)));