~ubuntu-branches/ubuntu/utopic/cairo-dock/utopic

1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
1
/**
2
* This file is a part of the Cairo-Dock project
3
*
4
* Copyright : (C) see the 'copyright' file.
5
* E-mail    : see the 'copyright' file.
6
*
7
* This program is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU General Public License
9
* as published by the Free Software Foundation; either version 3
10
* of the License, or (at your option) any later version.
11
*
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
*/
19
20
#include <stdlib.h>
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
21
#include <math.h>  // fabs
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
22
23
#include <cairo.h>
24
#include <gtk/gtk.h>
25
#if GTK_CHECK_VERSION (3, 10, 0)
26
#include "gtk3imagemenuitem.h"
27
#endif
28
29
#include "cairo-dock-container.h"
30
#include "cairo-dock-icon-factory.h"
31
#include "cairo-dock-icon-facility.h"  // cairo_dock_get_icon_container
32
#include "cairo-dock-desktop-manager.h"  // gldi_desktop_get_height
33
#include "cairo-dock-log.h"
34
#include "cairo-dock-draw.h"
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
35
#include "cairo-dock-backends-manager.h"  // cairo_dock_get_dialog_decorator
36
#include "cairo-dock-dialog-manager.h"  // myDialogsParam
37
#include "cairo-dock-style-manager.h"
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
38
#include "cairo-dock-menu.h"
39
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
40
#if GTK_MAJOR_VERSION > 2
41
static gboolean _draw_menu_item (GtkWidget *widget, cairo_t *cr, G_GNUC_UNUSED gpointer data);
42
static gboolean _on_select_menu_item (GtkWidget* pMenuItem, G_GNUC_UNUSED gpointer data);
43
static gboolean _on_deselect_menu_item (GtkWidget* pMenuItem, G_GNUC_UNUSED gpointer data);
44
static void _on_destroy_menu_item (GtkWidget* pMenuItem, G_GNUC_UNUSED gpointer data);
45
#endif
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
46
47
  ////////////
48
 /// MENU ///
