-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpopover-manager.c
343 lines (296 loc) · 12.3 KB
/
popover-manager.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/*
* This file is part of ui-tests
*
* Copyright © 2016-2017 Ikey Doherty <[email protected]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*/
#define _GNU_SOURCE
#include "util.h"
BUDGIE_BEGIN_PEDANTIC
#include "popover-manager.h"
#include <gtk/gtk.h>
BUDGIE_END_PEDANTIC
struct _BudgiePopoverManagerClass {
GObjectClass parent_class;
};
struct _BudgiePopoverManager {
GObject parent;
GHashTable *popovers;
BudgiePopover *active_popover;
};
G_DEFINE_TYPE(BudgiePopoverManager, budgie_popover_manager, G_TYPE_OBJECT)
static gboolean budgie_popover_manager_enter_notify(BudgiePopoverManager *manager,
GdkEventCrossing *crossing, GtkWidget *widget);
static void budgie_popover_manager_link_signals(BudgiePopoverManager *manager,
GtkWidget *parent_widget, BudgiePopover *popover);
static void budgie_popover_manager_unlink_signals(BudgiePopoverManager *manager,
GtkWidget *parent_widget, BudgiePopover *popover);
static gboolean budgie_popover_manager_coords_within_window(GtkWindow *window, gint root_x,
gint root_y);
static BudgiePopover *budgie_popover_manager_get_popover_for_coords(BudgiePopoverManager *self,
gint root_x, gint root_y);
static gboolean budgie_popover_manager_popover_mapped(BudgiePopover *popover, GdkEvent *event,
BudgiePopoverManager *self);
static gboolean budgie_popover_manager_popover_unmapped(BudgiePopover *popover, GdkEvent *event,
BudgiePopoverManager *self);
/**
* budgie_popover_manager_new:
*
* Construct a new BudgiePopoverManager object
*/
BudgiePopoverManager *budgie_popover_manager_new()
{
return g_object_new(BUDGIE_TYPE_POPOVER_MANAGER, NULL);
}
/**
* budgie_popover_manager_dispose:
*
* Clean up a BudgiePopoverManager instance
*/
static void budgie_popover_manager_dispose(GObject *obj)
{
BudgiePopoverManager *self = NULL;
self = BUDGIE_POPOVER_MANAGER(obj);
g_clear_pointer(&self->popovers, g_hash_table_unref);
G_OBJECT_CLASS(budgie_popover_manager_parent_class)->dispose(obj);
}
/**
* budgie_popover_manager_class_init:
*
* Handle class initialisation
*/
static void budgie_popover_manager_class_init(BudgiePopoverManagerClass *klazz)
{
GObjectClass *obj_class = G_OBJECT_CLASS(klazz);
/* gobject vtable hookup */
obj_class->dispose = budgie_popover_manager_dispose;
}
/**
* budgie_popover_manager_init:
*
* Handle construction of the BudgiePopoverManager
*/
static void budgie_popover_manager_init(BudgiePopoverManager *self)
{
/* We don't re-ref anything as we just effectively hold floating references
* to the WhateverTheyAres
*/
self->popovers = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);
}
void budgie_popover_manager_register_popover(BudgiePopoverManager *self, GtkWidget *parent_widget,
BudgiePopover *popover)
{
g_assert(self != NULL);
g_return_if_fail(parent_widget != NULL && popover != NULL);
if (g_hash_table_contains(self->popovers, parent_widget)) {
g_warning("register_popover(): Widget %p is already registered",
(gpointer)parent_widget);
return;
}
/* We're a popover manager, so we're meant for use in some kind of panel
* situation. Use toplevel hints for better positioning */
budgie_popover_set_position_policy(popover, BUDGIE_POPOVER_POSITION_TOPLEVEL_HINT);
/* Stick it into the map and hook it up */
budgie_popover_manager_link_signals(self, parent_widget, popover);
g_hash_table_insert(self->popovers, parent_widget, popover);
}
void budgie_popover_manager_unregister_popover(BudgiePopoverManager *self, GtkWidget *parent_widget)
{
g_assert(self != NULL);
g_return_if_fail(parent_widget != NULL);
BudgiePopover *popover = NULL;
popover = g_hash_table_lookup(self->popovers, parent_widget);
if (!popover) {
g_warning("unregister_popover(): Widget %p is unknown", (gpointer)parent_widget);
return;
}
budgie_popover_manager_unlink_signals(self, parent_widget, popover);
g_hash_table_remove(self->popovers, parent_widget);
}
/**
* Show a popover on the idle loop to prevent any weird event locks
*/
static gboolean show_one_popover(gpointer v)
{
gtk_widget_show(GTK_WIDGET(v));
return FALSE;
}
void budgie_popover_manager_show_popover(BudgiePopoverManager *self, GtkWidget *parent_widget)
{
BudgiePopover *popover = NULL;
g_assert(self != NULL);
g_return_if_fail(parent_widget != NULL);
popover = g_hash_table_lookup(self->popovers, parent_widget);
if (!popover) {
g_warning("show_popover(): Widget %p is unknown", (gpointer)parent_widget);
return;
}
g_idle_add(show_one_popover, popover);
}
/**
* The widget has died, so remove it from our internal state
*/
static void budgie_popover_manager_widget_died(BudgiePopoverManager *self, GtkWidget *child)
{
if (!g_hash_table_contains(self->popovers, child)) {
return;
}
g_hash_table_remove(self->popovers, child);
}
/**
* Hook up the various signals we need to manage this popover correctly
*/
static void budgie_popover_manager_link_signals(BudgiePopoverManager *self,
GtkWidget *parent_widget, BudgiePopover *popover)
{
/* Need enter-notify to check if we entered a parent widget */
g_signal_connect_swapped(popover,
"enter-notify-event",
G_CALLBACK(budgie_popover_manager_enter_notify),
self);
g_signal_connect_swapped(parent_widget,
"destroy",
G_CALLBACK(budgie_popover_manager_widget_died),
self);
g_signal_connect(popover,
"map-event",
G_CALLBACK(budgie_popover_manager_popover_mapped),
self);
g_signal_connect(popover,
"unmap-event",
G_CALLBACK(budgie_popover_manager_popover_unmapped),
self);
}
/**
* Disconnect any prior signals for this popover so we stop receiving events for it
*/
static void budgie_popover_manager_unlink_signals(BudgiePopoverManager *self,
GtkWidget *parent_widget, BudgiePopover *popover)
{
g_signal_handlers_disconnect_by_data(parent_widget, self);
g_signal_handlers_disconnect_by_data(popover, self);
}
/**
* Handle an enter-notify for a widget to handle roll-over selection when grabbed
*/
static gboolean budgie_popover_manager_enter_notify(BudgiePopoverManager *self,
GdkEventCrossing *crossing, GtkWidget *widget)
{
BudgiePopover *target_popover = NULL;
/* We only want to hear about the grabbed events */
if (!GTK_IS_WINDOW(widget)) {
return GDK_EVENT_PROPAGATE;
}
/* If we're inside the popover, not interested. */
if (budgie_popover_manager_coords_within_window(GTK_WINDOW(widget),
(gint)crossing->x_root,
(gint)crossing->y_root)) {
return GDK_EVENT_PROPAGATE;
}
target_popover = budgie_popover_manager_get_popover_for_coords(self,
(gint)crossing->x_root,
(gint)crossing->y_root);
if (!target_popover) {
return GDK_EVENT_PROPAGATE;
}
/* Don't show the same popover again. :P */
if (target_popover == self->active_popover) {
return GDK_EVENT_PROPAGATE;
}
if (self->active_popover) {
gtk_widget_hide(GTK_WIDGET(self->active_popover));
self->active_popover = NULL;
}
g_idle_add(show_one_popover, target_popover);
return GDK_EVENT_STOP;
}
/**
* Window specific method that determines if X,Y is currently within the
* confines of the GtkWindow. This helps us quickly determine that we've
* re-entered a BudgiePopover and don't need to find an associated window.
*/
static gboolean budgie_popover_manager_coords_within_window(GtkWindow *window, gint root_x,
gint root_y)
{
gint x, y = 0;
gint w, h = 0;
gtk_window_get_position(window, &x, &y);
gtk_window_get_size(window, &w, &h);
if ((root_x >= x && root_x <= x + w) && (root_y >= y && root_y <= y + h)) {
return TRUE;
}
return FALSE;
}
/**
* After having received an enter notify event and determining that it isn't
* a BudgiePopover that we entered, we iterate our registered widgets and try
* to find the one matching the X, Y coordinates.
*
* Upon finding a matching widget, we'll return the associated popover.
*/
static BudgiePopover *budgie_popover_manager_get_popover_for_coords(BudgiePopoverManager *self,
gint root_x, gint root_y)
{
GHashTableIter iter = { 0 };
GtkWidget *parent_widget = NULL;
BudgiePopover *assoc_popover = NULL;
g_hash_table_iter_init(&iter, self->popovers);
while (g_hash_table_iter_next(&iter, (void **)&parent_widget, (void **)&assoc_popover)) {
GtkAllocation alloc = { 0 };
GtkWidget *toplevel = NULL;
GdkWindow *toplevel_window = NULL;
gint rx, ry = 0;
gint x, y = 0;
/* Determine the parent_widget's absolute x, y on screen */
toplevel = gtk_widget_get_toplevel(parent_widget);
toplevel_window = gtk_widget_get_window(toplevel);
gdk_window_get_position(toplevel_window, &x, &y);
gtk_widget_translate_coordinates(parent_widget, toplevel, x, y, &rx, &ry);
gtk_widget_get_allocation(parent_widget, &alloc);
if ((root_x >= rx && root_x <= rx + alloc.width) &&
(root_y >= ry && root_y <= ry + alloc.height)) {
return assoc_popover;
}
}
return NULL;
}
/**
* Handle the BudgiePopover becoming visible on screen, updating our knowledge
* of who the currently active popover is
*/
static gboolean budgie_popover_manager_popover_mapped(BudgiePopover *popover,
__budgie_unused__ GdkEvent *event,
BudgiePopoverManager *self)
{
self->active_popover = popover;
return GDK_EVENT_PROPAGATE;
}
/**
* Handle the BudgiePopover becoming invisible on screen, updating our knowledge
* of who the currently active popover is
*/
static gboolean budgie_popover_manager_popover_unmapped(BudgiePopover *popover,
__budgie_unused__ GdkEvent *event,
BudgiePopoverManager *self)
{
if (popover == self->active_popover) {
self->active_popover = NULL;
}
return GDK_EVENT_PROPAGATE;
}
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 8
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=8 tabstop=8 expandtab:
* :indentSize=8:tabSize=8:noTabs=true:
*/