1 /* High Contrast - a cairo based GTK+ engine
2  * Copyright (C) 2003 Sun Microsystems Inc.
3  * Copyright (C) 2006 Andrew Johnson <acjgenius@earthlink.net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Project contact: <gnome-themes-list@gnome.org>
20  *
21  */
22 
23 
24 #include "hc_gtk2_engine.h"
25 #include "hc_gtk2_support.h"
26 #include "hc_gtk2_drawing.h"
27 
28 void
hc_simple_border_gap_clip(cairo_t * canvas,gint border_thickness,gint x,gint y,gint width,gint height,GtkPositionType gap_side,gint gap_pos,gint gap_size)29 hc_simple_border_gap_clip(cairo_t *canvas,
30 			  gint border_thickness,
31 
32 			  gint x,
33 			  gint y,
34 			  gint width,
35 			  gint height,
36 
37 			  GtkPositionType gap_side,
38 			  gint gap_pos,
39 			  gint gap_size)
40 {
41 	switch (gap_side)
42 	{
43 		default:
44 		case GTK_POS_TOP:
45 			cairo_move_to(canvas, x, y);
46 			cairo_line_to(canvas, x, y + height);
47 			cairo_line_to(canvas, x + width, y + height);
48 			cairo_line_to(canvas, x + width, y);
49 			cairo_line_to(canvas, x + gap_pos + gap_size, y);
50 			cairo_line_to(canvas, x + gap_pos + gap_size, y + border_thickness + 1);
51 			cairo_line_to(canvas, x + gap_pos, y + border_thickness + 1);
52 			cairo_line_to(canvas, x + gap_pos, y);
53 			cairo_close_path(canvas);
54 		break;
55 
56 		case GTK_POS_LEFT:
57 			cairo_move_to(canvas, x, y);
58 			cairo_line_to(canvas, x + width, y);
59 			cairo_line_to(canvas, x + width, y + height);
60 			cairo_line_to(canvas, x, y + height);
61 			cairo_line_to(canvas, x, y + gap_pos + gap_size);
62 			cairo_line_to(canvas, x + border_thickness + 1, y + gap_pos + gap_size);
63 			cairo_line_to(canvas, x + border_thickness + 1, y + gap_pos);
64 			cairo_line_to(canvas, x, y + gap_pos);
65 			cairo_close_path(canvas);
66 		break;
67 
68 		case GTK_POS_BOTTOM:
69 			cairo_move_to(canvas, x + width, y + height);
70 			cairo_line_to(canvas, x + width, y);
71 			cairo_line_to(canvas, x, y);
72 			cairo_line_to(canvas, x, y + height);
73 			cairo_line_to(canvas, x + gap_pos, y + height);
74 			cairo_line_to(canvas, x + gap_pos, y + height - border_thickness - 1);
75 			cairo_line_to(canvas, x + gap_pos + gap_size, y + height - border_thickness - 1);
76 			cairo_line_to(canvas, x + gap_pos + gap_size, y + height);
77 			cairo_close_path(canvas);
78 		break;
79 
80 		case GTK_POS_RIGHT:
81 			cairo_line_to(canvas, x + width, y);
82 			cairo_line_to(canvas, x, y);
83 			cairo_line_to(canvas, x, y + height);
84 			cairo_line_to(canvas, x + width, y + height);
85 			cairo_line_to(canvas, x + width, y + gap_pos + gap_size);
86 			cairo_line_to(canvas, x + width - border_thickness - 1, y + gap_pos + gap_size);
87 			cairo_line_to(canvas, x + width - border_thickness - 1, y + gap_pos);
88 			cairo_line_to(canvas, x + width, y + gap_pos);
89 			cairo_close_path(canvas);
90 		break;
91 	}
92 
93 	cairo_clip(canvas);
94 }
95 
96 /***********************************************
97  * do_hc_draw_arrow -
98  *
99  *   A simple routine to draw a hc style
100  *   arrow using the passed Color.
101  *
102  *   Taken in part from smooth, it was based on
103  *   XFCE's & CleanIce draw arrow routines,
104  *   both which  were based on ThinIce's.
105  ***********************************************/
106 void
do_hc_draw_arrow(cairo_t * canvas,CairoColor * color,GtkArrowType arrow_type,gboolean fill,gint x,gint y,gint width,gint height)107 do_hc_draw_arrow (cairo_t *canvas,
108                CairoColor * color,
109                GtkArrowType arrow_type,
110                gboolean fill,
111                gint x,
112                gint y,
113                gint width,
114                gint height)
115 {
116 	gint aw = width, ah = height;
117  	GdkPoint points[3];
118 
119 	switch (arrow_type)
120 	{
121 		case GTK_ARROW_UP:
122 		case GTK_ARROW_DOWN:
123 		{
124 			gdouble tmp=((aw+1)/2) - ((height%2)?1:0);
125 
126 			if (tmp > ah)
127 			{
128 				aw = 2*ah - 1 - ((height%2)?1:0);
129 				ah = (aw+1)/2;
130 			}
131 			else
132 			{
133 				ah = (gint) tmp;
134 				aw = 2*ah - 1;
135 			}
136 
137 			if ((aw < 5) || (ah < 3))
138 			{
139 				aw = 5;
140 				ah = 3;
141 			}
142 
143 			x += (width - aw) / 2 ;
144 			y += (height - ah) / 2;
145 			width = aw;
146 			height = ah;
147 
148 			width += width % 2 - 1;
149 
150 			points[0].x = x;
151 			points[1].x = x + width - 1;
152 			points[2].x = x + ((height - 1) - (height - (1 + width / 2)));
153 
154 			points[0].y = points[1].y = y;
155 			points[2].y = y + height - 1;
156 
157 			if (arrow_type == GTK_ARROW_UP)
158 			{
159 				gint flip = points[1].y;
160 
161 				points[0].y = points[1].y = points[2].y;
162 				points[2].y = flip;
163 			}
164 		}
165 		break;
166 
167 		case GTK_ARROW_LEFT:
168 		case GTK_ARROW_RIGHT:
169 		{
170 			gdouble tmp=((ah+1)/2) - ((width%2)?1:0);
171 
172 			if (tmp > aw)
173 			{
174 				ah = 2*aw - 1 - ((width%2)?1:0);
175 				aw = (ah+1)/2;
176 			}
177 			else
178 			{
179 				aw = (gint) tmp;
180 				ah = 2*aw - 1;
181 			}
182 
183 			if ((ah < 5) || (aw < 3))
184 			{
185 				ah = 5;
186 				aw = 3;
187 			}
188 
189 			x += (width - aw) / 2 ;
190 			y += (height - ah) / 2;
191 			width = aw;
192 			height = ah;
193 
194 			height += height % 2 - 1;
195 
196 			points[0].y = y;
197 			points[1].y = y + height - 1;
198 			points[2].y = y + ((width - 1) - (width - (1 + height / 2)));
199 
200 			points[0].x = points[1].x = x;
201 			points[2].x = x + width - 1;
202 
203 			if (arrow_type == GTK_ARROW_LEFT)
204 			{
205 				gint flip = points[0].x;
206 
207 				points[0].x = points[1].x = points[2].x;
208 				points[2].x = flip;
209 			}
210 		}
211 		break;
212 
213 		default:
214 		{
215 			return;
216 		}
217 	}
218 
219 	cairo_save(canvas);
220 
221 	ge_cairo_set_color(canvas, color);
222 	cairo_set_line_width (canvas, 0.5);
223 
224 	cairo_move_to(canvas, points[0].x + 0.5, points[0].y + 0.5);
225 	cairo_line_to(canvas, points[1].x + 0.5, points[1].y + 0.5);
226 	cairo_line_to(canvas, points[2].x + 0.5, points[2].y + 0.5);
227 	cairo_close_path(canvas);
228 
229 	if (fill)
230 	{
231 		cairo_stroke_preserve(canvas);
232 
233 		cairo_fill(canvas);
234 	}
235 	else
236 	{
237 		cairo_stroke(canvas);
238 	}
239 
240 	cairo_restore(canvas);
241 }
242 
do_hc_draw_line(cairo_t * cr,CairoColor * color,gdouble thickness,gdouble x1,gdouble y1,gdouble x2,gdouble y2)243 void do_hc_draw_line (cairo_t *cr,
244 			CairoColor *color,
245 			gdouble thickness,
246 			gdouble x1,
247 			gdouble y1,
248 			gdouble x2,
249 			gdouble y2)
250 {
251 	cairo_save(cr);
252 
253 	ge_cairo_set_color(cr, color);
254 	cairo_set_line_width (cr, thickness);
255 	cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
256 
257 	cairo_move_to(cr, x1, y1);
258 	cairo_line_to(cr, x2, y2);
259 
260 	cairo_stroke(cr);
261 
262 	cairo_restore(cr);
263 }
264 
265 void
do_hc_draw_dot(cairo_t * canvas,CairoColor * light,CairoColor * dark,gint x,gint y)266 do_hc_draw_dot (cairo_t *canvas,
267 			CairoColor * light,
268 			CairoColor * dark,
269 			gint x,
270 			gint y)
271 {
272 	ge_cairo_set_color (canvas, dark);
273 	cairo_rectangle (canvas, x - 1, y - 1, 1, 1);
274 	cairo_rectangle (canvas, x - 1, y, 1, 1);
275 	cairo_rectangle (canvas, x, y - 1, 1, 1);
276 	cairo_fill (canvas);
277 
278 	ge_cairo_set_color (canvas, light);
279 	cairo_rectangle (canvas, x + 1, y + 1, 1, 1);
280 	cairo_rectangle (canvas, x + 1, y, 1, 1);
281 	cairo_rectangle (canvas, x, y + 1, 1, 1);
282 	cairo_fill (canvas);
283 }
284 
285 /***********************************************/
286 /* MenuShell/MenuBar Item Prelight Workaround  */
287 /***********************************************/
288 
289 /***********************************************
290  * hc_gtk2_engine_hack_menu_shell_style_set -
291  *
292  *   Style set signal to ensure menushell signals
293  *   get cleaned up if the theme changes
294  ***********************************************/
295 static gboolean
hc_gtk2_engine_hack_menu_shell_style_set(GtkWidget * widget,GtkStyle * previous_style,gpointer user_data)296 hc_gtk2_engine_hack_menu_shell_style_set(GtkWidget *widget,
297                          GtkStyle *previous_style,
298                          gpointer user_data)
299 {
300   hc_gtk2_engine_hack_menu_shell_cleanup_signals(widget);
301 
302   return FALSE;
303 }
304 
305 /***********************************************
306  * hc_gtk2_engine_hack_menu_shell_destroy -
307  *
308  *   Destroy signal to ensure menushell signals
309  *   get cleaned if it is destroyed
310  ***********************************************/
311 static gboolean
hc_gtk2_engine_hack_menu_shell_destroy(GtkWidget * widget,GdkEvent * event,gpointer user_data)312 hc_gtk2_engine_hack_menu_shell_destroy(GtkWidget *widget,
313                        GdkEvent *event,
314                        gpointer user_data)
315 {
316   hc_gtk2_engine_hack_menu_shell_cleanup_signals(widget);
317 
318   return FALSE;
319 }
320 
321 /***********************************************
322  * hc_gtk2_engine_hack_menu_shell_motion -
323  *
324  *   Motion signal to ensure menushell items
325  *   prelight state changes on mouse move.
326  ***********************************************/
327 static gboolean
hc_gtk2_engine_hack_menu_shell_motion(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)328 hc_gtk2_engine_hack_menu_shell_motion(GtkWidget *widget,
329                       GdkEventMotion *event,
330                       gpointer user_data)
331 {
332   if (GE_IS_MENU_SHELL(widget))
333     {
334       gint pointer_x, pointer_y;
335       GdkModifierType pointer_mask;
336       GList *children = NULL, *child = NULL;
337 
338       gdk_window_get_pointer(widget->window, &pointer_x, &pointer_y, &pointer_mask);
339 
340       if (GE_IS_CONTAINER(widget))
341         {
342           children = gtk_container_get_children(GTK_CONTAINER(widget));
343 
344           for (child = g_list_first(children); child; child = g_list_next(child))
345             {
346 	      if ((child->data) && GE_IS_WIDGET(child->data) &&
347                   (GTK_WIDGET_STATE(GTK_WIDGET(child->data)) != GTK_STATE_INSENSITIVE))
348 	        {
349 	          if ((pointer_x >= GTK_WIDGET(child->data)->allocation.x) &&
350 	              (pointer_y >= GTK_WIDGET(child->data)->allocation.y) &&
351 	              (pointer_x < (GTK_WIDGET(child->data)->allocation.x +
352 	                              GTK_WIDGET(child->data)->allocation.width)) &&
353 	              (pointer_y < (GTK_WIDGET(child->data)->allocation.y +
354 	                              GTK_WIDGET(child->data)->allocation.height)))
355 	            {
356                       gtk_widget_set_state (GTK_WIDGET(child->data), GTK_STATE_PRELIGHT);
357 	            }
358 	          else
359                     {
360                       gtk_widget_set_state (GTK_WIDGET(child->data), GTK_STATE_NORMAL);
361                     }
362                  }
363              }
364 
365            if (children)
366              g_list_free(children);
367         }
368     }
369 
370   return FALSE;
371 }
372 
373 /***********************************************
374  * hc_gtk2_engine_hack_menu_shell_leave -
375  *
376  *   Leave signal to ensure menushell items
377  *   normal state on mouse leave.
378  ***********************************************/
379 static gboolean
hc_gtk2_engine_hack_menu_shell_leave(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)380 hc_gtk2_engine_hack_menu_shell_leave(GtkWidget *widget,
381                       GdkEventCrossing *event,
382                       gpointer user_data)
383 {
384   if (GE_IS_MENU_SHELL(widget))
385     {
386       GList *children = NULL, *child = NULL;
387 
388       if (GE_IS_CONTAINER(widget))
389         {
390           children = gtk_container_get_children(GTK_CONTAINER(widget));
391 
392           for (child = g_list_first(children); child; child = g_list_next(child))
393             {
394 	      if ((child->data) && GE_IS_MENU_ITEM(child->data) &&
395                   (GTK_WIDGET_STATE(GTK_WIDGET(child->data)) != GTK_STATE_INSENSITIVE))
396 	        {
397                   if ((!GE_IS_MENU(GTK_MENU_ITEM(child->data)->submenu)) ||
398                       (!(GTK_WIDGET_REALIZED(GTK_MENU_ITEM(child->data)->submenu) &&
399                          GTK_WIDGET_VISIBLE(GTK_MENU_ITEM(child->data)->submenu) &&
400                          GTK_WIDGET_REALIZED(GTK_MENU(GTK_MENU_ITEM(child->data)->submenu)->toplevel) &&
401                          GTK_WIDGET_VISIBLE(GTK_MENU(GTK_MENU_ITEM(child->data)->submenu)->toplevel))))
402 	          {
403                     gtk_widget_set_state (GTK_WIDGET(child->data), GTK_STATE_NORMAL);
404                   }
405                 }
406             }
407 
408           if (children)
409   	    g_list_free(children);
410         }
411     }
412 
413   return FALSE;
414 }
415 
416 /***********************************************
417  * hc_gtk2_engine_menu_shell_setup_signals -
418  *
419  *   Setup Menu Shell with signals to ensure
420  *   prelight works on items
421  ***********************************************/
422 void
hc_gtk2_engine_hack_menu_shell_setup_signals(GtkWidget * widget)423 hc_gtk2_engine_hack_menu_shell_setup_signals(GtkWidget *widget)
424 {
425   if (GE_IS_MENU_BAR(widget))
426     {
427       gint id = 0;
428 
429       if (!g_object_get_data(G_OBJECT(widget), "HC_MENU_SHELL_HACK_SET"))
430       {
431         id = g_signal_connect(G_OBJECT(widget), "motion-notify-event",
432                                              (GCallback)hc_gtk2_engine_hack_menu_shell_motion,
433                                              NULL);
434 
435         g_object_set_data(G_OBJECT(widget), "HC_MENU_SHELL_MOTION_ID", (gpointer)id);
436 
437         id = g_signal_connect(G_OBJECT(widget), "leave-notify-event",
438                                              (GCallback)hc_gtk2_engine_hack_menu_shell_leave,
439                                              NULL);
440         g_object_set_data(G_OBJECT(widget), "HC_MENU_SHELL_LEAVE_ID", (gpointer)id);
441 
442         id = g_signal_connect(G_OBJECT(widget), "destroy-event",
443                                              (GCallback)hc_gtk2_engine_hack_menu_shell_destroy,
444                                              NULL);
445         g_object_set_data(G_OBJECT(widget), "HC_MENU_SHELL_DESTROY_ID", (gpointer)id);
446 
447         g_object_set_data(G_OBJECT(widget), "HC_MENU_SHELL_HACK_SET", (gpointer)1);
448 
449         id = g_signal_connect(G_OBJECT(widget), "style-set",
450                                              (GCallback)hc_gtk2_engine_hack_menu_shell_style_set,
451                                              NULL);
452         g_object_set_data(G_OBJECT(widget), "HC_MENU_SHELL_STYLE_SET_ID", (gpointer)id);
453       }
454     }
455 }
456 
457 /***********************************************
458  * hc_gtk2_engine_hack_menu_shell_cleanuo_signals -
459  *
460  *   Cleanup/remove Menu Shell signals
461  ***********************************************/
462 void
hc_gtk2_engine_hack_menu_shell_cleanup_signals(GtkWidget * widget)463 hc_gtk2_engine_hack_menu_shell_cleanup_signals(GtkWidget *widget)
464 {
465   if (GE_IS_MENU_BAR(widget))
466     {
467       gint id = 0;
468 
469       id = (gint)g_object_steal_data (G_OBJECT(widget), "HC_MENU_SHELL_MOTION_ID");
470       g_signal_handler_disconnect(G_OBJECT(widget), id);
471 
472       id = (gint)g_object_steal_data (G_OBJECT(widget), "HC_MENU_SHELL_LEAVE_ID");
473       g_signal_handler_disconnect(G_OBJECT(widget), id);
474 
475       id = (gint)g_object_steal_data (G_OBJECT(widget), "HC_MENU_SHELL_DESTROY_ID");
476       g_signal_handler_disconnect(G_OBJECT(widget), id);
477 
478       id = (gint)g_object_steal_data (G_OBJECT(widget), "HC_MENU_SHELL_STYLE_SET_ID");
479       g_signal_handler_disconnect(G_OBJECT(widget), id);
480 
481       g_object_steal_data (G_OBJECT(widget), "HC_MENU_SHELL_HACK_SET");
482     }
483 }
484