49
/////////////
50
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
51
#if GTK_MAJOR_VERSION > 2
52
53
static void _init_menu_style (void)
54
{
55
	static GtkCssProvider *cssProvider = NULL;
56
	static int index = 0;
57
	
58
	if (index == gldi_style_colors_get_index())
59
		return;
60
	index = gldi_style_colors_get_index();
61
	g_print ("%s (%d)\n", __func__, index);
62
	
63
	if (myDialogsParam.bUseDefaultColors && myStyleParam.bUseSystemColors)
64
	{
65
		if (cssProvider != NULL)
66
		{
67
			gldi_style_colors_freeze ();
68
			gtk_style_context_remove_provider_for_screen (gdk_screen_get_default(), GTK_STYLE_PROVIDER(cssProvider));
69
			gldi_style_colors_freeze ();
70
			g_object_unref (cssProvider);
71
			cssProvider = NULL;
72
		}
73
	}
74
	else
75
	{
76
		if (cssProvider == NULL)
77
		{
78
			cssProvider = gtk_css_provider_new ();
79
			gldi_style_colors_freeze ();
80
			gtk_style_context_add_provider_for_screen (gdk_screen_get_default(), GTK_STYLE_PROVIDER(cssProvider), GTK_STYLE_PROVIDER_PRIORITY_USER);
81
			gldi_style_colors_freeze ();
82
		}
83
		
84
		double *bg_color = (myDialogsParam.bUseDefaultColors ? myStyleParam.fBgColor : myDialogsParam.fBgColor);
85
		double *text_color = (myDialogsParam.bUseDefaultColors ? myStyleParam.textDescription.fColorStart : myDialogsParam.dialogTextDescription.fColorStart);
86
		
87
		double rgb[4];
88
		gldi_style_color_shade (bg_color, .2, rgb);
89
		double rgbs[4];
90
		gldi_style_color_shade (bg_color, .2, rgbs);
91
		double rgbb[4];
92
		gldi_style_color_shade (bg_color, .3, rgbb);
93
		
94
		gchar *css = g_strdup_printf ("@define-color menuitem_bg_color rgb (%d, %d, %d); \
95
		@define-color menuitem_text_color rgb (%d, %d, %d); \
96
		@define-color menuitem_insensitive_text_color rgba (%d, %d, %d, .5); \
97
		@define-color menuitem_separator_color rgb (%d, %d, %d); \
98
		@define-color menuitem_bg_color2 rgb (%d, %d, %d); \
99
		@define-color menu_bg_color rgba (%d, %d, %d, %d); \
100
		.menuitem2 { \
101
			text-shadow: none; \
102
			border-image: none; \
103
			box-shadow: none; \
104
			background: transparent; \
105
			color: @menuitem_text_color; \
106
		} \
107
		.menuitem2 GtkImage { \
108
			background: transparent; \
109
		} \
110
		.menuitem2.separator, \
111
		.menuitem2 .separator { \
112
			color: @menuitem_separator_color; \
113
			border-width: 1px; \
114
			border-style: solid; \
115
			border-image: none; \
116
			border-color: @menuitem_separator_color; \
117
			border-bottom-color: alpha (@menuitem_separator_color, 0.6); \
118
			border-right-color: alpha (@menuitem_separator_color, 0.6); \
119
		} \
120
		.menuitem2:hover, \
121
		.menuitem2 *:hover { \
122
			background: @menuitem_bg_color; \
123
			background-image: none; \
124
			text-shadow: none; \
125
			border-image: none; \
126
			box-shadow: none; \
127
			color: @menuitem_text_color; \
128
		} \
129
		.menuitem2 *:insensitive { \
130
			text-shadow: none; \
131
			color: @menuitem_insensitive_text_color; \
132
		} \
133
		.menuitem2 .entry, \
134
		.menuitem2.entry { \
135
			background: @menuitem_bg_color; \
136
			border-image: none; \
137
			border-color: transparent; \
138
			color: @menuitem_text_color; \
139
		} \
140
		.menuitem2 .button, \
141
		.menuitem2.button { \
142
			background: @menuitem_bg_color; \
143
			background-image: none; \
144
			box-shadow: none; \
145
			border-color: transparent; \
146
			padding: 2px; \
147
		} \
148
		.menuitem2 .scale, \
149
		.menuitem2.scale { \
150
			background: @menuitem_bg_color2; \
151
			background-image: none; \
152
			color: @menuitem_text_color; \
153
			border-image: none; \
154
		} \
155
		.menuitem2 .scale.left, \
156
		.menuitem2.scale.left { \
157
			background: @menuitem_bg_color; \
158
			background-image: none; \
159
			border-image: none; \
160
		} \
161
		.menuitem2 .scale.slider, \
162
		.menuitem2.scale.slider { \
163
			background: @menuitem_text_color; \
164
			background-image: none; \
165
			border-image: none; \
166
		} \
167
		.menuitem2 GtkCalendar, \
168
		.menuitem2 GtkCalendar.button, \
169
		.menuitem2 GtkCalendar.header, \
170
		.menuitem2 GtkCalendar.view { \
171
			background-color: @menuitem_bg_color; \
172
			background-image: none; \
173
			color: @menuitem_text_color; \
174
		} \
175
		.menuitem2 GtkCalendar { \
176
			background-color: @menuitem_bg_color2; \
177
			background-image: none; \
178
		} \
179
		.menuitem2 GtkCalendar:inconsistent { \
180
			color: shade (@menuitem_bg_color2, 0.6); \
181
		} \
182
		.menu2 { \
183
			background: @menu_bg_color; \
184
			background-image: none; \
185
			color: @menuitem_text_color; \
186
		}",
187
		(int)(rgb[0]*255), (int)(rgb[1]*255), (int)(rgb[2]*255),
188
		(int)(text_color[0]*255), (int)(text_color[1]*255), (int)(text_color[2]*255),
189
		(int)(text_color[0]*255), (int)(text_color[1]*255), (int)(text_color[2]*255),
190
		(int)(rgbs[0]*255), (int)(rgbs[1]*255), (int)(rgbs[2]*255),
191
		(int)(rgbb[0]*255), (int)(rgbb[1]*255), (int)(rgbb[2]*255),
192
		(int)(bg_color[0]*255), (int)(bg_color[1]*255), (int)(bg_color[2]*255), (int)(bg_color[3]*255));  // we also define ".menu", so that custom widgets (like in the SoundMenu) can get our colors.
193
		
194
		gldi_style_colors_freeze ();
195
		gtk_css_provider_load_from_data (cssProvider,
196
			css, -1, NULL);  // (should) clear any previously loaded information
197
		gldi_style_colors_freeze ();
198
		g_free (css);
199
	}
200
}
201
202
static gboolean _draw_menu (GtkWidget *pWidget,
203
	cairo_t *pCairoContext,
204
	G_GNUC_UNUSED GtkWidget *menu)
