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