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 <string.h>
21 #include <math.h>
22 #include <cairo-dock.h>
23 
24 #include "applet-struct.h"
25 #include "applet-decorator-tooltip.h"
26 
27 #define _CAIRO_DIALOG_TOOLTIP_ARROW_WIDTH 28
28 #define _CAIRO_DIALOG_TOOLTIP_MARGIN 4
29 #define CD_ARROW_HEIGHT 8  /// maybe decrease a little if the width or the height is too small ?...
30 #define CD_ALIGN 0.5
31 #define CD_RADIUS (myDialogsParam.bUseDefaultColors ? myStyleParam.iCornerRadius : myDialogsParam.iCornerRadius)
32 
33 /*
34 ic______^___  arrow height + margin
35 ic     msg
36  |
37  |   widget
38  |
39  |____________  bottom margin
40 
41 */
42 
cd_decorator_set_frame_size_tooltip(CairoDialog * pDialog)43 void cd_decorator_set_frame_size_tooltip (CairoDialog *pDialog)
44 {
45 	int iMargin = .5 * myDialogsParam.iLineWidth + (1. - sqrt (2) / 2) * CD_RADIUS;
46 	int iIconOffset = pDialog->iIconSize / 2;
47 	pDialog->iRightMargin = iMargin + _CAIRO_DIALOG_TOOLTIP_MARGIN;
48 	pDialog->iLeftMargin = iIconOffset + iMargin + _CAIRO_DIALOG_TOOLTIP_MARGIN;
49 	pDialog->iTopMargin = iIconOffset + _CAIRO_DIALOG_TOOLTIP_MARGIN + myDialogsParam.iLineWidth;
50 	pDialog->iBottomMargin = _CAIRO_DIALOG_TOOLTIP_MARGIN;
51 	pDialog->iMinBottomGap = CD_ARROW_HEIGHT;
52 	pDialog->iMinFrameWidth = _CAIRO_DIALOG_TOOLTIP_ARROW_WIDTH;
53 	pDialog->fAlign = .5;
54 	pDialog->container.fRatio = 0.;
55 	pDialog->container.bUseReflect = FALSE;
56 	pDialog->iIconOffsetX = iIconOffset;
57 	pDialog->iIconOffsetY = pDialog->iTopMargin;
58 }
59 
60 
cd_decorator_draw_decorations_tooltip(cairo_t * pCairoContext,CairoDialog * pDialog)61 void cd_decorator_draw_decorations_tooltip (cairo_t *pCairoContext, CairoDialog *pDialog)
62 {
63 	double fLineWidth = myDialogsParam.iLineWidth;
64 	double fRadius = CD_RADIUS;
65 	double fIconOffset = pDialog->iIconSize / 2;  // myDialogsParam.iDialogIconSize/2
66 
67 	double fOffsetX = fRadius + fLineWidth / 2 + fIconOffset;
68 	double fOffsetY = (pDialog->container.bDirectionUp ? fLineWidth / 2 : pDialog->container.iHeight - fLineWidth / 2) + (pDialog->container.bDirectionUp ? fIconOffset : /**-fIconOffset*/ -_CAIRO_DIALOG_TOOLTIP_MARGIN);  // _CAIRO_DIALOG_TOOLTIP_MARGIN is to compensate for the slightly different placement of top dialogs
69 	int sens = (pDialog->container.bDirectionUp ? 1 : -1);
70 	int iWidth = pDialog->container.iWidth - fIconOffset;
71 
72 	int h = pDialog->iBubbleHeight + pDialog->iTopMargin + pDialog->iBottomMargin - (2 * fRadius + fLineWidth);
73 	if (pDialog->container.bDirectionUp)
74 		h -= fIconOffset;
75 	else
76 		h -= _CAIRO_DIALOG_TOOLTIP_MARGIN;
77 
78 	//On se déplace la ou il le faut
79 	cairo_move_to (pCairoContext, fOffsetX, fOffsetY);
80 
81 	// Ligne du haut (Haut gauche -> Haut Droite)
82 	cairo_rel_line_to (pCairoContext, iWidth - (2 * fRadius + fLineWidth), 0);
83 
84 	// Coin haut droit.
85 	cairo_rel_curve_to (pCairoContext,
86 		0, 0,
87 		fRadius, 0,
88 		fRadius, sens * fRadius);
89 
90 	// Ligne droite. (Haut droit -> Bas droit)
91 	cairo_rel_line_to (pCairoContext, 0, sens * h);
92 
93 	// Coin bas droit.
94 	cairo_rel_curve_to (pCairoContext,
95 		0, 0,
96 		0, sens * fRadius,
97 		-fRadius, sens * fRadius);
98 
99 	// La pointe.
100 	int iDeltaIconX = pDialog->container.iWindowPositionX + pDialog->container.iWidth - fRadius - fLineWidth/2 - pDialog->iAimedX;
101 	cairo_rel_line_to (pCairoContext, - iDeltaIconX + _CAIRO_DIALOG_TOOLTIP_ARROW_WIDTH/2, 0);
102 	cairo_rel_line_to (pCairoContext, - _CAIRO_DIALOG_TOOLTIP_ARROW_WIDTH/2, sens * CD_ARROW_HEIGHT);
103 	cairo_rel_line_to (pCairoContext, - _CAIRO_DIALOG_TOOLTIP_ARROW_WIDTH/2, -sens * CD_ARROW_HEIGHT);
104 	cairo_rel_line_to (pCairoContext, - iWidth + 2*fRadius + fLineWidth + iDeltaIconX + _CAIRO_DIALOG_TOOLTIP_ARROW_WIDTH/2, 0);
105 
106 	// Coin bas gauche.
107 	cairo_rel_curve_to (pCairoContext,
108 		0, 0,
109 		-fRadius, 0,
110 		-fRadius, -sens * fRadius);
111 
112 	// On remonte.
113 	cairo_rel_line_to (pCairoContext, 0, - sens * h);
114 
115 	// Coin haut gauche.
116 	cairo_rel_curve_to (pCairoContext,
117 		0, 0,
118 		0, -sens * fRadius,
119 		fRadius, -sens * fRadius);
120 	if (fRadius < 1)
121 		cairo_close_path (pCairoContext);
122 
123 	// draw background
124 	if (myDialogsParam.bUseDefaultColors)
125 		gldi_style_colors_set_bg_color (pCairoContext);
126 	else
127 		gldi_color_set_cairo (pCairoContext, &myDialogsParam.fBgColor);
128 	///cairo_fill_preserve (pCairoContext);
129 	cairo_save (pCairoContext);
130 	cairo_clip_preserve (pCairoContext);
131 	///gldi_style_colors_paint_bg_color_with_alpha (pCairoContext, pDialog->container.iWidth, myDialogsParam.bUseDefaultColors ? -1. : myDialogsParam.fBgColor[3]);
132 	cairo_paint (pCairoContext);
133 	cairo_restore (pCairoContext);
134 
135 	// draw outline
136 	if (myDialogsParam.bUseDefaultColors)
137 		gldi_style_colors_set_line_color (pCairoContext);
138 	else
139 		gldi_color_set_cairo (pCairoContext, &myDialogsParam.fLineColor);
140 	cairo_set_line_width (pCairoContext, fLineWidth);
141 	cairo_stroke (pCairoContext);
142 }
143 
144 
_render_menu(GtkWidget * pMenu,cairo_t * pCairoContext)145 static void _render_menu (GtkWidget *pMenu, cairo_t *pCairoContext)
146 {
147 	GldiMenuParams *pParams = g_object_get_data (G_OBJECT(pMenu), "gldi-params");
148 	int iMarginPosition = -1;
149 	int iAimedX = 0, iAimedY = 0;
150 	int ah = CD_ARROW_HEIGHT;
151 	if (pParams && pParams->pIcon)  // main menu
152 	{
153 		iMarginPosition = pParams->iMarginPosition;
154 		iAimedX = pParams->iAimedX;
155 		iAimedY = pParams->iAimedY;
156 	}
157 	double fRadius = CD_RADIUS, fLineWidth = myDialogsParam.iLineWidth;
158 
159 	// draw the outline and set the clip
160 	GtkAllocation alloc;
161 	gtk_widget_get_allocation (pMenu, &alloc);
162 
163 	int w = alloc.width, h = alloc.height;
164 	int x, y;
165 	gdk_window_get_position (gtk_widget_get_window (gtk_widget_get_toplevel(pMenu)), &x, &y);
166 	int _ah = ah - fLineWidth;  // we want the tip of the arrow to reach the border, not the middle of the stroke
167 	int aw = _CAIRO_DIALOG_TOOLTIP_ARROW_WIDTH/2;
168 	int _aw = aw;
169 	double dx, dy;
170 
171 	double fDockOffsetX = fRadius + fLineWidth/2;
172 	double fDockOffsetY = fLineWidth/2;
173 	double fFrameWidth, fFrameHeight;
174 	fFrameWidth = w - 2*fRadius - fLineWidth;
175 	fFrameHeight = h - fLineWidth;
176 	switch (iMarginPosition)
177 	{
178 		case 0:  // bottom
179 			fFrameHeight -= ah;
180 		break;
181 		case 1:  // top
182 			fFrameHeight -= ah;
183 			fDockOffsetY += ah;
184 		break;
185 		case 2:  // right
186 			fFrameWidth -= ah;
187 		break;
188 		case 3:  // left
189 			fFrameWidth -= ah;
190 			fDockOffsetX += ah;
191 		break;
192 		default:
193 		break;
194 	}
195 
196 	cairo_move_to (pCairoContext, fDockOffsetX, fDockOffsetY);
197 
198 	if (iMarginPosition == 1)  // top arrow
199 	{
200 		dx = MIN (w - fRadius - 2*aw, MAX (fRadius, iAimedX - x - aw));
201 		cairo_line_to (pCairoContext, dx, fDockOffsetY);
202 		cairo_line_to (pCairoContext, MIN (w, MAX (0, iAimedX - x)), fDockOffsetY - _ah);
203 		cairo_line_to (pCairoContext, dx + 2*aw, fDockOffsetY);
204 		cairo_line_to (pCairoContext, fFrameWidth, fDockOffsetY);
205 	}
206 	else
207 		cairo_rel_line_to (pCairoContext, fFrameWidth, 0);
208 
209 	//\_________________ Coin haut droit.
210 	cairo_arc (pCairoContext,
211 		fDockOffsetX + fFrameWidth, fDockOffsetY + fRadius,
212 		fRadius,
213 		-G_PI/2, 0.);
214 	if (iMarginPosition == 2)  // right arrow
215 	{
216 		if (h < 2*aw + 2*fRadius)
217 			_aw = (h - 2*fRadius) / 2;
218 		dy = MIN (h - fRadius - 2*aw, MAX (fRadius, iAimedY - y - _aw));
219 		cairo_line_to (pCairoContext, w - ah, dy);
220 		cairo_line_to (pCairoContext, w - ah + _ah, MAX (0, iAimedY - y));
221 		cairo_line_to (pCairoContext, w - ah, dy + 2*_aw);
222 		cairo_line_to (pCairoContext, w - ah, h - fRadius);
223 	}
224 	else
225 		cairo_rel_line_to (pCairoContext, 0, (fFrameHeight - fRadius * 2));
226 
227 	//\_________________ Coin bas droit.
228 	cairo_arc (pCairoContext,
229 		fDockOffsetX + fFrameWidth, fDockOffsetY + fFrameHeight - fRadius,
230 		fRadius,
231 		0., G_PI/2);
232 
233 	if (iMarginPosition == 0)  // bottom arrow
234 	{
235 		dx = MIN (w - fRadius - 2*aw, MAX (fRadius, iAimedX - x - aw));
236 		cairo_line_to (pCairoContext, dx + 2*aw, fDockOffsetY + fFrameHeight);
237 		cairo_line_to (pCairoContext, MIN (w, MAX (0, iAimedX - x)), fDockOffsetY + fFrameHeight + _ah);
238 		cairo_line_to (pCairoContext, dx, fDockOffsetY + fFrameHeight);
239 		cairo_line_to (pCairoContext, fDockOffsetX, fDockOffsetY + fFrameHeight);
240 	}
241 	else
242 		cairo_rel_line_to (pCairoContext, - fFrameWidth, 0);
243 
244 	//\_________________ Coin bas gauche.
245 	cairo_arc (pCairoContext,
246 		fDockOffsetX, fDockOffsetY + fFrameHeight - fRadius,
247 		fRadius,
248 		G_PI/2, G_PI);
249 
250 	if (iMarginPosition == 3)  // left arrow
251 	{
252 		if (h < 2*aw + 2*fRadius)
253 			_aw = (h - 2*fRadius) / 2;
254 		dy = MIN (h - fRadius - 2*aw, MAX (fRadius, iAimedY - y - _aw));
255 		cairo_line_to (pCairoContext, ah, dy);
256 		cairo_line_to (pCairoContext, ah - _ah, MAX (0, iAimedY - y));
257 		cairo_line_to (pCairoContext, ah, dy + 2*_aw);
258 		cairo_line_to (pCairoContext, ah, fRadius);
259 	}
260 	else
261 		cairo_rel_line_to (pCairoContext, 0, - fFrameHeight + fRadius * 2);
262 	//\_________________ Coin haut gauche.
263 	cairo_arc (pCairoContext,
264 		fDockOffsetX, fDockOffsetY + fRadius,
265 		fRadius,
266 		G_PI, -G_PI/2);
267 
268 	// draw the background
269 	if (myDialogsParam.bUseDefaultColors)
270 		gldi_style_colors_set_bg_color_full (pCairoContext, FALSE);
271 	else
272 		gldi_color_set_cairo_rgb (pCairoContext, &myDialogsParam.fBgColor);
273 	cairo_save (pCairoContext);
274 	cairo_clip_preserve (pCairoContext);
275 	gldi_style_colors_paint_bg_color_with_alpha (pCairoContext, alloc.width, myDialogsParam.bUseDefaultColors ? -1. : myDialogsParam.fBgColor.rgba.alpha);
276 	cairo_restore (pCairoContext);
277 
278 	// draw outline
279 	if (fLineWidth != 0)  // draw the outline with same color as bg, but opaque
280 	{
281 		if (myDialogsParam.bUseDefaultColors)
282 			gldi_style_colors_set_line_color (pCairoContext);
283 		else
284 			gldi_color_set_cairo (pCairoContext, &myDialogsParam.fLineColor);
285 		cairo_set_line_width (pCairoContext, fLineWidth);
286 		cairo_stroke_preserve (pCairoContext);
287 	}
288 
289 	cairo_clip (pCairoContext);  // clip
290 }
291 
_setup_menu(GtkWidget * pMenu)292 static void _setup_menu (GtkWidget *pMenu)
293 {
294 	GldiMenuParams *pParams = g_object_get_data (G_OBJECT(pMenu), "gldi-params");
295 	pParams->iRadius = CD_RADIUS;
296 	pParams->fAlign = CD_ALIGN;
297 	pParams->iArrowHeight = CD_ARROW_HEIGHT;
298 }
299 
cd_decorator_register_tooltip(void)300 void cd_decorator_register_tooltip (void)
301 {
302 	CairoDialogDecorator *pDecorator = g_new (CairoDialogDecorator, 1);
303 	pDecorator->set_size = cd_decorator_set_frame_size_tooltip;
304 	pDecorator->render = cd_decorator_draw_decorations_tooltip;
305 	pDecorator->render_opengl = NULL;
306 	pDecorator->setup_menu = _setup_menu;
307 	pDecorator->render_menu = _render_menu;
308 	pDecorator->cDisplayedName = D_ (MY_APPLET_DECORATOR_TOOLTIP_NAME);
309 	cairo_dock_register_dialog_decorator (MY_APPLET_DECORATOR_TOOLTIP_NAME, pDecorator);
310 }
311