205
{
206
	// erase the default background
207
	cairo_dock_erase_cairo_context (pCairoContext);
208
	
209
	// draw the background/outline and set the clip
210
	CairoDialogDecorator *pDecorator = cairo_dock_get_dialog_decorator (myDialogsParam.cDecoratorName);
211
	if (pDecorator)
212
		pDecorator->render_menu (pWidget, pCairoContext);
213
	
214
	// draw the items
215
	cairo_set_source_rgba (pCairoContext, 0.0, 0.0, 0.0, 1.0);
216
	
217
	GtkWidgetClass *parent_class = g_type_class_peek (g_type_parent (G_TYPE_FROM_INSTANCE (pWidget)));
218
	parent_class = g_type_class_peek_parent (parent_class);  // skip the direct parent (GtkBin, which does anyway nothing usually), because dbusmenu-gtk draws it
219
	parent_class->draw (pWidget, pCairoContext);
220
	
221
	return TRUE;
222
}
223
224
static void _set_margin_position (GtkWidget *pMenu, GldiMenuParams *pParams)
225
{
226
	if (pParams == NULL)
227
		pParams = g_object_get_data (G_OBJECT (pMenu), "gldi-params");
228
	g_return_if_fail (pParams);
229
	Icon *pIcon = pParams->pIcon;
230
	g_return_if_fail (pIcon);
231
	GldiContainer *pContainer = cairo_dock_get_icon_container (pIcon);
232
	g_return_if_fail (pContainer);
233
	
234
	// define where the menu will point
235
	int iMarginPosition;  // b, t, r, l
236
	int y0 = pContainer->iWindowPositionY + pIcon->fDrawY;
237
	if (pContainer->bDirectionUp)
238
		y0 += pIcon->fHeight * pIcon->fScale - pIcon->image.iHeight;  // the icon might not be maximised yet
239
	int Hs = (pContainer->bIsHorizontal ? gldi_desktop_get_height() : gldi_desktop_get_width());
240
	if (pContainer->bIsHorizontal)
241
	{
242
		iMarginPosition = (y0 > Hs/2 ? 0 : 1);
243
	}
244
	else
245
	{
246
		iMarginPosition = (y0 > Hs/2 ? 2 : 3);
247
	}
248
	
249
	// store the result, and allocate some space to draw the arrow
250
	if (iMarginPosition != pParams->iMarginPosition)  // margin position is now defined or has changed -> update it on the menu
251
	{
252
		g_print ("margin position: %d -> %d\n", pParams->iMarginPosition, iMarginPosition);
253
		// store the value
254
		pParams->iMarginPosition = iMarginPosition;
255
		
256
		// get/add a css (gtk_widget_set_margin_xxx doesn't work as expected :-/)
257
		GtkCssProvider *cssProvider = pParams->cssProvider;
258
		if (cssProvider)  // unfortunately, GTK doesn't update correctly a css provider if we load some new data inside (the previous padding values are kept, along with the new ones, although 'gtk_css_provider_load_from_data' is supposed to "clear any previously loaded information"), so we have to remove/add it :-/
259
		{
260
			gtk_style_context_remove_provider (gtk_widget_get_style_context(pMenu), GTK_STYLE_PROVIDER(cssProvider));
261
			g_object_unref (cssProvider);
262
			cssProvider = NULL;
263
			pParams->cssProvider = NULL;
264
		}
265
		if (cssProvider == NULL)
266
		{
267
			cssProvider = gtk_css_provider_new ();
268
			gtk_style_context_add_provider (gtk_widget_get_style_context(pMenu), GTK_STYLE_PROVIDER(cssProvider), GTK_STYLE_PROVIDER_PRIORITY_USER);  // this adds a reference on the provider, plus the one we own
269
			pParams->cssProvider = cssProvider;
270
			g_print (" + css\n");
271
		}
272
		
273
		// load the new padding rule into the css
274
		gchar *css = NULL;
275
		int ah = pParams->iArrowHeight;
276
		int b=0, t=0, r=0, l=0;
277
		switch (iMarginPosition)
278
		{
279
			case 0: b = ah; break;
280
			case 1: t = ah; break;
281
			case 2: r = ah; break;
282
			case 3: l = ah; break;
283
			default: break;
284
		}
285
		css = g_strdup_printf ("GtkMenu { \
286
			padding-bottom: %dpx; \
287
			padding-top: %dpx; \
288
			padding-right: %dpx; \
289
			padding-left: %dpx; \
290
		}", b, t, r, l);  // we must define all the paddings, else if the margin position changes, clearing the css won't make the padding disappear :-/
291
		gtk_css_provider_load_from_data (cssProvider,
292
			css, -1, NULL);
293
		g_free (css);
294
	}
295
}
296
#endif
297
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
298
GtkWidget *gldi_menu_new (G_GNUC_UNUSED Icon *pIcon)
299
{
300
	GtkWidget *pMenu = gtk_menu_new ();
301
	
302
	gldi_menu_init (pMenu, pIcon);
303
	
304
	return pMenu;
305
}
306
307
static gboolean _on_icon_destroyed (GtkWidget *pMenu, G_GNUC_UNUSED Icon *pIcon)
308
{
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
309
	GldiMenuParams *pParams = g_object_get_data (G_OBJECT (pMenu), "gldi-params");
310
	if (pParams)
311
		pParams->pIcon = NULL;
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
312
	return GLDI_NOTIFICATION_LET_PASS;
313
}
314
315
static void _on_menu_destroyed (GtkWidget *pMenu, G_GNUC_UNUSED gpointer data)
316
{
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
317
	GldiMenuParams *pParams = g_object_get_data (G_OBJECT (pMenu), "gldi-params");
318
	if (!pParams)
319
		return;
320
	Icon *pIcon = pParams->pIcon;
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
321
	if (pIcon)
322
		gldi_object_remove_notification (pIcon,
323
			NOTIFICATION_DESTROY,
324
			(GldiNotificationFunc) _on_icon_destroyed,
325
			NULL);
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
326
	#if GTK_MAJOR_VERSION > 2
327
	if (pParams->cssProvider)
328
	{
329
		g_object_unref (pParams->cssProvider);  /// need to remove the provider from the style context ?... probably not since the style context will be destroyed and will release its reference on the provider
330
	}
331
	g_free (pParams);
332
	#endif
333
}
334
335
#if GTK_MAJOR_VERSION > 2
336
static void _on_menu_deactivated (GtkMenuShell *pMenu, G_GNUC_UNUSED gpointer data)
337
{
338
	GldiMenuParams *pParams = g_object_get_data (G_OBJECT (pMenu), "gldi-params");
339
	if (!pParams)
340
		return;
341
	Icon *pIcon = pParams->pIcon;
342
	if (pIcon->iHideLabel > 0)
343
	{
344
		pIcon->iHideLabel --;
345
		GldiContainer *pContainer = cairo_dock_get_icon_container (pIcon);
346
		if (pIcon->iHideLabel == 0 && pContainer)
347
			gtk_widget_queue_draw (pContainer->pWidget);
348
	}
349
}
350
#endif
351
352
void gldi_menu_init (G_GNUC_UNUSED GtkWidget *pMenu, Icon *pIcon)
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
353
{
354
	#if (CAIRO_DOCK_FORCE_ICON_IN_MENUS == 1)
355
	gtk_menu_set_reserve_toggle_size (GTK_MENU(pMenu), TRUE);
356
	#endif
357
	
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
358
	#if GTK_MAJOR_VERSION > 2
359
	// connect to 'draw' event to draw the menu (background and items)
360
	GtkWidget *pWindow = gtk_widget_get_toplevel (pMenu);
361
	cairo_dock_set_default_rgba_visual (pWindow);
362
	
363
	g_signal_connect (G_OBJECT (pMenu),
364
		"draw",
365
		G_CALLBACK (_draw_menu),
366
		pMenu);
367
	
368
	_init_menu_style ();
369
	
370
	gtk_style_context_add_class (gtk_widget_get_style_context (pMenu), "menu2");
371
	#endif
372
	
373
	// set params
374
	GldiMenuParams *pParams = g_new0 (GldiMenuParams, 1);
375
	g_object_set_data (G_OBJECT (pMenu), "gldi-params", pParams);
376
	g_signal_connect (G_OBJECT (pMenu),
377
		"destroy",
378
		G_CALLBACK (_on_menu_destroyed),
379
		NULL);
380
		
381
	// init a main menu
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
382
	if (pIcon != NULL)  // the menu points on an icon
383
	{
384
		// link it to the icon
385
		g_object_set_data (G_OBJECT (pMenu), "gldi-icon", pIcon);
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
386
		pParams->pIcon = pIcon;
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
387
		gldi_object_register_notification (pIcon,
388
			NOTIFICATION_DESTROY,
389
			(GldiNotificationFunc) _on_icon_destroyed,
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
390
			GLDI_RUN_AFTER, pMenu);  // when the icon is destroyed, unlink the menu from it; when the menu is destroyed, the above notification will be unregistered on the icon in the "destroy" callback
391
		
392
		GldiContainer *pContainer = cairo_dock_get_icon_container (pIcon);
393
		if (pContainer != NULL)
394
		{
395
			#if GTK_MAJOR_VERSION > 2
396
			// init the rendering --> align, margin-height
397
			CairoDialogDecorator *pDecorator = cairo_dock_get_dialog_decorator (myDialogsParam.cDecoratorName);
398
			if (pDecorator)
399
				pDecorator->setup_menu (pMenu);
400
			
401
			// define where the menu will point, and allocate some space to draw the arrow
402
			pParams->iMarginPosition = -1;
403
			_set_margin_position (pMenu, pParams);
404
			
405
			// show the icon's label back when the menu is hidden
406
			g_signal_connect (G_OBJECT (pMenu),
407
				"deactivate",
408
				G_CALLBACK (_on_menu_deactivated),
409
				NULL);
410
			
411
			gchar *css = NULL;
412
			const gchar *orientation = "";
413
			int ah = pParams->iArrowHeight;
414
			switch (pParams->iMarginPosition)
415
			{
416
				case 0: orientation = "bottom";  break;
417
				case 1: orientation = "top";  break;
418
				case 2: orientation = "right";  break;
419
				case 3: orientation = "left";  break;
420
				default: break;
421
			}
422
			
423
			GtkCssProvider *cssProvider = gtk_css_provider_new ();
424
			css = g_strdup_printf ("GtkMenu { \
425
				padding-%s: %dpx; \
426
			}", orientation, ah);
427
			gtk_css_provider_load_from_data (cssProvider,
428
				css, -1, NULL);
429
			gtk_style_context_add_provider (gtk_widget_get_style_context(pMenu), GTK_STYLE_PROVIDER(cssProvider), GTK_STYLE_PROVIDER_PRIORITY_USER);
430
			g_free (css);
431
			#endif
432
		}
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
433
	}
434
}
435
436
static void _place_menu_on_icon (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, G_GNUC_UNUSED gpointer data)
437
{
438
	*push_in = FALSE;
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
439
	GldiMenuParams *pParams = g_object_get_data (G_OBJECT(menu), "gldi-params");
440
	g_return_if_fail (pParams != NULL);
441
	
442
	Icon *pIcon = pParams->pIcon;
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
443
	GldiContainer *pContainer = (pIcon ? cairo_dock_get_icon_container (pIcon) : NULL);
444
	int x0 = pContainer->iWindowPositionX + pIcon->fDrawX;
445
	int y0 = pContainer->iWindowPositionY + pIcon->fDrawY;
446
	if (pContainer->bDirectionUp)
447
		y0 += pIcon->fHeight * pIcon->fScale - pIcon->image.iHeight;  // the icon might not be maximised yet
448
	
449
	int w, h;  // taille menu
450
	GtkRequisition requisition;
451
	#if (GTK_MAJOR_VERSION < 3)
452
	gtk_widget_size_request (GTK_WIDGET (menu), &requisition);
453
	#else
454
	gtk_widget_get_preferred_size (GTK_WIDGET (menu), &requisition, NULL);
455
	#endif
456
	w = requisition.width;
457
	h = requisition.height;
458
	
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
459
	/// TODO: use iMarginPosition...
460
	#if GTK_MAJOR_VERSION > 2
461
	double fAlign = pParams->fAlign;
462
	int r = pParams->iRadius;
463
	int ah = pParams->iArrowHeight;
464
	int w_, h_;
465
	#endif
466
	int iAimedX, iAimedY;
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
467
	int Hs = (pContainer->bIsHorizontal ? gldi_desktop_get_height() : gldi_desktop_get_width());
468
	if (pContainer->bIsHorizontal)
469
	{
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
470
		iAimedX = x0 + pIcon->image.iWidth/2;
471
		#if GTK_MAJOR_VERSION > 2
472
		w_ = w - 2 * r;
473
		h_ = h - 2 * r - ah;
474
		*x = MAX (0, iAimedX - fAlign * w_ - r);
475
		#else
476
		*x = iAimedX;
477
		#endif
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
478
		if (y0 > Hs/2)  // pContainer->bDirectionUp
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
479
		{
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
480
			*y = y0 - h;
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
481
			iAimedY = y0;
482
		}
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
483
		else
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
484
		{
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
485
			*y = y0 + pIcon->fHeight * pIcon->fScale;
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
486
			iAimedY = y0 + pIcon->image.iHeight;
487
		}
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
488
	}
489
	else
490
	{
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
491
		iAimedY = x0 + pIcon->image.iWidth/2;
492
		#if GTK_MAJOR_VERSION > 2
493
		w_ = w - 2 * r - ah;
494
		h_ = h - 2 * r;
495
		*y = MIN (iAimedY - fAlign * h_ - r, gldi_desktop_get_height() - h);
496
		#else
497
		*y = MIN (iAimedY, gldi_desktop_get_height() - h);
498
		#endif
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
499
		if (y0 > Hs/2)  // pContainer->bDirectionUp
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
500
		{
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
501
			*x = y0 - w;
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
502
			iAimedX = y0;
503
		}
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
504
		else
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
505
		{
506
			*x = y0 + pIcon->image.iHeight;
507
			iAimedX = y0 + pIcon->image.iHeight;
508
		}
509
	}
510
	pParams->iAimedX = iAimedX;
511
	pParams->iAimedY = iAimedY;
512
}
513
514
#if GTK_MAJOR_VERSION > 2
515
static void _init_menu_item (GtkWidget *pMenuItem)
516
{
517
	int index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pMenuItem), "gldi-text-color"));
