1 /*
2  * Copyright © 2018 Canonical Ltd.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <config.h>
19 #include "cc-input-row.h"
20 #include "cc-input-source-ibus.h"
21 
22 struct _CcInputRow
23 {
24   GtkListBoxRow    parent_instance;
25 
26   CcInputSource   *source;
27 
28   GtkEventBox     *drag_handle;
29   GtkLabel        *name_label;
30   GtkButton       *remove_button;
31   GtkButton       *settings_button;
32   GtkSeparator    *settings_separator;
33 
34   GtkListBox      *drag_widget;
35 };
36 
37 G_DEFINE_TYPE (CcInputRow, cc_input_row, GTK_TYPE_LIST_BOX_ROW)
38 
39 enum
40 {
41   SIGNAL_SHOW_SETTINGS,
42   SIGNAL_SHOW_LAYOUT,
43   SIGNAL_MOVE_ROW,
44   SIGNAL_REMOVE_ROW,
45   SIGNAL_LAST
46 };
47 
48 static guint signals[SIGNAL_LAST] = { 0, };
49 
50 static void
drag_begin_cb(CcInputRow * self,GdkDragContext * drag_context)51 drag_begin_cb (CcInputRow     *self,
52                GdkDragContext *drag_context)
53 {
54   GtkAllocation alloc;
55   gint x = 0, y = 0;
56 
57   gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
58 
59   gdk_window_get_device_position (gtk_widget_get_window (GTK_WIDGET (self)),
60                                   gdk_drag_context_get_device (drag_context),
61                                   &x, &y, NULL);
62 
63   self->drag_widget = GTK_LIST_BOX (gtk_list_box_new ());
64   gtk_widget_show (GTK_WIDGET (self->drag_widget));
65   gtk_widget_set_size_request (GTK_WIDGET (self->drag_widget), alloc.width, alloc.height);
66   CcInputRow *drag_row = cc_input_row_new (self->source);
67   gtk_widget_show (GTK_WIDGET (drag_row));
68   gtk_container_add (GTK_CONTAINER (self->drag_widget), GTK_WIDGET (drag_row));
69   gtk_list_box_drag_highlight_row (self->drag_widget, GTK_LIST_BOX_ROW (drag_row));
70 
71   gtk_drag_set_icon_widget (drag_context, GTK_WIDGET (self->drag_widget), x - alloc.x, y - alloc.y);
72 }
73 
74 static void
drag_end_cb(CcInputRow * self)75 drag_end_cb (CcInputRow *self)
76 {
77   g_clear_pointer ((GtkWidget **) &self->drag_widget, gtk_widget_destroy);
78 }
79 
80 static void
drag_data_get_cb(CcInputRow * self,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time_)81 drag_data_get_cb (CcInputRow       *self,
82                   GdkDragContext   *context,
83                   GtkSelectionData *selection_data,
84                   guint             info,
85                   guint             time_)
86 {
87   gtk_selection_data_set (selection_data,
88                           gdk_atom_intern_static_string ("GTK_LIST_BOX_ROW"),
89                           32,
90                           (const guchar *)&self,
91                           sizeof (gpointer));
92 }
93 
94 static void
drag_data_received_cb(CcInputRow * self,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time_)95 drag_data_received_cb (CcInputRow       *self,
96                        GdkDragContext   *context,
97                        gint              x,
98                        gint              y,
99                        GtkSelectionData *selection_data,
100                        guint             info,
101                        guint             time_)
102 {
103   CcInputRow *source;
104 
105   source = *((CcInputRow **) gtk_selection_data_get_data (selection_data));
106 
107   if (source == self)
108     return;
109 
110   g_signal_emit (source,
111                  signals[SIGNAL_MOVE_ROW],
112                  0,
113                  self);
114 }
115 
116 static void
move_up_button_clicked_cb(CcInputRow * self,GtkButton * button)117 move_up_button_clicked_cb (CcInputRow *self,
118                            GtkButton  *button)
119 {
120   GtkListBox *list_box = GTK_LIST_BOX (gtk_widget_get_parent (GTK_WIDGET (self)));
121   gint previous_idx = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (self)) - 1;
122   GtkListBoxRow *previous_row = gtk_list_box_get_row_at_index (list_box, previous_idx);
123 
124   if (previous_row == NULL)
125     return;
126 
127   g_signal_emit (self,
128                  signals[SIGNAL_MOVE_ROW],
129                  0,
130                  previous_row);
131 }
132 
133 static void
move_down_button_clicked_cb(CcInputRow * self,GtkButton * button)134 move_down_button_clicked_cb (CcInputRow *self,
135                              GtkButton  *button)
136 {
137   GtkListBox *list_box = GTK_LIST_BOX (gtk_widget_get_parent (GTK_WIDGET (self)));
138   gint next_idx = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (self)) + 1;
139   GtkListBoxRow *next_row = gtk_list_box_get_row_at_index (list_box, next_idx);
140 
141   if (next_row == NULL)
142     return;
143 
144   g_signal_emit (next_row,
145                  signals[SIGNAL_MOVE_ROW],
146                  0,
147                  self);
148 }
149 
150 static void
settings_button_clicked_cb(CcInputRow * self)151 settings_button_clicked_cb (CcInputRow *self)
152 {
153   g_signal_emit (self,
154                  signals[SIGNAL_SHOW_SETTINGS],
155                  0);
156 }
157 
158 static void
layout_button_clicked_cb(CcInputRow * self)159 layout_button_clicked_cb (CcInputRow *self)
160 {
161   g_signal_emit (self,
162                  signals[SIGNAL_SHOW_LAYOUT],
163                  0);
164 }
165 
166 static void
remove_button_clicked_cb(CcInputRow * self)167 remove_button_clicked_cb (CcInputRow *self)
168 {
169   g_signal_emit (self,
170                  signals[SIGNAL_REMOVE_ROW],
171                  0);
172 }
173 
174 static void
cc_input_row_dispose(GObject * object)175 cc_input_row_dispose (GObject *object)
176 {
177   CcInputRow *self = CC_INPUT_ROW (object);
178 
179   g_clear_object (&self->source);
180 
181   G_OBJECT_CLASS (cc_input_row_parent_class)->dispose (object);
182 }
183 
184 void
cc_input_row_class_init(CcInputRowClass * klass)185 cc_input_row_class_init (CcInputRowClass *klass)
186 {
187   GObjectClass *object_class = G_OBJECT_CLASS (klass);
188   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
189 
190   object_class->dispose = cc_input_row_dispose;
191 
192   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-input-row.ui");
193 
194   gtk_widget_class_bind_template_child (widget_class, CcInputRow, drag_handle);
195   gtk_widget_class_bind_template_child (widget_class, CcInputRow, name_label);
196   gtk_widget_class_bind_template_child (widget_class, CcInputRow, remove_button);
197   gtk_widget_class_bind_template_child (widget_class, CcInputRow, settings_button);
198   gtk_widget_class_bind_template_child (widget_class, CcInputRow, settings_separator);
199 
200   gtk_widget_class_bind_template_callback (widget_class, drag_begin_cb);
201   gtk_widget_class_bind_template_callback (widget_class, drag_data_get_cb);
202   gtk_widget_class_bind_template_callback (widget_class, drag_data_received_cb);
203   gtk_widget_class_bind_template_callback (widget_class, drag_end_cb);
204   gtk_widget_class_bind_template_callback (widget_class, layout_button_clicked_cb);
205   gtk_widget_class_bind_template_callback (widget_class, move_down_button_clicked_cb);
206   gtk_widget_class_bind_template_callback (widget_class, move_up_button_clicked_cb);
207   gtk_widget_class_bind_template_callback (widget_class, remove_button_clicked_cb);
208   gtk_widget_class_bind_template_callback (widget_class, settings_button_clicked_cb);
209 
210   signals[SIGNAL_SHOW_SETTINGS] =
211     g_signal_new ("show-settings",
212                   G_TYPE_FROM_CLASS (object_class),
213                   G_SIGNAL_RUN_LAST,
214                   0,
215                   NULL, NULL,
216                   NULL,
217                   G_TYPE_NONE,
218                   0);
219 
220   signals[SIGNAL_SHOW_LAYOUT] =
221     g_signal_new ("show-layout",
222                   G_TYPE_FROM_CLASS (object_class),
223                   G_SIGNAL_RUN_LAST,
224                   0,
225                   NULL, NULL,
226                   NULL,
227                   G_TYPE_NONE,
228                   0);
229 
230   signals[SIGNAL_MOVE_ROW] =
231     g_signal_new ("move-row",
232                   G_TYPE_FROM_CLASS (object_class),
233                   G_SIGNAL_RUN_LAST,
234                   0,
235                   NULL, NULL,
236                   NULL,
237                   G_TYPE_NONE,
238                   1, CC_TYPE_INPUT_ROW);
239 
240   signals[SIGNAL_REMOVE_ROW] =
241     g_signal_new ("remove-row",
242                   G_TYPE_FROM_CLASS (object_class),
243                   G_SIGNAL_RUN_LAST,
244                   0,
245                   NULL, NULL,
246                   NULL,
247                   G_TYPE_NONE,
248                   0);
249 }
250 
251 static GtkTargetEntry entries[] =
252 {
253   { "GTK_LIST_BOX_ROW", GTK_TARGET_SAME_APP, 0 }
254 };
255 
256 void
cc_input_row_init(CcInputRow * self)257 cc_input_row_init (CcInputRow *self)
258 {
259   gtk_widget_init_template (GTK_WIDGET (self));
260 
261   gtk_drag_source_set (GTK_WIDGET (self->drag_handle), GDK_BUTTON1_MASK, entries, 1, GDK_ACTION_MOVE);
262   gtk_drag_dest_set (GTK_WIDGET (self), GTK_DEST_DEFAULT_ALL, entries, 1, GDK_ACTION_MOVE);
263 }
264 
265 static void
label_changed_cb(CcInputRow * self)266 label_changed_cb (CcInputRow *self)
267 {
268   g_autofree gchar *label = cc_input_source_get_label (self->source);
269   gtk_label_set_text (self->name_label, label);
270 }
271 
272 CcInputRow *
cc_input_row_new(CcInputSource * source)273 cc_input_row_new (CcInputSource *source)
274 {
275   CcInputRow *self;
276 
277   self = g_object_new (CC_TYPE_INPUT_ROW, NULL);
278   self->source = g_object_ref (source);
279 
280   g_signal_connect_object (source, "label-changed", G_CALLBACK (label_changed_cb), self, G_CONNECT_SWAPPED);
281   label_changed_cb (self);
282 
283   gtk_widget_set_visible (GTK_WIDGET (self->settings_button), CC_IS_INPUT_SOURCE_IBUS (source));
284   gtk_widget_set_visible (GTK_WIDGET (self->settings_separator), CC_IS_INPUT_SOURCE_IBUS (source));
285 
286   return self;
287 }
288 
289 CcInputSource *
cc_input_row_get_source(CcInputRow * self)290 cc_input_row_get_source (CcInputRow *self)
291 {
292   g_return_val_if_fail (CC_IS_INPUT_ROW (self), NULL);
293   return self->source;
294 }
295 
296 void
cc_input_row_set_removable(CcInputRow * self,gboolean removable)297 cc_input_row_set_removable (CcInputRow *self,
298                             gboolean    removable)
299 {
300   g_return_if_fail (CC_IS_INPUT_ROW (self));
301   gtk_widget_set_sensitive (GTK_WIDGET (self->remove_button), removable);
302 }
303 
304 void
cc_input_row_set_draggable(CcInputRow * self,gboolean draggable)305 cc_input_row_set_draggable (CcInputRow *self,
306                             gboolean    draggable)
307 {
308   if (draggable)
309     gtk_drag_source_set (GTK_WIDGET (self->drag_handle), GDK_BUTTON1_MASK, entries, 1, GDK_ACTION_MOVE);
310   else
311     gtk_drag_source_unset (GTK_WIDGET (self->drag_handle));
312 }
313