1 /*
2  * Copyright (C) 2016 Andriy Grytsenko <andrej@rep.kiev.ua>
3  *
4  * This file is a part of LXPanel project.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include "private.h"
26 #include "space.h"
27 
_lxpanel_button_release(GtkWidget * widget,GdkEventButton * event)28 gboolean _lxpanel_button_release(GtkWidget *widget, GdkEventButton *event)
29 {
30     Panel *p = PLUGIN_PANEL(widget)->priv;
31 
32     if (event->device == p->move_device && event->button == 2 &&
33         p->move_state != PANEL_MOVE_STOP)
34     {
35         if (p->move_state == PANEL_MOVE_MOVING)
36         {
37             /* ungrab device and return back previous cursor */
38 #if GTK_CHECK_VERSION(3, 0, 0)
39             gdk_device_ungrab(event->device, event->time);
40 #else
41             gdk_pointer_ungrab(event->time);
42 #endif
43         }
44         p->move_state = PANEL_MOVE_STOP;
45         p->move_device = NULL;
46         return TRUE;
47     }
48     return FALSE;
49 }
50 
_lxpanel_motion_notify(GtkWidget * widget,GdkEventMotion * event)51 gboolean _lxpanel_motion_notify(GtkWidget *widget, GdkEventMotion *event)
52 {
53     Panel *p = PLUGIN_PANEL(widget)->priv;
54     GList *plugins, *l;
55     GtkAllocation alloc;
56     GdkGrabStatus s;
57     int x, y, old_pos, new_pos;
58     gboolean expand, rtl;
59     config_setting_t *cfg;
60     PanelPluginMoveData *in, *out;
61 
62     if (event->device != p->move_device)
63         return FALSE;
64 
65     if (p->move_state == PANEL_MOVE_DETECT)
66     {
67         gdk_window_get_origin(event->window, &x, &y);
68         x += event->x - p->ax;
69         y += event->y - p->ay;
70         /* check threshold, start moving */
71         if (gtk_drag_check_threshold(widget, p->move_x, p->move_y, x, y))
72         {
73             plugins = gtk_container_get_children(GTK_CONTAINER(p->box));
74             for (l = plugins; l; l = l->next)
75             {
76                 gtk_widget_get_allocation(l->data, &alloc);
77                 if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
78                 {
79                     if (p->move_x >= alloc.x && p->move_x < alloc.x + alloc.width)
80                         break;
81                 }
82                 else
83                 {
84                     if (p->move_y >= alloc.y && p->move_y < alloc.y + alloc.height)
85                         break;
86                 }
87             }
88             if (l == NULL || PANEL_IS_SPACE(l->data))
89             {
90                 p->move_state = PANEL_MOVE_STOP;
91                 p->move_device = NULL;
92                 g_list_free(plugins);
93                 return TRUE;
94             }
95             /* grab pointer, use cursor "move" */
96 #if GTK_CHECK_VERSION(3, 0, 0)
97             s = gdk_device_grab(event->device, gtk_widget_get_window(widget),
98                                 GDK_OWNERSHIP_NONE, FALSE,
99                                 GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
100                                 gdk_cursor_new_from_name(p->display, "move"),
101                                 event->time);
102 #else
103             s = gdk_pointer_grab(gtk_widget_get_window(widget), FALSE,
104                                  GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
105                                  NULL, gdk_cursor_new_from_name(p->display, "move"),
106                                  event->time);
107 #endif
108             if (s == GDK_GRAB_SUCCESS)
109             {
110                 p->move_state = PANEL_MOVE_MOVING;
111                 /* load all drag data into panel data */
112                 p->move_plugin = l->data;
113                 p->move_before.space = NULL;
114                 p->move_before.plugin = NULL;
115                 p->move_after.space = NULL;
116                 p->move_after.plugin = NULL;
117                 if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
118                     p->move_diff = alloc.x + alloc.width / 2 - p->move_x;
119                 else
120                     p->move_diff = alloc.y + alloc.height / 2 - p->move_y;
121                 /* g_debug("move_diff is %d",p->move_diff); */
122                 if (l->prev)
123                 {
124                     GList *save = l;
125 
126                     l = l->prev;
127                     if (PANEL_IS_SPACE(l->data))
128                     {
129                         p->move_before.space = l->data;
130                         gtk_container_child_get(GTK_CONTAINER(p->box), l->data,
131                                                 "expand", &expand, NULL);
132                         if (expand)
133                         {
134                             gtk_widget_get_allocation(l->data, &alloc);
135                             if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
136                                 p->move_before.space_size = alloc.width;
137                             else
138                                 p->move_before.space_size = alloc.height;
139                         }
140                         else
141                             p->move_before.space_size = -1;
142                         l = l->prev;
143                     }
144                     if (l)
145                     {
146                         p->move_before.plugin = l->data;
147                         gtk_widget_get_allocation(l->data, &alloc);
148                         if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
149                             p->move_before.plugin_center = alloc.x + alloc.width / 2;
150                         else
151                             p->move_before.plugin_center = alloc.y + alloc.height / 2;
152                     }
153                     l = save;
154                 }
155                 if (l->next)
156                 {
157                     l = l->next;
158                     if (PANEL_IS_SPACE(l->data))
159                     {
160                         p->move_after.space = l->data;
161                         gtk_container_child_get(GTK_CONTAINER(p->box), l->data,
162                                                 "expand", &expand, NULL);
163                         if (expand)
164                         {
165                             gtk_widget_get_allocation(l->data, &alloc);
166                             if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
167                                 p->move_after.space_size = alloc.width;
168                             else
169                                 p->move_after.space_size = alloc.height;
170                         }
171                         else
172                             p->move_after.space_size = -1;
173                         l = l->next;
174                     }
175                     if (l)
176                     {
177                         p->move_after.plugin = l->data;
178                         gtk_widget_get_allocation(l->data, &alloc);
179                         if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
180                             p->move_after.plugin_center = alloc.x + alloc.width / 2;
181                         else
182                             p->move_after.plugin_center = alloc.y + alloc.height / 2;
183                     }
184                 }
185                 g_list_free(plugins);
186                 return TRUE;
187             }
188             g_list_free(plugins);
189         }
190     }
191     else if (p->move_state == PANEL_MOVE_MOVING)
192     {
193         /* calculate current and new positions of moving widget center */
194         gdk_window_get_origin(event->window, &x, &y);
195         gtk_widget_get_allocation(p->move_plugin, &alloc);
196         if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
197         {
198             old_pos = alloc.x + alloc.width / 2;
199             new_pos = x + event->x - p->ax + p->move_diff;
200             rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
201         }
202         else
203         {
204             old_pos = alloc.y + alloc.height / 2;
205             new_pos = y + event->y - p->ay + p->move_diff;
206             rtl = FALSE;
207         }
208         /* actually move widget, changing spaces or swapping with others */
209         if (new_pos > old_pos)
210         {
211             x = new_pos - old_pos;
212             /* cursor is right/down */
213             if (rtl)
214                 goto _split_or_swap_before;
215             else
216                 goto _split_or_swap_after;
217         }
218         else if (new_pos < old_pos)
219         {
220             x = old_pos - new_pos;
221             /* cursor is left/up */
222             if (rtl)
223             {
224                 rtl = FALSE; /* it now means before => after */
225 _split_or_swap_after:
226                 in = &p->move_before;
227                 out = &p->move_after;
228             }
229             else
230             {
231                 rtl = TRUE; /* it now means after => before */
232 _split_or_swap_before:
233                 in = &p->move_after;
234                 out = &p->move_before;
235             }
236             /* g_debug("moving plugin by %d", new_pos - old_pos); */
237             if (out->space)
238             {
239                 /* split space and move plugin */
240                 if (in->space)
241                 {
242                     /* between spaces */
243                     if (out->space_size < 0)
244                     {
245                         /* fixed size, decrease out */
246                         y = _panel_space_get_size(out->space) - x;
247                         /* g_debug("next is fixed space of %d -> %d", x+y, y); */
248                         if (y >= 2)
249                         {
250                             _panel_space_resize(out->space, y);
251                             if (in->space_size > 0 &&
252                                 y > in->space_size - x)
253                             {
254                                 /* fixed became bigger than expanded, let swap */
255                                 /* g_debug("swap 'expand' on spaces"); */
256                                 gtk_container_child_set(GTK_CONTAINER(p->box),
257                                                         out->space,
258                                                         "expand", TRUE, NULL);
259                                 cfg = g_object_get_qdata(G_OBJECT(out->space),
260                                                          lxpanel_plugin_qconf);
261                                 config_group_set_int(cfg, "expand", 1);
262                                 out->space_size = y;
263                                 gtk_container_child_set(GTK_CONTAINER(p->box),
264                                                         in->space,
265                                                         "expand", FALSE, NULL);
266                                 cfg = g_object_get_qdata(G_OBJECT(in->space),
267                                                          lxpanel_plugin_qconf);
268                                 config_group_set_int(cfg, "expand", 0);
269                                 _panel_space_resize(in->space, in->space_size + x);
270                                 in->space_size = -1;
271                             }
272                             /* in->space_size < 0 will be handled below */
273                         }
274                         else
275                         {
276                             /* remove empty space plugin */
277                             /* g_debug("removing next space %p", out->space); */
278                             lxpanel_remove_plugin(p->topgwin, out->space);
279                             out->space = NULL;
280                             y += x + gtk_box_get_spacing(GTK_BOX(p->box));
281                             if (in->space_size >= 0)
282                                 /* correct size of expanded */
283                                 in->space_size += y;
284                             else
285                             {
286                                 /* both were fixed size - correct size of which left */
287                                 y += _panel_space_get_size(in->space);
288                                 _panel_space_resize(in->space, y);
289                                 /* g_debug("change prev size to %d", y); */
290                             }
291                             /* both spaces were handled so may return */
292                             return TRUE;
293                         }
294                     }
295                     else
296                     {
297                         if (out->space_size < x)
298                             /* expandable size exhausted */
299                             x = out->space_size;
300                             //FIXME: if in->space_size >= 0 then remove out->space
301                         out->space_size -= x;
302                     }
303                     if (in->space_size < 0)
304                     {
305                         /* fixed size, increase before */
306                         y = _panel_space_get_size(in->space) + x;
307                         _panel_space_resize(in->space, y);
308                         /* g_debug("prev is fixed space of %d -> %d", y-x, y); */
309                         if (out->space_size >= 0 &&
310                             y > out->space_size)
311                         {
312                             /* g_debug("swap 'expand' on spaces"); */
313                             /* fixed became bigger than expanded, let swap */
314                             gtk_container_child_set(GTK_CONTAINER(p->box),
315                                                     in->space,
316                                                     "expand", TRUE, NULL);
317                             cfg = g_object_get_qdata(G_OBJECT(in->space),
318                                                      lxpanel_plugin_qconf);
319                             config_group_set_int(cfg, "expand", 1);
320                             in->space_size = y;
321                             if (out->space_size >= 2)
322                             {
323                                 gtk_container_child_set(GTK_CONTAINER(p->box),
324                                                         out->space,
325                                                         "expand", FALSE, NULL);
326                                 cfg = g_object_get_qdata(G_OBJECT(out->space),
327                                                          lxpanel_plugin_qconf);
328                                 config_group_set_int(cfg, "expand", 0);
329                                 _panel_space_resize(out->space, out->space_size);
330                                 out->space_size = -1;
331                             }
332                             else
333                             {
334                                 /* g_debug("removing next space"); */
335                                 /* remove empty space plugin */
336                                 lxpanel_remove_plugin(p->topgwin, out->space);
337                                 out->space = NULL;
338                                 in->space_size += out->space_size;
339                                 in->space_size += gtk_box_get_spacing(GTK_BOX(p->box));
340                             }
341                         }
342                     }
343                     else
344                         in->space_size += x;
345                     /* end of between spaces case */
346                 }
347                 else if (x >= 2)
348                 {
349                     /* there is only space after */
350                     /* ensure out->space size is at least x+2 */
351                     if (out->space_size < 0)
352                     {
353                         y = _panel_space_get_size(out->space);
354                         if (y - x < 2)
355                             goto _swap_next_space;
356                         /* decrement in->space size by x */
357                         _panel_space_resize(out->space, y - x);
358                         goto _add_prev_space;
359                     }
360                     else if (out->space_size - x < 2)
361                     {
362 _swap_next_space:
363                         /* too big change, just swap */
364                         gtk_container_child_get(GTK_CONTAINER(p->box),
365                                                 p->move_plugin,
366                                                 "position", &y, NULL);
367                         /* reposition space from next to prev */
368                         in->space = out->space;
369                         in->space_size = out->space_size;
370                         out->space = NULL;
371                         gtk_container_child_set(GTK_CONTAINER(p->box),
372                                                 in->space, "position",
373                                                 y, NULL);
374                         cfg = g_object_get_qdata(G_OBJECT(in->space),
375                                                  lxpanel_plugin_qconf);
376                         config_setting_move_elem(cfg, config_setting_get_parent(cfg),
377                                                  y + 1);
378                         /* g_debug("swapped with next space %p", in->space); */
379                     }
380                     else
381                     {
382                         out->space_size -= x;
383 _add_prev_space:
384                         /* create PanelSpace of size x before plugin on move */
385                         gtk_container_child_get(GTK_CONTAINER(p->box),
386                                                 p->move_plugin,
387                                                 "position", &y, NULL);
388                         cfg = config_group_add_subgroup(config_root_setting(p->config),
389                                                         "Plugin");
390                         config_group_set_string(cfg, "type", "space");
391                         in->space_size = -1;
392                         if (rtl)
393                             /* create space after moving one */
394                             y++;
395                             /* else create space before moving one, i.e. in place */
396                         in->space = lxpanel_add_plugin(p->topgwin, "space", cfg, y);
397                         config_setting_move_elem(cfg, config_setting_get_parent(cfg),
398                                                  y + 1);
399                         if (in->space == NULL)
400                             //FIXME: is it ever possible?
401                             config_setting_destroy(cfg);
402                         else
403                             _panel_space_resize(in->space, x);
404                         /* g_debug("added space %d before plugin", x); */
405                     }
406                 }
407             }
408             else if (out->plugin)
409             {
410                 /* no space after, check if need swap */
411                 if (new_pos < old_pos)
412                 {
413                     /* going down */
414                     if (new_pos < out->plugin_center)
415                     {
416                         /* need swap */
417                         gtk_widget_get_allocation(p->move_plugin, &alloc);
418                         if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
419                             x = alloc.width;
420                         else
421                             x = alloc.height;
422                         x += gtk_box_get_spacing(GTK_BOX(p->box));
423                         goto _swap_next;
424                     }
425                 }
426                 else if (new_pos > out->plugin_center)
427                 {
428                     /* going up, need swap */
429                     gtk_widget_get_allocation(p->move_plugin, &alloc);
430                     if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
431                         x = -alloc.width;
432                     else
433                         x = -alloc.height;
434                     x -= gtk_box_get_spacing(GTK_BOX(p->box));
435 _swap_next:
436                     /* swap with next plugin and reload all data */
437                     plugins = gtk_container_get_children(GTK_CONTAINER(p->box));
438                     l = g_list_find(plugins, p->move_plugin);
439                     y = g_list_position(plugins, l);
440                     if (rtl)
441                     {
442                         /* going down in list */
443                         g_assert(l && l->prev);
444                         l = l->prev->prev;
445                     }
446                     else
447                     {
448                         /* going up in list */
449                         g_assert(l && l->next);
450                         l = l->next->next;
451                     }
452                     /* g_debug("swapping with next plugin %p", out->plugin); */
453                     in->space = NULL;
454                     in->plugin = out->plugin;
455                     in->plugin_center = out->plugin_center + x;
456                     /* swap next plugin with one being moved */
457                     gtk_container_child_set(GTK_CONTAINER(p->box),
458                                             in->plugin, "position", y, NULL);
459                     cfg = g_object_get_qdata(G_OBJECT(in->plugin),
460                                              lxpanel_plugin_qconf);
461                     config_setting_move_elem(cfg, config_setting_get_parent(cfg),
462                                              y + 1);
463                     /* check and set out->plugin and out->space */
464                     if (l && PANEL_IS_SPACE(l->data))
465                     {
466                         out->space = l->data;
467                         gtk_container_child_get(GTK_CONTAINER(p->box), l->data,
468                                                 "expand", &expand, NULL);
469                         if (expand)
470                         {
471                             gtk_widget_get_allocation(l->data, &alloc);
472                             if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
473                                 out->space_size = alloc.width;
474                             else
475                                 out->space_size = alloc.height;
476                         }
477                         else
478                             out->space_size = -1;
479                         if (rtl)
480                             l = l->prev;
481                         else
482                             l = l->next;
483                     }
484                     else
485                         out->space = NULL;
486                     if (l)
487                     {
488                         out->plugin = l->data;
489                         gtk_widget_get_allocation(l->data, &alloc);
490                         if (p->orientation == GTK_ORIENTATION_HORIZONTAL)
491                             out->plugin_center = alloc.x + alloc.width / 2;
492                         else
493                             out->plugin_center = alloc.y + alloc.height / 2;
494                     }
495                     else
496                         out->plugin = NULL;
497                     g_list_free(plugins);
498                 }
499             }
500         }
501         return TRUE;
502     }
503     return FALSE;
504 }
505