518
	if (index == 0)
519
	{
520
		// the following code is to draw the menu item; this is more like a proof of concept
521
		/**g_signal_connect (G_OBJECT (pMenuItem),
522
			"draw",
523
			G_CALLBACK (_draw_menu_item),
524
			NULL);
525
		g_signal_connect (G_OBJECT (pMenuItem),
526
			"select",
527
			G_CALLBACK (_on_select_menu_item),
528
			NULL);
529
		g_signal_connect (G_OBJECT (pMenuItem),
530
			"deselect",
531
			G_CALLBACK (_on_deselect_menu_item),
532
			NULL);
533
		g_signal_connect (G_OBJECT (pMenuItem),
534
			"destroy",
535
			G_CALLBACK (_on_destroy_menu_item),
536
			NULL);*/
537
		
538
		gtk_style_context_add_class (gtk_widget_get_style_context (pMenuItem), "menuitem2");
539
		
540
		g_object_set_data (G_OBJECT (pMenuItem), "gldi-text-color", GINT_TO_POINTER(1));
541
	}
542
	
543
	GtkWidget *pSubMenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (pMenuItem));
544
	if (pSubMenu != NULL)  /// TODO: if it's a sub-menu not made by us (for instance, the NetworkManager indicator), set the drawing callback...
545
		gtk_container_forall (GTK_CONTAINER (pSubMenu), (GtkCallback) _init_menu_item, NULL);
