| 26 |
26 |
| 27 #include <purple.h> |
27 #include <purple.h> |
| 28 |
28 |
| 29 #include "pidginstatusbox.h" |
29 #include "pidginstatusbox.h" |
| 30 #include "pidginiconname.h" |
30 #include "pidginiconname.h" |
| 31 |
31 #include "pidginstatusdisplay.h" |
| 32 #define PRIMITIVE_FORMAT "primitive_%d" |
32 |
| 33 #define SAVEDSTATUS_FORMAT "savedstatus_%lu" |
33 #define SAVEDSTATUS_FORMAT "savedstatus_%lu" |
| 34 |
|
| 35 typedef enum { |
|
| 36 PIDGIN_STATUS_BOX_TYPE_SEPARATOR, |
|
| 37 PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, |
|
| 38 PIDGIN_STATUS_BOX_TYPE_POPULAR, |
|
| 39 PIDGIN_STATUS_BOX_TYPE_ACTION, |
|
| 40 PIDGIN_STATUS_BOX_NUM_TYPES |
|
| 41 } PidginStatusBoxItemType; |
|
| 42 |
34 |
| 43 struct _PidginStatusBox { |
35 struct _PidginStatusBox { |
| 44 GtkBox parent; |
36 GtkBox parent; |
| 45 |
37 |
| 46 GtkListStore *model; |
38 GtkWidget *button; |
| 47 GtkWidget *combo; |
39 PidginStatusDisplay *display; |
| 48 |
40 GtkPopover *popover; |
| 49 /* This is used to flipback to the correct status when one of the actions |
41 |
| 50 * items is selected. |
42 GMenu *primitives; |
| 51 */ |
43 GMenu *saved_statuses; |
| 52 gchar *active_id; |
44 GList *custom_widgets; |
| 53 }; |
45 }; |
| 54 |
46 |
| 55 enum { |
|
| 56 ID_COLUMN, |
|
| 57 TYPE_COLUMN, /* PidginStatusBoxItemType */ |
|
| 58 ICON_NAME_COLUMN, |
|
| 59 PRIMITIVE_COLUMN, |
|
| 60 TEXT_COLUMN, |
|
| 61 /* This value depends on TYPE_COLUMN. For POPULAR types, this is the |
|
| 62 * creation time. |
|
| 63 */ |
|
| 64 DATA_COLUMN, |
|
| 65 EMBLEM_VISIBLE_COLUMN, |
|
| 66 NUM_COLUMNS |
|
| 67 }; |
|
| 68 |
|
| 69 G_DEFINE_TYPE(PidginStatusBox, pidgin_status_box, GTK_TYPE_BOX) |
47 G_DEFINE_TYPE(PidginStatusBox, pidgin_status_box, GTK_TYPE_BOX) |
| 70 |
|
| 71 /* This prototype is necessary so we can block this signal handler when we are |
|
| 72 * manually updating the combobox. |
|
| 73 */ |
|
| 74 static void pidgin_status_box_combo_changed_cb(GtkComboBox *combo, gpointer user_data); |
|
| 75 |
48 |
| 76 /****************************************************************************** |
49 /****************************************************************************** |
| 77 * Helpers |
50 * Helpers |
| 78 *****************************************************************************/ |
51 *****************************************************************************/ |
| 79 static void |
52 static GtkWidget * |
| 80 pidgin_status_box_update_to_status(PidginStatusBox *status_box, |
53 pidgin_status_box_make_primitive_widget(const char *action, const char *id) { |
| 81 PurpleSavedStatus *status) |
54 GtkWidget *button = NULL; |
| 82 { |
55 GtkWidget *display = NULL; |
| 83 gchar *id = NULL; |
56 PurpleStatusPrimitive primitive = PURPLE_STATUS_UNSET; |
| 84 gboolean set = FALSE; |
57 |
| |
58 primitive = purple_primitive_get_type_from_id(id); |
| |
59 display = pidgin_status_display_new_for_primitive(primitive); |
| |
60 |
| |
61 button = gtk_button_new(); |
| |
62 gtk_widget_add_css_class(button, "flat"); |
| |
63 gtk_button_set_child(GTK_BUTTON(button), display); |
| |
64 |
| |
65 gtk_actionable_set_action_name(GTK_ACTIONABLE(button), action); |
| |
66 gtk_actionable_set_action_target(GTK_ACTIONABLE(button), |
| |
67 (const char *)G_VARIANT_TYPE_INT32, |
| |
68 primitive); |
| |
69 |
| |
70 return button; |
| |
71 } |
| |
72 |
| |
73 static void |
| |
74 pidgin_status_box_populate_primitives(PidginStatusBox *status_box) { |
| |
75 GtkPopoverMenu *popover = GTK_POPOVER_MENU(status_box->popover); |
| |
76 GMenuModel *menu = G_MENU_MODEL(status_box->primitives); |
| |
77 gint n_items = 0; |
| |
78 |
| |
79 n_items = g_menu_model_get_n_items(menu); |
| |
80 for(gint index = 0; index < n_items; index++) { |
| |
81 GtkWidget *button = NULL; |
| |
82 char *action = NULL; |
| |
83 char *target = NULL; |
| |
84 char *custom_id = NULL; |
| |
85 |
| |
86 g_menu_model_get_item_attribute(menu, index, G_MENU_ATTRIBUTE_ACTION, |
| |
87 "s", &action); |
| |
88 g_menu_model_get_item_attribute(menu, index, G_MENU_ATTRIBUTE_TARGET, |
| |
89 "s", &target); |
| |
90 g_menu_model_get_item_attribute(menu, index, "custom", "s", &custom_id); |
| |
91 |
| |
92 button = pidgin_status_box_make_primitive_widget(action, target); |
| |
93 gtk_popover_menu_add_child(popover, button, custom_id); |
| |
94 |
| |
95 g_free(action); |
| |
96 g_free(target); |
| |
97 g_free(custom_id); |
| |
98 } |
| |
99 } |
| |
100 |
| |
101 static char * |
| |
102 pidgin_status_box_make_savedstatus_widget(PurpleSavedStatus *saved_status, |
| |
103 GtkWidget **widget) |
| |
104 { |
| |
105 GtkWidget *button = NULL; |
| |
106 GtkWidget *display = NULL; |
| 85 time_t creation_time = 0; |
107 time_t creation_time = 0; |
| 86 |
108 |
| 87 /* Try to set the combo box to the saved status. */ |
109 display = pidgin_status_display_new_for_saved_status(saved_status); |
| 88 creation_time = purple_savedstatus_get_creation_time(status); |
110 |
| 89 id = g_strdup_printf(SAVEDSTATUS_FORMAT, creation_time); |
111 if(!purple_savedstatus_is_transient(saved_status)) { |
| 90 |
112 GtkWidget *image = gtk_image_new_from_icon_name("document-save"); |
| 91 set = gtk_combo_box_set_active_id(GTK_COMBO_BOX(status_box->combo), id); |
113 gtk_widget_set_halign(image, GTK_ALIGN_END); |
| 92 g_free(id); |
114 gtk_widget_set_hexpand(image, TRUE); |
| 93 |
115 gtk_box_append(GTK_BOX(display), image); |
| 94 /* If we failed to set via the savedstatus, fallback to the primitive. */ |
116 } |
| 95 if(!set) { |
117 |
| 96 PurpleStatusPrimitive primitive; |
118 button = gtk_button_new(); |
| 97 |
119 gtk_widget_add_css_class(button, "flat"); |
| 98 primitive = purple_savedstatus_get_primitive_type(status); |
120 gtk_button_set_child(GTK_BUTTON(button), display); |
| 99 id = g_strdup_printf(PRIMITIVE_FORMAT, primitive); |
121 |
| 100 |
122 creation_time = purple_savedstatus_get_creation_time(saved_status); |
| 101 gtk_combo_box_set_active_id(GTK_COMBO_BOX(status_box->combo), id); |
123 gtk_actionable_set_action_name(GTK_ACTIONABLE(button), "status.set-saved"); |
| 102 g_free(id); |
124 gtk_actionable_set_action_target(GTK_ACTIONABLE(button), |
| 103 } |
125 (const char *)G_VARIANT_TYPE_INT64, |
| 104 } |
126 creation_time); |
| 105 |
127 *widget = button; |
| 106 static void |
128 |
| 107 pidgin_status_box_add(PidginStatusBox *status_box, |
129 return g_strdup_printf(SAVEDSTATUS_FORMAT, creation_time); |
| 108 PidginStatusBoxItemType type, |
130 } |
| 109 PurpleStatusPrimitive primitive, const gchar *text, |
131 |
| 110 gpointer data) |
132 static void |
| 111 { |
133 pidgin_status_box_populate_saved_statuses(PidginStatusBox *status_box) |
| 112 GtkTreeIter iter; |
134 { |
| 113 gchar *id = NULL, *escaped_text = NULL; |
135 GtkPopoverMenu *popover_menu = NULL; |
| 114 const gchar *icon_name = NULL; |
136 GMenu *menu = NULL; |
| 115 gboolean emblem_visible = FALSE; |
|
| 116 |
|
| 117 escaped_text = g_markup_escape_text(text, -1); |
|
| 118 |
|
| 119 if(type == PIDGIN_STATUS_BOX_TYPE_POPULAR) { |
|
| 120 PurpleSavedStatus *saved_status = NULL; |
|
| 121 time_t creation_time = GPOINTER_TO_INT(data); |
|
| 122 |
|
| 123 saved_status = purple_savedstatus_find_by_creation_time(creation_time); |
|
| 124 |
|
| 125 if(saved_status != NULL) { |
|
| 126 id = g_strdup_printf(SAVEDSTATUS_FORMAT, creation_time); |
|
| 127 |
|
| 128 if(!purple_savedstatus_is_transient(saved_status)) { |
|
| 129 emblem_visible = TRUE; |
|
| 130 } |
|
| 131 } |
|
| 132 } |
|
| 133 |
|
| 134 if(id == NULL && primitive != PURPLE_STATUS_UNSET) { |
|
| 135 id = g_strdup_printf(PRIMITIVE_FORMAT, primitive); |
|
| 136 } |
|
| 137 |
|
| 138 icon_name = pidgin_icon_name_from_status_primitive(primitive, NULL); |
|
| 139 |
|
| 140 gtk_list_store_append(status_box->model, &iter); |
|
| 141 gtk_list_store_set(status_box->model, &iter, |
|
| 142 ID_COLUMN, id, |
|
| 143 TYPE_COLUMN, type, |
|
| 144 ICON_NAME_COLUMN, icon_name, |
|
| 145 PRIMITIVE_COLUMN, primitive, |
|
| 146 TEXT_COLUMN, escaped_text, |
|
| 147 DATA_COLUMN, data, |
|
| 148 EMBLEM_VISIBLE_COLUMN, emblem_visible, |
|
| 149 -1); |
|
| 150 |
|
| 151 g_free(escaped_text); |
|
| 152 g_free(id); |
|
| 153 } |
|
| 154 |
|
| 155 static gboolean |
|
| 156 pidgin_status_box_row_separator_func(GtkTreeModel *model, GtkTreeIter *iter, |
|
| 157 G_GNUC_UNUSED gpointer data) |
|
| 158 { |
|
| 159 PidginStatusBoxItemType type; |
|
| 160 |
|
| 161 gtk_tree_model_get(model, iter, TYPE_COLUMN, &type, -1); |
|
| 162 |
|
| 163 return type == PIDGIN_STATUS_BOX_TYPE_SEPARATOR; |
|
| 164 } |
|
| 165 |
|
| 166 static void |
|
| 167 pidgin_status_box_add_separator(PidginStatusBox *status_box) { |
|
| 168 GtkTreeIter iter; |
|
| 169 |
|
| 170 gtk_list_store_append(status_box->model, &iter); |
|
| 171 gtk_list_store_set(status_box->model, &iter, |
|
| 172 TYPE_COLUMN, PIDGIN_STATUS_BOX_TYPE_SEPARATOR, |
|
| 173 -1); |
|
| 174 } |
|
| 175 |
|
| 176 static void |
|
| 177 pidgin_status_box_update_saved_statuses(PidginStatusBox *status_box) { |
|
| 178 GList *list, *cur; |
137 GList *list, *cur; |
| 179 |
138 |
| 180 list = purple_savedstatuses_get_popular(6); |
139 list = purple_savedstatuses_get_popular(6); |
| 181 if (list == NULL) { |
140 if (list == NULL) { |
| 182 /* Odd... oh well, nothing we can do about it. */ |
141 /* Odd... oh well, nothing we can do about it. */ |
| 183 return; |
142 return; |
| 184 } |
143 } |
| 185 |
144 |
| |
145 popover_menu = GTK_POPOVER_MENU(status_box->popover); |
| |
146 menu = status_box->saved_statuses; |
| 186 for(cur = list; cur != NULL; cur = cur->next) { |
147 for(cur = list; cur != NULL; cur = cur->next) { |
| 187 PurpleSavedStatus *saved = cur->data; |
148 PurpleSavedStatus *saved = cur->data; |
| 188 GString *text = NULL; |
149 GtkWidget *widget = NULL; |
| 189 time_t creation_time; |
150 char *id = NULL; |
| 190 |
151 GMenuItem *item = NULL; |
| 191 text = g_string_new(purple_savedstatus_get_title(saved)); |
152 |
| 192 |
153 id = pidgin_status_box_make_savedstatus_widget(saved, &widget); |
| 193 if(!purple_savedstatus_is_transient(saved)) { |
154 item = g_menu_item_new(NULL, NULL); |
| 194 /* |
155 g_menu_item_set_attribute(item, "custom", "s", id); |
| 195 * Transient statuses do not have a title, so the savedstatus |
156 g_menu_append_item(menu, item); |
| 196 * API returns the message when purple_savedstatus_get_title() is |
157 gtk_popover_menu_add_child(popover_menu, widget, id); |
| 197 * called, so we don't need to get the message a second time. |
158 status_box->custom_widgets = g_list_prepend(status_box->custom_widgets, |
| 198 */ |
159 widget); |
| 199 const gchar *message = NULL; |
160 |
| 200 |
161 g_free(id); |
| 201 message = purple_savedstatus_get_message(saved); |
|
| 202 if(message != NULL) { |
|
| 203 gchar *stripped = purple_markup_strip_html(message); |
|
| 204 |
|
| 205 purple_util_chrreplace(stripped, '\n', ' '); |
|
| 206 g_string_append_printf(text, " - %s", stripped); |
|
| 207 g_free(stripped); |
|
| 208 } |
|
| 209 } |
|
| 210 |
|
| 211 creation_time = purple_savedstatus_get_creation_time(saved); |
|
| 212 pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_POPULAR, |
|
| 213 purple_savedstatus_get_primitive_type(saved), |
|
| 214 text->str, GINT_TO_POINTER(creation_time)); |
|
| 215 |
|
| 216 g_string_free(text, TRUE); |
|
| 217 } |
162 } |
| 218 |
163 |
| 219 g_list_free(list); |
164 g_list_free(list); |
| 220 |
|
| 221 pidgin_status_box_add_separator(status_box); |
|
| 222 } |
|
| 223 |
|
| 224 static void |
|
| 225 pidgin_status_box_populate(PidginStatusBox *status_box) { |
|
| 226 pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, |
|
| 227 PURPLE_STATUS_AVAILABLE, _("Available"), NULL); |
|
| 228 pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, |
|
| 229 PURPLE_STATUS_AWAY, _("Away"), NULL); |
|
| 230 pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, |
|
| 231 PURPLE_STATUS_UNAVAILABLE, _("Do not disturb"), |
|
| 232 NULL); |
|
| 233 pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, |
|
| 234 PURPLE_STATUS_INVISIBLE, _("Invisible"), NULL); |
|
| 235 pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_PRIMITIVE, |
|
| 236 PURPLE_STATUS_OFFLINE, _("Offline"), NULL); |
|
| 237 |
|
| 238 pidgin_status_box_add_separator(status_box); |
|
| 239 |
|
| 240 pidgin_status_box_update_saved_statuses(status_box); |
|
| 241 |
|
| 242 pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_ACTION, |
|
| 243 PURPLE_STATUS_UNSET, _("New Status..."), |
|
| 244 "new-status"); |
|
| 245 pidgin_status_box_add(status_box, PIDGIN_STATUS_BOX_TYPE_ACTION, |
|
| 246 PURPLE_STATUS_UNSET, _("Saved Statuses..."), |
|
| 247 "status-manager"); |
|
| 248 } |
165 } |
| 249 |
166 |
| 250 /****************************************************************************** |
167 /****************************************************************************** |
| 251 * Callbacks |
168 * Callbacks |
| 252 *****************************************************************************/ |
169 *****************************************************************************/ |
| 253 static void |
170 static void |
| 254 pidgin_status_box_combo_changed_cb(GtkComboBox *combo, gpointer user_data) { |
171 pidgin_status_box_set_primitive(G_GNUC_UNUSED GSimpleAction *action, |
| 255 PidginStatusBox *status_box = user_data; |
172 GVariant *parameter, gpointer data) |
| 256 PidginStatusBoxItemType type; |
173 { |
| |
174 PidginStatusBox *status_box = data; |
| 257 PurpleSavedStatus *saved_status = NULL; |
175 PurpleSavedStatus *saved_status = NULL; |
| 258 PurpleStatusPrimitive primitive; |
176 PurpleStatusPrimitive primitive; |
| 259 GtkTreeIter iter; |
177 |
| 260 gchar *id = NULL; |
178 gtk_menu_button_popdown(GTK_MENU_BUTTON(status_box->button)); |
| 261 gpointer data; |
179 |
| 262 |
180 if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT32)) { |
| 263 if(!gtk_combo_box_get_active_iter(combo, &iter)) { |
181 g_critical("status.set-primitive action parameter is of incorrect type %s", |
| |
182 g_variant_get_type_string(parameter)); |
| 264 return; |
183 return; |
| 265 } |
184 } |
| 266 |
185 |
| 267 gtk_tree_model_get(GTK_TREE_MODEL(status_box->model), &iter, |
186 primitive = g_variant_get_int32(parameter); |
| 268 ID_COLUMN, &id, |
187 |
| 269 TYPE_COLUMN, &type, |
188 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL); |
| 270 PRIMITIVE_COLUMN, &primitive, |
189 if(saved_status == NULL) { |
| 271 DATA_COLUMN, &data, |
190 saved_status = purple_savedstatus_new(NULL, primitive); |
| 272 -1); |
|
| 273 |
|
| 274 if(type == PIDGIN_STATUS_BOX_TYPE_PRIMITIVE) { |
|
| 275 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, NULL); |
|
| 276 if(saved_status == NULL) { |
|
| 277 saved_status = purple_savedstatus_new(NULL, primitive); |
|
| 278 } |
|
| 279 } else if(type == PIDGIN_STATUS_BOX_TYPE_POPULAR) { |
|
| 280 time_t creation_time = GPOINTER_TO_INT(data); |
|
| 281 |
|
| 282 saved_status = purple_savedstatus_find_by_creation_time(creation_time); |
|
| 283 } else if(type == PIDGIN_STATUS_BOX_TYPE_ACTION) { |
|
| 284 GApplication *application = NULL; |
|
| 285 const gchar *action_name = (const gchar *)data; |
|
| 286 |
|
| 287 application = g_application_get_default(); |
|
| 288 |
|
| 289 g_action_group_activate_action(G_ACTION_GROUP(application), |
|
| 290 action_name, NULL); |
|
| 291 |
|
| 292 gtk_combo_box_set_active_id(combo, status_box->active_id); |
|
| 293 } |
191 } |
| 294 |
192 |
| 295 if(saved_status != NULL) { |
193 if(saved_status != NULL) { |
| 296 if(saved_status != purple_savedstatus_get_current()) { |
194 if(saved_status != purple_savedstatus_get_current()) { |
| 297 purple_savedstatus_activate(saved_status); |
195 purple_savedstatus_activate(saved_status); |
| 298 } |
196 } |
| 299 |
197 } |
| 300 g_free(status_box->active_id); |
198 } |
| 301 status_box->active_id = id; |
199 |
| 302 } else { |
200 static void |
| 303 g_free(id); |
201 pidgin_status_box_set_saved_status(G_GNUC_UNUSED GSimpleAction *action, |
| |
202 GVariant *parameter, gpointer data) |
| |
203 { |
| |
204 PidginStatusBox *status_box = data; |
| |
205 PurpleSavedStatus *saved_status = NULL; |
| |
206 time_t creation_time = 0; |
| |
207 |
| |
208 gtk_menu_button_popdown(GTK_MENU_BUTTON(status_box->button)); |
| |
209 |
| |
210 if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT64)) { |
| |
211 g_critical("status.set-saved action parameter is of incorrect type %s", |
| |
212 g_variant_get_type_string(parameter)); |
| |
213 return; |
| |
214 } |
| |
215 |
| |
216 creation_time = (time_t)g_variant_get_int64(parameter); |
| |
217 saved_status = purple_savedstatus_find_by_creation_time(creation_time); |
| |
218 |
| |
219 if(saved_status != NULL) { |
| |
220 if(saved_status != purple_savedstatus_get_current()) { |
| |
221 purple_savedstatus_activate(saved_status); |
| |
222 } |
| 304 } |
223 } |
| 305 } |
224 } |
| 306 |
225 |
| 307 static void |
226 static void |
| 308 pidgin_status_box_savedstatus_changed_cb(PurpleSavedStatus *now, |
227 pidgin_status_box_savedstatus_changed_cb(PurpleSavedStatus *now, |