Add a EventBox wrapper to forward unmap events.
[ardour.git] / libs / gtkmm2ext / window_proxy.cc
1 /*
2     Copyright (C) 2015 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <gtkmm/action.h>
21 #include <gtkmm/window.h>
22
23 #include "pbd/convert.h"
24 #include "pbd/xml++.h"
25 #include "pbd/stacktrace.h"
26
27 #include "gtkmm2ext/window_proxy.h"
28 #include "gtkmm2ext/visibility_tracker.h"
29
30 #include "pbd/i18n.h"
31
32 using namespace Gtk;
33 using namespace Gtkmm2ext;
34 using namespace PBD;
35
36 WindowProxy::WindowProxy (const std::string& name)
37         : _name (name)
38         , _window (0)
39         , _visible (false)
40         , _x_off (-1)
41         , _y_off (-1)
42         , _width (-1)
43         , _height (-1)
44         , vistracker (0)
45         , _state_mask (StateMask (Position|Size))
46 {
47 }
48
49 WindowProxy::WindowProxy (const std::string& name, const std::string& menu_name)
50         : _name (name)
51         , _menu_name (menu_name)
52         , _window (0)
53         , _visible (false)
54         , _x_off (-1)
55         , _y_off (-1)
56         , _width (-1)
57         , _height (-1)
58         , vistracker (0)
59         , _state_mask (StateMask (Position|Size))
60 {
61 }
62
63 WindowProxy::WindowProxy (const std::string& name, const std::string& menu_name, const XMLNode& node)
64         : _name (name)
65         , _menu_name (menu_name)
66         , _window (0)
67         , _visible (false)
68         , _x_off (-1)
69         , _y_off (-1)
70         , _width (-1)
71         , _height (-1)
72         , vistracker (0)
73 {
74         set_state (node, 0);
75 }
76
77 WindowProxy::~WindowProxy ()
78 {
79         delete vistracker;
80         delete _window;
81 }
82
83 int
84 WindowProxy::set_state (const XMLNode& node, int /* version */)
85 {
86         XMLNodeList children = node.children ();
87         XMLNode const * child;
88         XMLNodeList::const_iterator i = children.begin ();
89
90         while (i != children.end()) {
91                 child = *i;
92                 XMLProperty const * prop = child->property (X_("name"));
93                 if (child->name() == X_("Window") && prop && prop->value() == _name) {
94                         break;
95                 }
96
97                 ++i;
98         }
99
100         if (i != children.end()) {
101
102                 XMLProperty const * prop;
103                 child = *i;
104
105                 if ((prop = child->property (X_("visible"))) != 0) {
106                         _visible = PBD::string_is_affirmative (prop->value ());
107                 }
108
109                 if ((prop = child->property (X_("x-off"))) != 0) {
110                         _x_off = atoi (prop->value());
111                 }
112                 if ((prop = child->property (X_("y-off"))) != 0) {
113                         _y_off = atoi (prop->value());
114                 }
115                 if ((prop = child->property (X_("x-size"))) != 0) {
116                         _width = atoi (prop->value());
117                 }
118                 if ((prop = child->property (X_("y-size"))) != 0) {
119                         _height = atoi (prop->value());
120                 }
121         }
122
123         if (_window) {
124                 setup ();
125         }
126
127         return 0;
128 }
129
130 void
131 WindowProxy::set_action (Glib::RefPtr<Gtk::Action> act)
132 {
133         _action = act;
134 }
135
136 std::string
137 WindowProxy::action_name() const
138 {
139         return string_compose (X_("toggle-%1"), _name);
140 }
141
142 void
143 WindowProxy::toggle()
144 {
145         if (!_window) {
146                 (void) get (true);
147                 setup ();
148                 assert (_window);
149                 /* XXX this is a hack - the window object should really
150                    ensure its components are all visible. sigh.
151                 */
152                 _window->show_all();
153                 /* we'd like to just call this and nothing else */
154                 _window->present ();
155         } else {
156                 if (_window->is_mapped()) {
157                         save_pos_and_size();
158                 }
159
160                 vistracker->cycle_visibility ();
161
162                 if (_window->is_mapped()) {
163                         if (_width != -1 && _height != -1) {
164                                 _window->set_default_size (_width, _height);
165                         }
166                         if (_x_off != -1 && _y_off != -1) {
167                                 _window->move (_x_off, _y_off);
168                         }
169                 }
170         }
171 }
172
173 std::string
174 WindowProxy::xml_node_name()
175 {
176         return X_("Window");
177 }
178
179 XMLNode&
180 WindowProxy::get_state ()
181 {
182         XMLNode* node = new XMLNode (xml_node_name());
183         char buf[32];
184
185         node->add_property (X_("name"), _name);
186
187         if (_window && vistracker) {
188
189                 /* we have a window, so use current state */
190
191                 _visible = vistracker->partially_visible ();
192                 _window->get_position (_x_off, _y_off);
193                 _window->get_size (_width, _height);
194         }
195
196         int x, y, w, h;
197
198         if (_state_mask & Position) {
199                 x = _x_off;
200                 y = _y_off;
201         } else {
202                 x = -1;
203                 y = -1;
204         }
205
206         if (_state_mask & Size) {
207                 w = _width;
208                 h = _height;
209         } else {
210                 w = -1;
211                 h = -1;
212         }
213
214         node->add_property (X_("visible"), _visible? X_("yes") : X_("no"));
215         snprintf (buf, sizeof (buf), "%d", x);
216         node->add_property (X_("x-off"), buf);
217         snprintf (buf, sizeof (buf), "%d", y);
218         node->add_property (X_("y-off"), buf);
219         snprintf (buf, sizeof (buf), "%d", w);
220         node->add_property (X_("x-size"), buf);
221         snprintf (buf, sizeof (buf), "%d", h);
222         node->add_property (X_("y-size"), buf);
223
224         return *node;
225 }
226
227 void
228 WindowProxy::drop_window ()
229 {
230         if (_window) {
231                 delete_connection.disconnect ();
232                 configure_connection.disconnect ();
233                 map_connection.disconnect ();
234                 unmap_connection.disconnect ();
235                 _window->hide ();
236                 delete _window;
237                 _window = 0;
238                 delete vistracker;
239                 vistracker = 0;
240         }
241 }
242
243 void
244 WindowProxy::use_window (Gtk::Window& win)
245 {
246         drop_window ();
247         _window = &win;
248         setup ();
249 }
250
251 void
252 WindowProxy::setup ()
253 {
254         assert (_window);
255
256         vistracker = new Gtkmm2ext::VisibilityTracker (*_window);
257
258         delete_connection = _window->signal_delete_event().connect (sigc::mem_fun (*this, &WindowProxy::delete_event_handler));
259         configure_connection = _window->signal_configure_event().connect (sigc::mem_fun (*this, &WindowProxy::configure_handler), false);
260         map_connection = _window->signal_map().connect (sigc::mem_fun (*this, &WindowProxy::map_handler), false);
261         unmap_connection = _window->signal_unmap().connect (sigc::mem_fun (*this, &WindowProxy::unmap_handler), false);
262
263         set_pos_and_size ();
264 }
265
266 void
267 WindowProxy::map_handler ()
268 {
269         /* emit our own signal */
270         signal_map ();
271 }
272
273 void
274 WindowProxy::unmap_handler ()
275 {
276         /* emit out own signal */
277         signal_unmap ();
278 }
279
280 bool
281 WindowProxy::configure_handler (GdkEventConfigure* ev)
282 {
283         /* stupidly, the geometry data in the event isn't the same as we get
284            from the window geometry APIs.so we have to actively interrogate
285            them to get the new information.
286
287            the difference is generally down to window manager framing.
288         */
289         if (!visible() || !_window->is_mapped()) {
290                 return false;
291         }
292         save_pos_and_size ();
293         return false;
294 }
295
296
297 bool
298 WindowProxy::visible() const
299 {
300         if (vistracker) {
301                 /* update with current state */
302                 _visible = vistracker->partially_visible();
303         }
304         return _visible;
305 }
306
307 bool
308 WindowProxy::fully_visible () const
309 {
310         if (!vistracker) {
311                 /* no vistracker .. no window .. cannot be fully visible */
312                 return false;
313         }
314         return vistracker->fully_visible();
315 }
316
317 void
318 WindowProxy::show ()
319 {
320         get (true);
321         assert (_window);
322         _window->show ();
323 }
324
325 void
326 WindowProxy::maybe_show ()
327 {
328         if (_visible) {
329                 show ();
330         }
331 }
332
333 void
334 WindowProxy::show_all ()
335 {
336         get (true);
337         assert (_window);
338         _window->show_all ();
339 }
340
341 void
342 WindowProxy::present ()
343 {
344         get (true);
345         assert (_window);
346
347         _window->show_all ();
348         _window->present ();
349
350         /* turn off any mouse-based positioning */
351         _window->set_position (Gtk::WIN_POS_NONE);
352 }
353
354 void
355 WindowProxy::hide ()
356 {
357         if (_window) {
358                 save_pos_and_size();
359                 _window->hide ();
360         }
361 }
362
363 bool
364 WindowProxy::delete_event_handler (GdkEventAny* /*ev*/)
365 {
366         if (_action) {
367                 _action->activate ();
368         } else {
369                 hide();
370         }
371
372         return true;
373 }
374
375 void
376 WindowProxy::save_pos_and_size ()
377 {
378         if (_window) {
379                 _window->get_position (_x_off, _y_off);
380                 _window->get_size (_width, _height);
381         }
382 }
383
384 void
385 WindowProxy::set_pos_and_size ()
386 {
387         if (!_window) {
388                 return;
389         }
390
391         if ((_state_mask & Position) && (_width != -1 || _height != -1 || _x_off != -1 || _y_off != -1)) {
392                 /* cancel any mouse-based positioning */
393                 _window->set_position (Gtk::WIN_POS_NONE);
394         }
395
396         if ((_state_mask & Size) && _width != -1 && _height != -1) {
397                 _window->resize (_width, _height);
398         }
399
400         if ((_state_mask & Position) && _x_off != -1 && _y_off != -1) {
401                 _window->move (_x_off, _y_off);
402         }
403 }
404
405 void
406 WindowProxy::set_pos ()
407 {
408         if (!_window) {
409                 return;
410         }
411
412         if (!(_state_mask & Position)) {
413                 return;
414         }
415
416         if (_width != -1 || _height != -1 || _x_off != -1 || _y_off != -1) {
417                 /* cancel any mouse-based positioning */
418                 _window->set_position (Gtk::WIN_POS_NONE);
419         }
420
421         if (_x_off != -1 && _y_off != -1) {
422                 _window->move (_x_off, _y_off);
423         }
424 }
425
426 void
427 WindowProxy::set_state_mask (StateMask sm)
428 {
429         _state_mask = sm;
430 }