546
}
547
#endif
548
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
549
static void _popup_menu (GtkWidget *menu, guint32 time)
550
{
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
551
	GldiMenuParams *pParams = g_object_get_data (G_OBJECT(menu), "gldi-params");
552
	g_return_if_fail (pParams != NULL);
553
	
554
	Icon *pIcon = pParams->pIcon;
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
555
	GldiContainer *pContainer = (pIcon ? cairo_dock_get_icon_container (pIcon) : NULL);
556
	
1.1.37 by Matthieu Baerts
Import upstream version 3.3.2
557
	// setup the menu for the container
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
558
	if (pContainer && pContainer->iface.setup_menu)
559
		pContainer->iface.setup_menu (pContainer, pIcon, menu);
560
	
561
	#if GTK_MAJOR_VERSION > 2
562
	{
563
		_init_menu_style ();
564
	}
565
	
566
	if (pIcon && pContainer)
567
	{
568
		// hide the icon's label, since menus are placed right above the icon (and therefore, the arrow overlaps the label, which makes it hard to see if both colors are similar).
569
		if (pIcon->iHideLabel == 0 && pContainer)
570
			gtk_widget_queue_draw (pContainer->pWidget);
571
		pIcon->iHideLabel ++;
572
		
573
		// ensure margin position is still correct
574
		_set_margin_position (menu, pParams);
575
	}
576
	#endif
577
	
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
578
	gtk_widget_show_all (GTK_WIDGET (menu));
579
	
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
580
	#if GTK_MAJOR_VERSION > 2
581
	// to draw the items ourselves
582
	gtk_container_forall (GTK_CONTAINER (menu), (GtkCallback) _init_menu_item, NULL);
583
	#endif
584
	
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
585
	gtk_menu_popup (GTK_MENU (menu),
586
		NULL,
587
		NULL,
588
		pIcon != NULL && pContainer != NULL ? _place_menu_on_icon : NULL,
589
		NULL,
590
		0,
591
		time);
592
}
593
static gboolean _popup_menu_delayed (GtkWidget *menu)
594
{
595
	_popup_menu (menu, 0);
596
	return FALSE;
597
}
598
void gldi_menu_popup (GtkWidget *menu)
599
{
600
	if (menu == NULL)
601
		return;
602
	
603
	guint32 t = gtk_get_current_event_time();
604
	cd_debug ("gtk_get_current_event_time: %d", t);
605
	if (t > 0)
606
	{
607
		_popup_menu (menu, t);
608
	}
609
	else  // 'gtk_menu_popup' is buggy and doesn't work if not triggered directly by an X event :-/ so in this case, we run it with a delay (200ms is the minimal value that always works).
610
	{
611
		g_timeout_add (250, (GSourceFunc)_popup_menu_delayed, menu);
612
	}
613
}
614
615
616
  /////////////////
