1 /* Copyright (C) 2005-2006 Emmanuele Bassi <ebassi@gmail.com>
2  * Copyright (C) 2012-2021 MATE Developers
3  *
4  * Ported from Seth Nickell's Python class.
5  * Copyright (C) 2003 Seth Nickell
6  *
7  * This file is part of MATE Utils.
8  *
9  * MATE Utils is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * MATE Utils is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with MATE Utils.  If not, see <https://www.gnu.org/licenses/>.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include <gtk/gtk.h>
32 #include <gdk/gdkx.h>
33 
34 #include "gdict-aligned-window.h"
35 
36 struct _GdictAlignedWindowPrivate
37 {
38   GtkWidget *align_widget;
39 
40   guint motion_id;
41 };
42 
43 enum
44 {
45   PROP_0,
46 
47   PROP_ALIGN_WIDGET
48 };
49 
50 static void gdict_aligned_window_finalize     (GObject      *object);
51 static void gdict_aligned_window_get_property (GObject      *object,
52 					       guint         prop_id,
53 					       GValue       *value,
54 					       GParamSpec   *pspec);
55 static void gdict_aligned_window_set_property (GObject      *object,
56 					       guint         prop_id,
57 					       const GValue *value,
58 					       GParamSpec   *pspec);
59 
60 static void     gdict_aligned_window_realize          (GtkWidget        *widget);
61 static void     gdict_aligned_window_show             (GtkWidget        *widget);
62 
63 static gboolean gdict_aligned_window_motion_notify_cb (GtkWidget        *widget,
64 						       GdkEventMotion   *event,
65 						       GdictAlignedWindow *aligned_window);
66 
67 
68 G_DEFINE_TYPE_WITH_PRIVATE (GdictAlignedWindow, gdict_aligned_window, GTK_TYPE_WINDOW);
69 
70 
71 
72 static void
gdict_aligned_window_class_init(GdictAlignedWindowClass * klass)73 gdict_aligned_window_class_init (GdictAlignedWindowClass *klass)
74 {
75   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
76   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
77 
78   gobject_class->set_property = gdict_aligned_window_set_property;
79   gobject_class->get_property = gdict_aligned_window_get_property;
80   gobject_class->finalize = gdict_aligned_window_finalize;
81 
82   widget_class->realize = gdict_aligned_window_realize;
83   widget_class->show = gdict_aligned_window_show;
84 
85   g_object_class_install_property (gobject_class, PROP_ALIGN_WIDGET,
86   				   g_param_spec_object ("align-widget",
87   				   			"Align Widget",
88   				   			"The widget the window should align to",
89   				   			GTK_TYPE_WIDGET,
90   				   			G_PARAM_READWRITE));
91 }
92 
93 static void
gdict_aligned_window_init(GdictAlignedWindow * aligned_window)94 gdict_aligned_window_init (GdictAlignedWindow *aligned_window)
95 {
96   GdictAlignedWindowPrivate *priv = gdict_aligned_window_get_instance_private (aligned_window);
97   GtkWindow *window = GTK_WINDOW (aligned_window);
98 
99   aligned_window->priv = priv;
100 
101   priv->align_widget = NULL;
102   priv->motion_id = 0;
103 
104   /* set window properties */
105 #if 0
106   gtk_window_set_modal (window, TRUE);
107 #endif
108   gtk_window_set_decorated (window, FALSE);
109   gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DOCK);
110 }
111 
112 static void
gdict_aligned_window_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)113 gdict_aligned_window_get_property (GObject    *object,
114 				   guint       prop_id,
115 				   GValue     *value,
116 				   GParamSpec *pspec)
117 {
118   GdictAlignedWindow *aligned_window = GDICT_ALIGNED_WINDOW (object);
119 
120   switch (prop_id)
121     {
122     case PROP_ALIGN_WIDGET:
123       g_value_set_object (value, aligned_window->priv->align_widget);
124       break;
125     default:
126       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
127       break;
128     }
129 }
130 
131 static void
gdict_aligned_window_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)132 gdict_aligned_window_set_property (GObject      *object,
133 				   guint         prop_id,
134 				   const GValue *value,
135 				   GParamSpec   *pspec)
136 {
137   GdictAlignedWindow *aligned_window = GDICT_ALIGNED_WINDOW (object);
138 
139   switch (prop_id)
140     {
141     case PROP_ALIGN_WIDGET:
142       gdict_aligned_window_set_widget (aligned_window,
143       				       g_value_get_object (value));
144       break;
145     default:
146       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
147       break;
148     }
149 }
150 
151 static void
gdict_aligned_window_position(GdictAlignedWindow * window)152 gdict_aligned_window_position (GdictAlignedWindow *window)
153 {
154   GdictAlignedWindowPrivate *priv;
155   GtkWidget *align_widget;
156   gint our_width, our_height;
157   gint entry_x, entry_y, entry_width, entry_height;
158   gint x, y;
159   GdkGravity gravity = GDK_GRAVITY_NORTH_WEST;
160   GdkWindow *gdk_window;
161   GdkDisplay *display;
162 
163   g_assert (GDICT_IS_ALIGNED_WINDOW (window));
164   priv = window->priv;
165 
166   if (!priv->align_widget)
167     return;
168 
169   align_widget = priv->align_widget;
170   gdk_window = gtk_widget_get_window (align_widget);
171 
172   display = gdk_display_get_default ();
173   gdk_display_flush (display);
174 
175   gdk_window_get_geometry (gtk_widget_get_window (GTK_WIDGET (window)), NULL, NULL, &our_width, &our_height);
176 
177   /* stick, skip taskbar and pager */
178   gtk_window_stick (GTK_WINDOW (window));
179   gtk_window_set_skip_taskbar_hint (GTK_WINDOW (window), TRUE);
180   gtk_window_set_skip_pager_hint (GTK_WINDOW (window), TRUE);
181 
182   /* make sure the align_widget is realized before we do anything */
183   gtk_widget_realize (align_widget);
184 
185   /* get the positional and dimensional attributes of the align widget */
186   gdk_window_get_origin (gdk_window,
187   			 &entry_x,
188   			 &entry_y);
189   gdk_window_get_geometry (gdk_window, NULL, NULL, &entry_width, &entry_height);
190 
191   if (entry_x + our_width < WidthOfScreen (gdk_x11_screen_get_xscreen (gdk_screen_get_default ())))
192     x = entry_x + 1;
193   else
194     {
195       x = entry_x + entry_width - our_width - 1;
196 
197       gravity = GDK_GRAVITY_NORTH_EAST;
198     }
199 
200   if (entry_y + entry_height + our_height < HeightOfScreen (gdk_x11_screen_get_xscreen (gdk_screen_get_default ())))
201     y = entry_y + entry_height - 1;
202   else
203     {
204       y = entry_y - our_height + 1;
205 
206       if (gravity == GDK_GRAVITY_NORTH_EAST)
207 	gravity = GDK_GRAVITY_SOUTH_EAST;
208       else
209 	gravity = GDK_GRAVITY_SOUTH_WEST;
210     }
211 
212   gtk_window_set_gravity (GTK_WINDOW (window), gravity);
213   gtk_window_move (GTK_WINDOW (window), x, y);
214 }
215 
216 static void
gdict_aligned_window_realize(GtkWidget * widget)217 gdict_aligned_window_realize (GtkWidget *widget)
218 {
219   GTK_WIDGET_CLASS (gdict_aligned_window_parent_class)->realize (widget);
220 
221   gdict_aligned_window_position (GDICT_ALIGNED_WINDOW (widget));
222 }
223 
224 static void
gdict_aligned_window_show(GtkWidget * widget)225 gdict_aligned_window_show (GtkWidget *widget)
226 {
227   gdict_aligned_window_position (GDICT_ALIGNED_WINDOW (widget));
228 
229   GTK_WIDGET_CLASS (gdict_aligned_window_parent_class)->show (widget);
230 }
231 
232 static void
gdict_aligned_window_finalize(GObject * object)233 gdict_aligned_window_finalize (GObject *object)
234 {
235   G_OBJECT_CLASS (gdict_aligned_window_parent_class)->finalize (object);
236 }
237 
238 static gboolean
gdict_aligned_window_motion_notify_cb(GtkWidget * widget,GdkEventMotion * event,GdictAlignedWindow * aligned_window)239 gdict_aligned_window_motion_notify_cb (GtkWidget        *widget,
240 				       GdkEventMotion   *event,
241 				       GdictAlignedWindow *aligned_window)
242 {
243   GtkAllocation alloc;
244   GdkRectangle rect;
245 
246   gtk_widget_get_allocation (GTK_WIDGET (aligned_window), &alloc);
247 
248   rect.x = 0;
249   rect.y = 0;
250   rect.width = alloc.width;
251   rect.height = alloc.height;
252 
253   gdk_window_invalidate_rect (gtk_widget_get_window (GTK_WIDGET (aligned_window)),
254 		  	      &rect,
255 			      FALSE);
256 
257   return FALSE;
258 }
259 
260 
261 /**
262  * gdict_aligned_window_new:
263  * @align_widget: a #GtkWidget to which the window should align
264  *
265  * Creates a new window, aligned to a previously created widget.
266  *
267  * Return value: a new #GdictAlignedWindow
268  */
269 GtkWidget *
gdict_aligned_window_new(GtkWidget * align_widget)270 gdict_aligned_window_new (GtkWidget *align_widget)
271 {
272   return g_object_new (GDICT_TYPE_ALIGNED_WINDOW,
273   		       "align-widget", align_widget,
274   		       NULL);
275 }
276 
277 /**
278  * gdict_aligned_window_set_widget:
279  * @aligned_window: a #GdictAlignedWindow
280  * @align_widget: the #GtkWidget @aligned_window should align to
281  *
282  * Sets @align_widget as the #GtkWidget to which @aligned_window should
283  * align.
284  *
285  * Note that @align_widget must have a #GdkWindow in order to
286  * #GdictAlignedWindow to work.
287  */
288 void
gdict_aligned_window_set_widget(GdictAlignedWindow * aligned_window,GtkWidget * align_widget)289 gdict_aligned_window_set_widget (GdictAlignedWindow *aligned_window,
290 			         GtkWidget          *align_widget)
291 {
292   GdictAlignedWindowPrivate *priv;
293 
294   g_return_if_fail (GDICT_IS_ALIGNED_WINDOW (aligned_window));
295   g_return_if_fail (GTK_IS_WIDGET (align_widget));
296 
297 #if 0
298   if (GTK_WIDGET_NO_WINDOW (align_widget))
299     {
300       g_warning ("Attempting to set a widget of class '%s' as the "
301                  "align widget, but widgets of this class does not "
302                  "have a GdkWindow.",
303                  g_type_name (G_OBJECT_TYPE (align_widget)));
304 
305       return;
306     }
307 #endif
308 
309   priv = gdict_aligned_window_get_instance_private (aligned_window);
310 
311   if (priv->align_widget)
312     {
313       g_signal_handler_disconnect (priv->align_widget, priv->motion_id);
314       priv->align_widget = NULL;
315     }
316 
317   priv->align_widget = align_widget;
318   priv->motion_id = g_signal_connect (priv->align_widget, "motion-notify-event",
319 				      G_CALLBACK (gdict_aligned_window_motion_notify_cb),
320 				      aligned_window);
321 }
322 
323 /**
324  * gdict_aligned_window_get_widget:
325  * @aligned_window: a #GdictAlignedWindow
326  *
327  * Retrieves the #GtkWidget to which @aligned_window is aligned to.
328  *
329  * Return value: the align widget.
330  */
331 GtkWidget *
gdict_aligned_window_get_widget(GdictAlignedWindow * aligned_window)332 gdict_aligned_window_get_widget (GdictAlignedWindow *aligned_window)
333 {
334   g_return_val_if_fail (GDICT_IS_ALIGNED_WINDOW (aligned_window), NULL);
335 
336   return aligned_window->priv->align_widget;
337 }
338