1 /*
2  * Copyright (C) 2006-2008 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
3  *               2006-2008 Jim Huang <jserv.tw@gmail.com>
4  *               2009 Marty Jack <martyj19@comcast.net>
5  *               2010 Julien Lavergne <julien.lavergne@gmail.com>
6  *               2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
7  *
8  * This file is a part of LXPanel project.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 
25 #include <stdlib.h>
26 
27 #include <glib/gi18n.h>
28 #include <libfm/fm-gtk.h>
29 
30 #include "space.h"
31 
32 #include "private.h"
33 
34 struct _PanelSpace
35 {
36     GtkEventBox parent;
37     config_setting_t *settings;
38     FmDndDest *dd;
39     int size;
40 };
41 
42 struct _PanelSpaceClass
43 {
44     GtkEventBoxClass parent_class;
45 };
46 
47 
48 /* Drag&drop support to create launchers */
49 enum {
50     LAUNCHER_DND_TARGET = N_FM_DND_DEST_DEFAULT_TARGETS
51 };
52 
53 static const GtkTargetEntry dnd_targets[] = {
54     { "application/x-lxpanel-launcher", GTK_TARGET_SAME_APP, LAUNCHER_DND_TARGET }
55 };
56 
57 static GdkAtom launcher_dnd_atom;
58 
panel_space_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)59 static gboolean panel_space_drag_drop(GtkWidget *widget, GdkDragContext *context,
60                                       gint x, gint y, guint time)
61 {
62     PanelSpace *sp = PANEL_SPACE(widget);
63     GdkAtom target;
64 
65     target = gtk_drag_dest_find_target(widget, context, NULL);
66     if (target == launcher_dnd_atom)
67     {
68         /* request for data, it will be processed on "drag-data-received" signal */
69         gtk_drag_get_data(widget, context, launcher_dnd_atom, time);
70         return TRUE;
71     }
72     target = fm_dnd_dest_find_target(sp->dd, context);
73     if (G_LIKELY(target != GDK_NONE))
74         return fm_dnd_dest_drag_drop(sp->dd, context, target, x, y, time);
75     return FALSE;
76 }
77 
panel_space_make_launcher(GtkWidget * widget,gint x,gint y,const char * str)78 static gboolean panel_space_make_launcher(GtkWidget *widget, gint x, gint y, const char *str)
79 {
80     PanelSpace *sp = PANEL_SPACE(widget);
81     GtkWidget *panel_box, *launchbar, *sp2;
82     LXPanel *panel;
83     config_setting_t *cfg;
84     LXPanelPluginInit *init;
85     char *cmd;
86     GtkAllocation alloc;
87     int idx = -1, size, size2 = 0;
88     int icon_size;
89     gboolean expand = FALSE;
90 
91     /* find position of this widget */
92     panel_box = gtk_widget_get_parent(widget);
93     gtk_container_child_get(GTK_CONTAINER(panel_box), widget, "position", &idx,
94                                                               "expand", &expand,
95                                                               NULL);
96     if (idx < 1)
97         // g_warning
98         return FALSE;
99     panel = PLUGIN_PANEL(widget);
100     /* prepare config for new widget and create launcher before PS */
101     cfg = config_group_add_subgroup(config_root_setting(panel->priv->config),
102                                     "Plugin");
103     config_group_set_string(cfg, "type", "launchbar");
104     config_setting_move_elem(cfg, config_setting_get_parent(cfg), idx + 1);
105     launchbar = lxpanel_add_plugin(panel, "launchbar", cfg, idx);
106     if (launchbar == NULL) /* failed to create */
107     {
108         config_setting_destroy(cfg);
109         return FALSE;
110     }
111     init = PLUGIN_CLASS(launchbar);
112     if (!init->control) /* cannot create a launcher */
113     {
114         lxpanel_remove_plugin(panel, launchbar);
115         return FALSE;
116     }
117     if (strncmp(str, "menu://applications/", 20) == 0)
118         cmd = g_strdup_printf("add %s", strrchr(str, '/') + 1);
119     else
120         cmd = g_strdup_printf("add %s", str);
121     if (!init->control(launchbar, cmd)) /* failed to create a launcher */
122     {
123         g_free(cmd);
124         lxpanel_remove_plugin(panel, launchbar);
125         return FALSE;
126     }
127     /* success */
128     g_free(cmd);
129     /* now to find where to insert the launcher */
130     icon_size = panel_get_icon_size(panel);
131     if (!expand && sp->size <= icon_size/2 + 4) //just drop this PS
132     {
133         lxpanel_remove_plugin(panel, widget);
134         return TRUE;
135     }
136     gtk_widget_get_allocation(widget, &alloc);
137     if (panel_get_orientation(panel) == GTK_ORIENTATION_HORIZONTAL)
138     {
139         size = alloc.width;
140     }
141     else
142     {
143         size = alloc.height;
144         x = y; /* use x below as a position value */
145     }
146     /* g_debug("making launcher at %d on PanelSpace of size %d", x, size); */
147     if (x <= icon_size/2 + 4) //leave launchbar at idx (before PS), size -= icon_size+3
148     {
149         lxpanel_config_save(panel);
150     }
151     else if (x >= size - icon_size/2 - 4) //move launchbar to idx+1 (after PS), size -= icon_size+3
152     {
153         gtk_box_reorder_child(GTK_BOX(panel_box), launchbar, idx + 1);
154         config_setting_move_elem(cfg, config_setting_get_parent(cfg), idx + 2);
155         lxpanel_config_save(panel);
156     }
157     else if (expand && x < size/2) //create another PS at idx of size pos-icon_size/2-2, shifting launchbar
158     {
159         cfg = config_group_add_subgroup(config_root_setting(panel->priv->config),
160                                         "Plugin");
161         config_group_set_string(cfg, "type", "space");
162         sp2 = lxpanel_add_plugin(panel, "space", cfg, idx);
163         size2 = x - icon_size/2 - 2;
164         /* g_debug("adding new PanelSpace of size %d before Launcher", size2); */
165         config_setting_move_elem(cfg, config_setting_get_parent(cfg), idx + 1);
166         if (sp2 == NULL)
167             //FIXME: is it ever possible?
168             config_setting_destroy(cfg);
169         else
170             _panel_space_resize(sp2, size2);
171     }
172     else //move launchbar to idx+1, then create another PS at idx+2 of size size-pos-icon_size/2-2
173     {
174         gtk_box_reorder_child(GTK_BOX(panel_box), launchbar, idx + 1);
175         config_setting_move_elem(cfg, config_setting_get_parent(cfg), idx + 2);
176         cfg = config_group_add_subgroup(config_root_setting(panel->priv->config),
177                                         "Plugin");
178         config_group_set_string(cfg, "type", "space");
179         sp2 = lxpanel_add_plugin(panel, "space", cfg, idx + 2);
180         size2 = size - x - icon_size/2 - 2;
181         /* g_debug("adding new PanelSpace of size %d after Launcher", size2); */
182         config_setting_move_elem(cfg, config_setting_get_parent(cfg), idx + 3);
183         if (sp2 == NULL)
184             //FIXME: is it ever possible?
185             config_setting_destroy(cfg);
186         else
187             _panel_space_resize(sp2, size2);
188     }
189     if (!expand) //resize to sp->size - icon_size - 3 - size2, then queue resize
190     {
191         /* g_debug("resizing this space to %d", sp->size - icon_size - 3 - size2); */
192         _panel_space_resize(widget, sp->size - icon_size - 3 - size2);
193     }
194     return TRUE;
195 }
196 
panel_space_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * sel_data,guint info,guint time)197 static void panel_space_drag_data_received(GtkWidget *widget,
198                                            GdkDragContext *context, gint x,
199                                            gint y, GtkSelectionData *sel_data,
200                                            guint info, guint time)
201 {
202     char *str;
203 
204     switch(info)
205     {
206     case LAUNCHER_DND_TARGET:
207         /* get data from selection */
208         str = (char *)gtk_selection_data_get_data(sel_data);
209         if (str == NULL)
210             break;
211         /* try to make a launcher */
212         if (panel_space_make_launcher(widget, x, y, str))
213             gtk_drag_finish(context, TRUE, TRUE, time);
214         else
215             gtk_drag_finish(context, FALSE, FALSE, time);
216         break;
217     default:
218         fm_dnd_dest_drag_data_received(PANEL_SPACE(widget)->dd, context, x, y,
219                                        sel_data, info, time);
220     }
221 }
222 
223 /* Handler for "drag-motion" event from launchtaskbar button. */
panel_space_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)224 static gboolean panel_space_drag_motion(GtkWidget *widget,
225                                         GdkDragContext *context, gint x, gint y,
226                                         guint time)
227 {
228     PanelSpace *sp = PANEL_SPACE(widget);
229     GdkAtom target;
230     GdkDragAction action = 0;
231 
232     target = fm_dnd_dest_find_target(sp->dd, context);
233     if (target == GDK_NONE)
234     {
235         target = gtk_drag_dest_find_target(widget, context, NULL);
236         if (target == launcher_dnd_atom)
237             action = GDK_ACTION_MOVE; /* see launchbar plugin */
238     }
239     else if (fm_dnd_dest_is_target_supported(sp->dd, target))
240     {
241         fm_dnd_dest_get_default_action(sp->dd, context, target);
242         action = GDK_ACTION_COPY;
243     }
244     gdk_drag_status(context, action, time);
245 
246     return TRUE;
247 }
248 
panel_space_drag_leave(GtkWidget * widget,GdkDragContext * drag_context,guint time)249 static void panel_space_drag_leave(GtkWidget *widget,
250                                    GdkDragContext *drag_context, guint time)
251 {
252     fm_dnd_dest_drag_leave(PANEL_SPACE(widget)->dd, drag_context, time);
253 }
254 
panel_space_files_dropped(FmDndDest * dd,int x,int y,GdkDragAction action,FmDndDestTargetType info_type,FmPathList * files,GtkWidget * sp)255 static gboolean panel_space_files_dropped(FmDndDest *dd, int x, int y, GdkDragAction action,
256                                           FmDndDestTargetType info_type,
257                                           FmPathList *files, GtkWidget *sp)
258 {
259     FmPath *path;
260     char *path_str;
261 
262     if (action != GDK_ACTION_COPY)
263         return FALSE;
264     path = fm_path_list_peek_head(files);
265     if (!path)
266         return FALSE;
267     path_str = fm_path_to_str(path);
268     /* g_debug("*** path '%s' pos %d", path_str, i); */
269     panel_space_make_launcher(sp, x, y, path_str);
270     g_free(path_str);
271     return TRUE;
272 }
273 
274 #if GTK_CHECK_VERSION(3, 0, 0)
panel_space_get_preferred_size(GtkWidget * widget,gint * minimal_width,gint * natural_width)275 static void panel_space_get_preferred_size(GtkWidget *widget,
276                                            gint *minimal_width,
277                                            gint *natural_width)
278 {
279     PanelSpace *p = PANEL_SPACE(widget);
280 
281     if (minimal_width)
282         *minimal_width = 2;
283     if (natural_width)
284         *natural_width = p->size;
285 }
286 #else
panel_space_size_request(GtkWidget * widget,GtkRequisition * requisition)287 static void panel_space_size_request(GtkWidget *widget,
288                                      GtkRequisition *requisition)
289 {
290     PanelSpace *p = PANEL_SPACE(widget);
291 
292     requisition->width = requisition->height = p->size;
293 }
294 #endif
295 
G_DEFINE_TYPE(PanelSpace,panel_space,GTK_TYPE_EVENT_BOX)296 G_DEFINE_TYPE(PanelSpace, panel_space, GTK_TYPE_EVENT_BOX)
297 
298 static void panel_space_dispose(GObject *object)
299 {
300     PanelSpace *self = (PanelSpace *)object;
301 
302     if (self->dd)
303     {
304         g_signal_handlers_disconnect_by_func(self->dd,
305                                              panel_space_files_dropped, self);
306         g_object_unref(self->dd);
307         self->dd = NULL;
308     }
309 
310     G_OBJECT_CLASS(panel_space_parent_class)->dispose(object);
311 }
312 
panel_space_class_init(PanelSpaceClass * klass)313 static void panel_space_class_init(PanelSpaceClass *klass)
314 {
315     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
316     GObjectClass *object_class = G_OBJECT_CLASS(klass);
317 
318 #if GTK_CHECK_VERSION(3, 0, 0)
319     widget_class->get_preferred_width = panel_space_get_preferred_size;
320     widget_class->get_preferred_height = panel_space_get_preferred_size;
321 #else
322     widget_class->size_request = panel_space_size_request;
323 #endif
324     widget_class->drag_drop = panel_space_drag_drop;
325     widget_class->drag_data_received = panel_space_drag_data_received;
326     widget_class->drag_motion = panel_space_drag_motion;
327     widget_class->drag_leave = panel_space_drag_leave;
328 
329     object_class->dispose = panel_space_dispose;
330 
331     launcher_dnd_atom = gdk_atom_intern_static_string("application/x-lxpanel-launcher");
332 }
333 
panel_space_init(PanelSpace * self)334 static void panel_space_init(PanelSpace *self)
335 {
336     gtk_widget_set_has_window(GTK_WIDGET(self), FALSE);
337     self->dd = fm_dnd_dest_new(GTK_WIDGET(self));
338     fm_dnd_dest_add_targets(GTK_WIDGET(self), dnd_targets, G_N_ELEMENTS(dnd_targets));
339     g_signal_connect(self->dd, "files-dropped",
340                      G_CALLBACK(panel_space_files_dropped), self);
341 }
342 
343 /* Plugin constructor. */
_panel_space_new(LXPanel * panel,config_setting_t * settings)344 GtkWidget *_panel_space_new(LXPanel *panel, config_setting_t *settings)
345 {
346     /* Allocate plugin context and set into Plugin private data pointer. */
347     PanelSpace * p = g_object_new(PANEL_TYPE_SPACE, NULL);
348 
349     /* Load parameters from the configuration file. */
350     config_setting_lookup_int(settings, "Size", &p->size);
351 
352     /* Save construction pointers */
353     p->settings = settings;
354 
355     /* Default the size parameter. */
356     if (p->size == 0)
357         p->size = 2;
358 
359     return GTK_WIDGET(p);
360 }
361 
362 /* Callback when the configuration dialog has recorded a configuration change. */
space_apply_configuration(gpointer user_data)363 static gboolean space_apply_configuration(gpointer user_data)
364 {
365     PanelSpace * p = user_data;
366 
367     /* Apply settings. */
368     gtk_widget_queue_resize(user_data);
369     /* Save config values */
370     config_group_set_int(p->settings, "Size", p->size);
371     return FALSE;
372 }
373 
_panel_space_resize(GtkWidget * spacer,gint size)374 void _panel_space_resize(GtkWidget *spacer, gint size)
375 {
376     PanelSpace * p = PANEL_SPACE(spacer);
377 
378     p->size = MAX(0, size);
379     space_apply_configuration(p);
380     lxpanel_config_save(PLUGIN_PANEL(spacer));
381 }
382 
_panel_space_get_size(GtkWidget * spacer)383 gint _panel_space_get_size(GtkWidget *spacer)
384 {
385     return PANEL_SPACE(spacer)->size;
386 }
387 
388 /* Callback when the configuration dialog is to be shown. */
space_configure(LXPanel * panel,GtkWidget * instance)389 static GtkWidget *space_configure(LXPanel *panel, GtkWidget *instance)
390 {
391     PanelSpace * p = PANEL_SPACE(instance);
392     GtkWidget * dlg;
393 
394     dlg = lxpanel_generic_config_dlg(_("Spacer"), panel,
395                                      space_apply_configuration, instance,
396                                      _("Size"), &p->size, CONF_TYPE_INT, NULL);
397     gtk_widget_set_size_request(dlg, 200, -1);	/* Improve geometry */
398     return dlg;
399 }
400 
401 /* Plugin descriptor. */
402 LXPanelPluginInit _lxpanel_static_plugin_space = {
403     .name = N_("Spacer"),
404     .description = N_("Allocate space"),
405 
406     /* Stretch is available but not default for this plugin. */
407     .expand_available = TRUE,
408 
409     .new_instance = _panel_space_new,
410     .config = space_configure,
411 };
412