617
 /// MENU ITEM ///
618
/////////////////
619
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
620
#if GTK_MAJOR_VERSION > 2
621
const int N = 10;
622
const int dt1 = 20;
623
const int dt2 = 30;
624
625
static void
626
get_arrow_size (GtkWidget *widget,
627
                GtkWidget *child,
628
                gint      *size,
629
                gint      *spacing)
630
{
631
	PangoContext     *context;
632
	PangoFontMetrics *metrics;
633
	gfloat            arrow_scaling;
634
	gint              arrow_spacing;
635
636
	g_assert (size);
637
638
	gtk_widget_style_get (widget,
639
		"arrow-scaling", &arrow_scaling,
640
		"arrow-spacing", &arrow_spacing,
641
		NULL);
642
643
	if (spacing != NULL)
644
		*spacing = arrow_spacing;
645
646
	context = gtk_widget_get_pango_context (child);
647
648
	metrics = pango_context_get_metrics (context,
649
									   pango_context_get_font_description (context),
650
									   pango_context_get_language (context));
651
652
	*size = (PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) +
653
						 pango_font_metrics_get_descent (metrics)));
654
655
	pango_font_metrics_unref (metrics);
656
657
	*size = *size * arrow_scaling;
658
}
659
static gboolean _draw_menu_item (GtkWidget *widget,
660
	cairo_t *cr,
661
	G_GNUC_UNUSED gpointer data)
662
{
663
	//GtkStateFlags state;
664
	GtkStyleContext *context;
665
	GtkWidget *child;
666
	gint x, y, w, h, width, height;
667
	guint border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
668
	
669
	child = gtk_bin_get_child (GTK_BIN (widget));
670
	
671
	//state = gtk_widget_get_state_flags (widget);
672
	context = gtk_widget_get_style_context (widget);
673
	width = gtk_widget_get_allocated_width (widget);
674
	height = gtk_widget_get_allocated_height (widget);
675
	
676
	x = border_width;
677
	y = border_width;
678
	w = width - border_width * 2;
679
	h = height - border_width * 2;
680
	
681
	// draw the background
682
	int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "gldi-step"));
683
	if (n != 0)  // so we only draw the barkground if the item is or was selected
684
	{
685
		double a = (double)n/N;
686
		
687
		cairo_save (cr);
688
		
689
		int r=6, l=0;
690
		cairo_dock_draw_rounded_rectangle (cr, r, l, w - 2*r, h);
691
		cairo_clip (cr);
692
		
693
		gldi_style_colors_set_selected_bg_color (cr);
694
		cairo_paint_with_alpha (cr, a);
695
		
696
		cairo_restore (cr);
697
	}
698
	
699
	// draw the arrow in case of a sub-menu
700
	if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)))
701
	{
702
		gint arrow_x, arrow_y;
703
		gint arrow_size, spacing;
704
		GtkTextDirection direction;
705
		gdouble angle;
706
707
		direction = gtk_widget_get_direction (widget);
708
		get_arrow_size (widget, child, &arrow_size, &spacing);
709
710
		if (direction == GTK_TEXT_DIR_LTR)
711
		{
712
		  arrow_x = x + w - arrow_size - spacing;
713
		  angle = G_PI / 2;
714
		}
715
		else
716
		{
717
		  arrow_x = x + spacing;
718
		  angle = (3 * G_PI) / 2;
719
		}
720
721
		arrow_y = y + (h - arrow_size) / 2;
722
723
		gtk_render_arrow (context, cr, angle, arrow_x, arrow_y, arrow_size);
724
	}
725
	else if (!child)  // separator
726
	{
727
		gboolean wide_separators;
728
		gint     separator_height;
729
		GtkBorder padding;
730
		
731
		g_print ("SEPARATOR\n");
732
		gtk_style_context_get_padding (context, 0, &padding);
733
		gtk_widget_style_get (widget,
734
			"wide-separators",    &wide_separators,
735
			"separator-height",   &separator_height,
736
			NULL);
737
		if (wide_separators)
738
			gtk_render_frame (context, cr,
739
				x + padding.left,
740
				y + padding.top,
741
				w - padding.left - padding.right,
742
				separator_height);
743
		else
744
			gtk_render_line (context, cr,
745
				x + padding.left,
746
				y + padding.top,
747
				x + w - padding.right - 1,
748
				y + padding.top);
749
	}
750
	
751
	// draw the item's content
752
	GtkWidgetClass *parent_class = g_type_class_peek (g_type_parent (G_TYPE_FROM_INSTANCE (widget)));
