1 /*
2 * pluma-status-combo-box.c
3 * This file is part of pluma
4 *
5 * Copyright (C) 2008 - Jesse van den Kieboom
6 * Copyright (C) 2012-2021 MATE Developers
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "pluma-status-combo-box.h"
25
26 #define COMBO_BOX_TEXT_DATA "PlumaStatusComboBoxTextData"
27
28 struct _PlumaStatusComboBoxPrivate
29 {
30 GtkWidget *frame;
31 GtkWidget *button;
32 GtkWidget *hbox;
33 GtkWidget *label;
34 GtkWidget *item;
35 GtkWidget *arrow;
36
37 GtkWidget *menu;
38 GtkWidget *current_item;
39 };
40
41 /* Signals */
42 enum
43 {
44 CHANGED,
45 NUM_SIGNALS
46 };
47
48 /* Properties */
49 enum
50 {
51 PROP_0,
52
53 PROP_LABEL
54 };
55
56 static guint signals[NUM_SIGNALS] = { 0 };
57
G_DEFINE_TYPE_WITH_PRIVATE(PlumaStatusComboBox,pluma_status_combo_box,GTK_TYPE_EVENT_BOX)58 G_DEFINE_TYPE_WITH_PRIVATE (PlumaStatusComboBox, pluma_status_combo_box, GTK_TYPE_EVENT_BOX)
59
60 static void
61 pluma_status_combo_box_finalize (GObject *object)
62 {
63 G_OBJECT_CLASS (pluma_status_combo_box_parent_class)->finalize (object);
64 }
65
66 static void
pluma_status_combo_box_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)67 pluma_status_combo_box_get_property (GObject *object,
68 guint prop_id,
69 GValue *value,
70 GParamSpec *pspec)
71 {
72 PlumaStatusComboBox *obj = PLUMA_STATUS_COMBO_BOX (object);
73
74 switch (prop_id)
75 {
76 case PROP_LABEL:
77 g_value_set_string (value, pluma_status_combo_box_get_label (obj));
78 break;
79 default:
80 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
81 break;
82 }
83 }
84
85 static void
pluma_status_combo_box_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)86 pluma_status_combo_box_set_property (GObject *object,
87 guint prop_id,
88 const GValue *value,
89 GParamSpec *pspec)
90 {
91 PlumaStatusComboBox *obj = PLUMA_STATUS_COMBO_BOX (object);
92
93 switch (prop_id)
94 {
95 case PROP_LABEL:
96 pluma_status_combo_box_set_label (obj, g_value_get_string (value));
97 break;
98 default:
99 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
100 break;
101 }
102 }
103
104 static void
pluma_status_combo_box_constructed(GObject * object)105 pluma_status_combo_box_constructed (GObject *object)
106 {
107 PlumaStatusComboBox *combo = PLUMA_STATUS_COMBO_BOX (object);
108 GtkStyleContext *context;
109 GtkCssProvider *css;
110 GError *error = NULL;
111 const gchar style[] =
112 "* {\n"
113 " padding: 0;\n"
114 "}";
115
116 /* make it as small as possible */
117 css = gtk_css_provider_new ();
118 if (!gtk_css_provider_load_from_data (css, style, -1, &error))
119 {
120 g_warning ("%s", error->message);
121 g_error_free (error);
122 return;
123 }
124
125 context = gtk_widget_get_style_context (GTK_WIDGET (combo));
126 gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (css),
127 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
128 g_object_unref (css);
129 }
130
131 static void
pluma_status_combo_box_changed(PlumaStatusComboBox * combo,GtkMenuItem * item)132 pluma_status_combo_box_changed (PlumaStatusComboBox *combo,
133 GtkMenuItem *item)
134 {
135 const gchar *text;
136
137 text = g_object_get_data (G_OBJECT (item), COMBO_BOX_TEXT_DATA);
138
139 if (text != NULL)
140 {
141 gtk_label_set_markup (GTK_LABEL (combo->priv->item), text);
142 combo->priv->current_item = GTK_WIDGET (item);
143 }
144 }
145
146 static void
pluma_status_combo_box_class_init(PlumaStatusComboBoxClass * klass)147 pluma_status_combo_box_class_init (PlumaStatusComboBoxClass *klass)
148 {
149 GObjectClass *object_class = G_OBJECT_CLASS (klass);
150
151 object_class->finalize = pluma_status_combo_box_finalize;
152 object_class->get_property = pluma_status_combo_box_get_property;
153 object_class->set_property = pluma_status_combo_box_set_property;
154 object_class->constructed = pluma_status_combo_box_constructed;
155
156 klass->changed = pluma_status_combo_box_changed;
157
158 signals[CHANGED] =
159 g_signal_new ("changed",
160 G_OBJECT_CLASS_TYPE (object_class),
161 G_SIGNAL_RUN_LAST,
162 G_STRUCT_OFFSET (PlumaStatusComboBoxClass,
163 changed), NULL, NULL,
164 g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
165 GTK_TYPE_MENU_ITEM);
166
167 g_object_class_install_property (object_class, PROP_LABEL,
168 g_param_spec_string ("label",
169 "LABEL",
170 "The label",
171 NULL,
172 G_PARAM_READWRITE));
173 }
174
175 static void
menu_deactivate(GtkMenu * menu,PlumaStatusComboBox * combo)176 menu_deactivate (GtkMenu *menu,
177 PlumaStatusComboBox *combo)
178 {
179 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo->priv->button), FALSE);
180 }
181
182 static void
button_press_event(GtkWidget * widget,GdkEventButton * event,PlumaStatusComboBox * combo)183 button_press_event (GtkWidget *widget,
184 GdkEventButton *event,
185 PlumaStatusComboBox *combo)
186 {
187 GtkRequisition request;
188 GtkAllocation allocation;
189 gint max_height;
190
191 gtk_widget_get_preferred_size (combo->priv->menu, NULL, &request);
192 gtk_widget_get_allocation (GTK_WIDGET (combo), &allocation);
193
194 /* do something relative to our own height here, maybe we can do better */
195 max_height = allocation.height * 20;
196
197 if (request.height > max_height)
198 {
199 gtk_widget_set_size_request (combo->priv->menu, -1, max_height);
200 gtk_widget_set_size_request (gtk_widget_get_toplevel (combo->priv->menu), -1, max_height);
201 }
202
203 gtk_menu_popup_at_widget (GTK_MENU (combo->priv->menu),
204 gtk_widget_get_parent (widget),
205 GDK_GRAVITY_NORTH_WEST,
206 GDK_GRAVITY_SOUTH_WEST,
207 (const GdkEvent*) event);
208
209 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo->priv->button), TRUE);
210
211 if (combo->priv->current_item)
212 {
213 gtk_menu_shell_select_item (GTK_MENU_SHELL (combo->priv->menu),
214 combo->priv->current_item);
215 }
216 }
217
218 static void
set_shadow_type(PlumaStatusComboBox * combo)219 set_shadow_type (PlumaStatusComboBox *combo)
220 {
221 GtkStyleContext *context;
222 GtkShadowType shadow_type;
223 GtkWidget *statusbar;
224
225 /* This is a hack needed to use the shadow type of a statusbar */
226 statusbar = gtk_statusbar_new ();
227 context = gtk_widget_get_style_context (statusbar);
228
229 gtk_style_context_get_style (context, "shadow-type", &shadow_type, NULL);
230 gtk_frame_set_shadow_type (GTK_FRAME (combo->priv->frame), shadow_type);
231
232 gtk_widget_destroy (statusbar);
233 }
234
235 static void
pluma_status_combo_box_init(PlumaStatusComboBox * self)236 pluma_status_combo_box_init (PlumaStatusComboBox *self)
237 {
238 self->priv = pluma_status_combo_box_get_instance_private (self);
239
240 gtk_event_box_set_visible_window (GTK_EVENT_BOX (self), TRUE);
241
242 self->priv->frame = gtk_frame_new (NULL);
243 gtk_widget_show (self->priv->frame);
244
245 self->priv->button = gtk_toggle_button_new ();
246 gtk_widget_set_name (self->priv->button, "pluma-status-combo-button");
247 gtk_button_set_relief (GTK_BUTTON (self->priv->button), GTK_RELIEF_NONE);
248 gtk_widget_show (self->priv->button);
249
250 set_shadow_type (self);
251
252 self->priv->hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
253 gtk_widget_show (self->priv->hbox);
254
255 gtk_container_add (GTK_CONTAINER (self), self->priv->frame);
256 gtk_container_add (GTK_CONTAINER (self->priv->frame), self->priv->button);
257 gtk_container_add (GTK_CONTAINER (self->priv->button), self->priv->hbox);
258
259 self->priv->label = gtk_label_new ("");
260 gtk_widget_show (self->priv->label);
261
262 gtk_label_set_single_line_mode (GTK_LABEL (self->priv->label), TRUE);
263 gtk_label_set_xalign (GTK_LABEL (self->priv->label), 0.0);
264
265 gtk_box_pack_start (GTK_BOX (self->priv->hbox), self->priv->label, FALSE, TRUE, 0);
266
267 self->priv->item = gtk_label_new ("");
268 gtk_widget_show (self->priv->item);
269
270 gtk_label_set_single_line_mode (GTK_LABEL (self->priv->item), TRUE);
271 gtk_widget_set_halign (self->priv->item, GTK_ALIGN_START);
272
273 gtk_box_pack_start (GTK_BOX (self->priv->hbox), self->priv->item, TRUE, TRUE, 0);
274
275 self->priv->arrow = gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
276 gtk_widget_show (self->priv->arrow);
277 gtk_widget_set_halign (self->priv->arrow, GTK_ALIGN_CENTER);
278 gtk_widget_set_valign (self->priv->arrow, GTK_ALIGN_CENTER);
279
280 gtk_box_pack_start (GTK_BOX (self->priv->hbox), self->priv->arrow, FALSE, TRUE, 0);
281
282 self->priv->menu = gtk_menu_new ();
283 g_object_ref_sink (self->priv->menu);
284
285 g_signal_connect (self->priv->button,
286 "button-press-event",
287 G_CALLBACK (button_press_event),
288 self);
289 g_signal_connect (self->priv->menu,
290 "deactivate",
291 G_CALLBACK (menu_deactivate),
292 self);
293 }
294
295 /* public functions */
296
297 /**
298 * pluma_status_combo_box_new:
299 * @label: (allow-none):
300 */
301 GtkWidget *
pluma_status_combo_box_new(const gchar * label)302 pluma_status_combo_box_new (const gchar *label)
303 {
304 return g_object_new (PLUMA_TYPE_STATUS_COMBO_BOX, "label", label, NULL);
305 }
306
307 /**
308 * pluma_status_combo_box_set_label:
309 * @combo:
310 * @label: (allow-none):
311 */
312 void
pluma_status_combo_box_set_label(PlumaStatusComboBox * combo,const gchar * label)313 pluma_status_combo_box_set_label (PlumaStatusComboBox *combo,
314 const gchar *label)
315 {
316 gchar *text;
317
318 g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
319
320 text = g_strconcat (" ", label, ": ", NULL);
321 gtk_label_set_markup (GTK_LABEL (combo->priv->label), text);
322 g_free (text);
323 }
324
325 const gchar *
pluma_status_combo_box_get_label(PlumaStatusComboBox * combo)326 pluma_status_combo_box_get_label (PlumaStatusComboBox *combo)
327 {
328 g_return_val_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo), NULL);
329
330 return gtk_label_get_label (GTK_LABEL (combo->priv->label));
331 }
332
333 static void
item_activated(GtkMenuItem * item,PlumaStatusComboBox * combo)334 item_activated (GtkMenuItem *item,
335 PlumaStatusComboBox *combo)
336 {
337 pluma_status_combo_box_set_item (combo, item);
338 }
339
340 /**
341 * pluma_status_combo_box_add_item:
342 * @combo:
343 * @item:
344 * @text: (allow-none):
345 */
346 void
pluma_status_combo_box_add_item(PlumaStatusComboBox * combo,GtkMenuItem * item,const gchar * text)347 pluma_status_combo_box_add_item (PlumaStatusComboBox *combo,
348 GtkMenuItem *item,
349 const gchar *text)
350 {
351 g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
352 g_return_if_fail (GTK_IS_MENU_ITEM (item));
353
354 gtk_menu_shell_append (GTK_MENU_SHELL (combo->priv->menu), GTK_WIDGET (item));
355
356 pluma_status_combo_box_set_item_text (combo, item, text);
357 g_signal_connect (item, "activate", G_CALLBACK (item_activated), combo);
358 }
359
360 void
pluma_status_combo_box_remove_item(PlumaStatusComboBox * combo,GtkMenuItem * item)361 pluma_status_combo_box_remove_item (PlumaStatusComboBox *combo,
362 GtkMenuItem *item)
363 {
364 g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
365 g_return_if_fail (GTK_IS_MENU_ITEM (item));
366
367 gtk_container_remove (GTK_CONTAINER (combo->priv->menu),
368 GTK_WIDGET (item));
369 }
370
371 /**
372 * pluma_status_combo_box_get_items:
373 * @combo:
374 *
375 * Returns: (element-type Gtk.Widget) (transfer container):
376 */
377 GList *
pluma_status_combo_box_get_items(PlumaStatusComboBox * combo)378 pluma_status_combo_box_get_items (PlumaStatusComboBox *combo)
379 {
380 g_return_val_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo), NULL);
381
382 return gtk_container_get_children (GTK_CONTAINER (combo->priv->menu));
383 }
384
385 const gchar *
pluma_status_combo_box_get_item_text(PlumaStatusComboBox * combo,GtkMenuItem * item)386 pluma_status_combo_box_get_item_text (PlumaStatusComboBox *combo,
387 GtkMenuItem *item)
388 {
389 const gchar *ret = NULL;
390
391 g_return_val_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo), NULL);
392 g_return_val_if_fail (GTK_IS_MENU_ITEM (item), NULL);
393
394 ret = g_object_get_data (G_OBJECT (item), COMBO_BOX_TEXT_DATA);
395
396 return ret;
397 }
398
399 /**
400 * pluma_status_combo_box_set_item_text:
401 * @combo:
402 * @item:
403 * @text: (allow-none):
404 */
405 void
pluma_status_combo_box_set_item_text(PlumaStatusComboBox * combo,GtkMenuItem * item,const gchar * text)406 pluma_status_combo_box_set_item_text (PlumaStatusComboBox *combo,
407 GtkMenuItem *item,
408 const gchar *text)
409 {
410 g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
411 g_return_if_fail (GTK_IS_MENU_ITEM (item));
412
413 g_object_set_data_full (G_OBJECT (item),
414 COMBO_BOX_TEXT_DATA,
415 g_strdup (text),
416 (GDestroyNotify)g_free);
417 }
418
419 void
pluma_status_combo_box_set_item(PlumaStatusComboBox * combo,GtkMenuItem * item)420 pluma_status_combo_box_set_item (PlumaStatusComboBox *combo,
421 GtkMenuItem *item)
422 {
423 g_return_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo));
424 g_return_if_fail (GTK_IS_MENU_ITEM (item));
425
426 g_signal_emit (combo, signals[CHANGED], 0, item, NULL);
427 }
428
429 GtkLabel *
pluma_status_combo_box_get_item_label(PlumaStatusComboBox * combo)430 pluma_status_combo_box_get_item_label (PlumaStatusComboBox *combo)
431 {
432 g_return_val_if_fail (PLUMA_IS_STATUS_COMBO_BOX (combo), NULL);
433
434 return GTK_LABEL (combo->priv->item);
435 }
436
437