753
	parent_class = g_type_class_peek_parent (parent_class);
754
	
755
	cairo_set_source_rgba (cr, 1, 1, 1, 1);
756
	
757
	parent_class->draw (widget, cr);
758
	return TRUE;  // intercept
759
}
760
761
static gboolean _update_menu_item (GtkWidget* pMenuItem, G_GNUC_UNUSED gpointer data)
762
{
763
	int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(pMenuItem), "gldi-step"));
764
	gboolean bInside = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(pMenuItem), "gldi-inside"));
765
	
766
	if (bInside)
767
	{
768
		if (n < N)
769
			n ++;
770
	}
771
	else
772
	{
773
		if (n > 0)
774
			n --;
775
	}
776
	g_object_set_data (G_OBJECT(pMenuItem), "gldi-step", GINT_TO_POINTER (n));
777
	
778
	gtk_widget_queue_draw (pMenuItem);
779
	
780
	if (n == 0 || n == N)
781
	{
782
		guint iSidAnimation = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT(pMenuItem), "gldi-animation"));
783
		if (iSidAnimation != 0)
784
		{
785
			g_source_remove (iSidAnimation);
786
			g_object_set_data (G_OBJECT(pMenuItem), "gldi-animation", NULL);
787
		}
788
		return FALSE;
789
	}
790
	return TRUE;
791
}
792
static gboolean _on_select_menu_item (GtkWidget* pMenuItem, G_GNUC_UNUSED gpointer data)
793
{
794
	guint iSidAnimation = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT(pMenuItem), "gldi-animation"));
795
	if (iSidAnimation != 0)
796
		g_source_remove (iSidAnimation);
797
	iSidAnimation = g_timeout_add (dt1, (GSourceFunc)_update_menu_item, pMenuItem);
798
	g_object_set_data (G_OBJECT(pMenuItem), "gldi-animation", GUINT_TO_POINTER (iSidAnimation));
799
	
800
	g_object_set_data (G_OBJECT(pMenuItem), "gldi-inside", GINT_TO_POINTER (TRUE));
801
	
802
	return FALSE;
803
}
804
static gboolean _on_deselect_menu_item (GtkWidget* pMenuItem, G_GNUC_UNUSED gpointer data)
805
{
806
	guint iSidAnimation = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT(pMenuItem), "gldi-animation"));
807
	if (iSidAnimation != 0)
808
		g_source_remove (iSidAnimation);
809
	iSidAnimation = g_timeout_add (dt2, (GSourceFunc)_update_menu_item, pMenuItem);
810
	g_object_set_data (G_OBJECT(pMenuItem), "gldi-animation", GUINT_TO_POINTER (iSidAnimation));
811
	
812
	g_object_set_data (G_OBJECT(pMenuItem), "gldi-inside", GINT_TO_POINTER (FALSE));
813
	
814
	return FALSE;
815
}
816
static void _on_destroy_menu_item (GtkWidget* pMenuItem, G_GNUC_UNUSED gpointer data)
817
{
818
	guint iSidAnimation = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT(pMenuItem), "gldi-animation"));
819
	if (iSidAnimation != 0)
820
	{
821
		g_source_remove (iSidAnimation);
822
		g_object_set_data (G_OBJECT(pMenuItem), "gldi-animation", NULL);
823
	}
824
}
825
#endif
826
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
827
GtkWidget *gldi_menu_item_new_full (const gchar *cLabel, const gchar *cImage, gboolean bUseMnemonic, GtkIconSize iSize)
828
{
829
	if (iSize == 0)
830
		iSize = GTK_ICON_SIZE_MENU;
831
	
832
	GtkWidget *pMenuItem;
833
	if (! cImage)
834
	{
835
		if (! cLabel)
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
836
			pMenuItem = gtk_menu_item_new ();
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
837
		else
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
838
			pMenuItem =  (bUseMnemonic ? gtk_menu_item_new_with_mnemonic (cLabel) : gtk_menu_item_new_with_label (cLabel));
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
839
	}
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
840
	else
841
	{
842
		GtkWidget *image = NULL;
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
843
#if (! GTK_CHECK_VERSION (3, 10, 0)) || (CAIRO_DOCK_FORCE_ICON_IN_MENUS == 1)
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
844
		if (*cImage == '/')
845
		{
846
			int size;
847
			gtk_icon_size_lookup (iSize, &size, NULL);
848
			GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size (cImage, size, size, NULL);
849
			image = gtk_image_new_from_pixbuf (pixbuf);
850
			g_object_unref (pixbuf);
851
		}
852
		else if (*cImage != '\0')
853
		{
854
			#if GTK_CHECK_VERSION (3, 10, 0)
855
			image = gtk_image_new_from_icon_name (cImage, iSize);  /// actually, this will not work until we replace all the gtk-stock names by standard icon names... which is a PITA, and will be done do later
856
			#else
857
			image = gtk_image_new_from_stock (cImage, iSize);
858
			#endif
859
		}
860
#endif
861
		
862
#if GTK_CHECK_VERSION (3, 10, 0)
863
		#if (CAIRO_DOCK_FORCE_ICON_IN_MENUS == 1)
864
		if (! cLabel)
865
			pMenuItem = gtk3_image_menu_item_new ();
866
		else
867
			pMenuItem = (bUseMnemonic ? gtk3_image_menu_item_new_with_mnemonic (cLabel) : gtk3_image_menu_item_new_with_label (cLabel));
868
		gtk3_image_menu_item_set_image (GTK3_IMAGE_MENU_ITEM (pMenuItem), image);
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
869
		#else
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
870
		if (! cLabel)
871
			pMenuItem = gtk_menu_item_new ();
872
		else
873
			pMenuItem = (bUseMnemonic ? gtk_menu_item_new_with_mnemonic (cLabel) : gtk_menu_item_new_with_label (cLabel));
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
874
		#endif
875
#else
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
876
		if (! cLabel)
877
			pMenuItem = gtk_image_menu_item_new ();
878
		else
879
			pMenuItem = (bUseMnemonic ? gtk_image_menu_item_new_with_mnemonic (cLabel) : gtk_image_menu_item_new_with_label (cLabel));
880
		gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (pMenuItem), image);
881
		#if ((CAIRO_DOCK_FORCE_ICON_IN_MENUS == 1) && (GTK_MAJOR_VERSION > 2 || GTK_MINOR_VERSION >= 16))
882
		gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (pMenuItem), TRUE);
883
		#endif
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
884
#endif
1.1.38 by Matthieu Baerts
Import upstream version 3.3.99.beta1.1~20140219~bzr1717
885
	}
1.1.36 by Matthieu Baerts
Import upstream version 3.3.1
886
	gtk_widget_show_all (pMenuItem);  // show immediately, so that the menu-item is realized when the menu is popped up
887
	
1.1.35 by Matthieu Baerts
Import upstream version 3.3.0
888
	return pMenuItem;
889
}
890
891
892
void gldi_menu_item_set_image (GtkWidget *pMenuItem, GtkWidget *image)
893
{
894
#if GTK_CHECK_VERSION (3, 10, 0)
895
	#if (CAIRO_DOCK_FORCE_ICON_IN_MENUS == 1)
896
	gtk3_image_menu_item_set_image (GTK3_IMAGE_MENU_ITEM (pMenuItem), image);
897
	#else
898
	g_object_unref (image);
899
	#endif
900
#else
901
	gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (pMenuItem), image);
902
	#if ((CAIRO_DOCK_FORCE_ICON_IN_MENUS == 1) && (GTK_MAJOR_VERSION > 2 || GTK_MINOR_VERSION >= 16))
903
	gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (pMenuItem), TRUE);
904
	#endif
905
#endif
906
}
907
908
GtkWidget *gldi_menu_item_get_image (GtkWidget *pMenuItem)
909
{
910
	#if GTK_CHECK_VERSION (3, 10, 0)
911
	return gtk3_image_menu_item_get_image (GTK3_IMAGE_MENU_ITEM (pMenuItem));
912
	#else
913
	return gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (pMenuItem));
914
	#endif
915
}
916
917
GtkWidget *gldi_menu_item_new_with_action (const gchar *cLabel, const gchar *cImage, GCallback pFunction, gpointer pData)
918
{
919
	GtkWidget *pMenuItem = gldi_menu_item_new (cLabel, cImage);
920
	if (pFunction)
921
		g_signal_connect (G_OBJECT (pMenuItem), "activate", G_CALLBACK (pFunction), pData);
922
	return pMenuItem;
923
}
924
925
GtkWidget *gldi_menu_item_new_with_submenu (const gchar *cLabel, const gchar *cImage, GtkWidget **pSubMenuPtr)
926
{
927
	GtkIconSize iSize;
928
	if (cImage && (*cImage == '/' || *cImage == '\0'))  // for icons that are not stock-icons, we choose a bigger size; the reason is that these icons usually don't have a 16x16 version, and don't scale very well to such a small size (most of the time, it's the icon of an application, or the cairo-dock or recent-documents icon (note: for these 2, we could make a small version)). it's a workaround and a better solution may exist ^^
929
		iSize = GTK_ICON_SIZE_LARGE_TOOLBAR;
930
	else
931
		iSize = 0;
932
	GtkWidget *pMenuItem = gldi_menu_item_new_full (cLabel, cImage, FALSE, iSize);
933
	GtkWidget *pSubMenu = gldi_submenu_new ();
934
	gtk_menu_item_set_submenu (GTK_MENU_ITEM (pMenuItem), pSubMenu);
935
	
936
	*pSubMenuPtr = pSubMenu;
937
	return pMenuItem;
938
}
939
940
GtkWidget *gldi_menu_add_item (GtkWidget *pMenu, const gchar *cLabel, const gchar *cImage, GCallback pFunction, gpointer pData)
941
{
942
	GtkWidget *pMenuItem = gldi_menu_item_new_with_action (cLabel, cImage, pFunction, pData);
943
	gtk_menu_shell_append (GTK_MENU_SHELL (pMenu), pMenuItem);
944
	return pMenuItem;
945
}
946
947
GtkWidget *gldi_menu_add_sub_menu_full (GtkWidget *pMenu, const gchar *cLabel, const gchar *cImage, GtkWidget **pMenuItemPtr)
948
{
949
	GtkWidget *pSubMenu;
950
	GtkWidget *pMenuItem = gldi_menu_item_new_with_submenu (cLabel, cImage, &pSubMenu);
951
	gtk_menu_shell_append (GTK_MENU_SHELL (pMenu), pMenuItem);
952
	if (pMenuItemPtr)
953
		*pMenuItemPtr = pMenuItem;
954
	return pSubMenu; 
955
}