make window-manager session-aware
[ardour.git] / gtk2_ardour / window_manager.cc
1 /*
2     Copyright (C) 2013 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 #include <gtkmm/window.h>
20
21 #include "pbd/xml++.h"
22
23 #include "ardour/session_handle.h"
24
25 #include "gtkmm2ext/visibility_tracker.h"
26
27 #include "actions.h"
28 #include "ardour_dialog.h"
29 #include "ardour_window.h"
30 #include "window_manager.h"
31 #include "processor_box.h"
32
33 #include "i18n.h"
34
35 using std::string;
36 using namespace WM;
37 using namespace PBD;
38
39 Manager* Manager::_instance = 0;
40
41 Manager&
42 Manager::instance ()
43 {
44         if (!_instance) {
45                 _instance = new Manager;
46         }
47         return *_instance;
48 }
49
50 Manager::Manager ()
51         : current_transient_parent (0)
52 {
53 }
54
55 Manager::~Manager ()
56 {
57 }
58
59 void
60 Manager::register_window (ProxyBase* info)
61 {
62         _windows.push_back (info);
63
64         if (!info->menu_name().empty()) {
65
66                 if (!window_actions) {
67                         window_actions = Gtk::ActionGroup::create (X_("Window"));
68                         ActionManager::add_action_group (window_actions);
69                 }
70
71                 info->set_action (ActionManager::register_action (window_actions, info->action_name().c_str(), info->menu_name().c_str(), 
72                                                                   sigc::bind (sigc::mem_fun (*this, &Manager::toggle_window), info)));
73         }
74 }
75
76 void
77 Manager::remove (const ProxyBase* info)
78 {
79         for (Windows::iterator i = _windows.begin(); i != _windows.end(); ++i) {
80                 if ((*i) == info) {
81                         _windows.erase (i);
82                         return;
83                 }
84         }
85 }
86
87 void
88 Manager::toggle_window (ProxyBase* proxy)
89 {
90         if (proxy) {
91                 proxy->toggle ();
92         }
93 }
94
95 void
96 Manager::show_visible() const
97 {
98         for (Windows::const_iterator i = _windows.begin(); i != _windows.end(); ++i) {
99                 if ((*i)->visible()) {
100                         (*i)->show_all ();
101                         (*i)->present ();
102                 }
103         }
104 }
105
106 void
107 Manager::add_state (XMLNode& root) const
108 {
109         for (Windows::const_iterator i = _windows.begin(); i != _windows.end(); ++i) {
110                 /* don't save state for temporary proxy windows
111                  */
112                 if (dynamic_cast<ProxyTemporary*> (*i)) {
113                         continue;
114                 }
115                 if (dynamic_cast<ProcessorWindowProxy*> (*i)) {
116                         ProcessorWindowProxy *pi = dynamic_cast<ProcessorWindowProxy*> (*i);
117                         root.add_child_nocopy (pi->get_state());
118                 } else {
119                         root.add_child_nocopy ((*i)->get_state());
120                 }
121         }
122 }
123
124 void
125 Manager::set_session (ARDOUR::Session* s)
126 {
127         SessionHandlePtr::set_session (s);
128         for (Windows::const_iterator i = _windows.begin(); i != _windows.end(); ++i) {
129                 (*i)->set_session(s);
130         }
131 }
132
133 void
134 Manager::set_transient_for (Gtk::Window* parent)
135 {
136         /* OS X has a richer concept of window layering than X does (or
137          * certainly, than any accepted conventions on X), and so the use of
138          * Manager::set_transient_for() is not necessary on that platform.
139          * 
140          * On OS X this is mostly taken care of by using the window type rather
141          * than explicit 1:1 transient-for relationships.
142          */
143
144 #ifndef __APPLE__
145         if (parent) {
146                 for (Windows::const_iterator i = _windows.begin(); i != _windows.end(); ++i) {
147                         Gtk::Window* win = (*i)->get();
148                         if (win) {
149                                 win->set_transient_for (*parent);
150                         }
151                 }
152         } else {
153                 for (Windows::const_iterator i = _windows.begin(); i != _windows.end(); ++i) {
154                         Gtk::Window* win = (*i)->get();
155                         if (win) {
156                                 gtk_window_set_transient_for (win->gobj(), 0);
157                         }
158                 }
159         }
160         
161         current_transient_parent = parent;
162 #endif
163 }
164
165 /*-------------------------*/
166
167 ProxyBase::ProxyBase (const string& name, const std::string& menu_name)
168         : _name (name)
169         , _menu_name (menu_name)
170         , _window (0)
171         , _visible (false)
172         , _x_off (-1)
173         , _y_off (-1)
174         , _width (-1)
175         , _height (-1) 
176         , vistracker (0)
177 {
178 }
179
180 ProxyBase::ProxyBase (const string& name, const std::string& menu_name, const XMLNode& node)
181         : _name (name)
182         , _menu_name (menu_name)
183         , _window (0)
184         , _visible (false)
185         , _x_off (-1)
186         , _y_off (-1)
187         , _width (-1)
188         , _height (-1) 
189         , vistracker (0)
190 {
191         set_state (node);
192 }
193
194 ProxyBase::~ProxyBase ()
195 {
196         delete vistracker;
197 }
198
199 void
200 ProxyBase::set_state (const XMLNode& node)
201 {
202         XMLNodeList children = node.children ();
203
204         XMLNodeList::const_iterator i = children.begin ();
205
206         while (i != children.end()) {
207                 XMLProperty* prop = (*i)->property (X_("name"));
208                 if ((*i)->name() == X_("Window") && prop && prop->value() == _name) {
209                         break;
210                 }
211
212                 ++i;
213         }
214
215         if (i != children.end()) {
216
217                 XMLProperty* prop;
218
219                 if ((prop = (*i)->property (X_("visible"))) != 0) {
220                         _visible = PBD::string_is_affirmative (prop->value ());
221                 }
222
223                 if ((prop = (*i)->property (X_("x-off"))) != 0) {
224                         _x_off = atoi (prop->value());
225                 }
226                 if ((prop = (*i)->property (X_("y-off"))) != 0) {
227                         _y_off = atoi (prop->value());
228                 }
229                 if ((prop = (*i)->property (X_("x-size"))) != 0) {
230                         _width = atoi (prop->value());
231                 }
232                 if ((prop = (*i)->property (X_("y-size"))) != 0) {
233                         _height = atoi (prop->value());
234                 }
235         }
236
237         /* if we have a window already, reset its properties */
238
239         if (_window) {
240                 setup ();
241         }
242 }
243
244 void
245 ProxyBase::set_action (Glib::RefPtr<Gtk::Action> act)
246 {
247         _action = act;
248 }
249
250 std::string
251 ProxyBase::action_name() const 
252 {
253         return string_compose (X_("toggle-%1"), _name);
254 }
255
256 void
257 ProxyBase::toggle() 
258 {
259         if (!_window) {
260                 (void) get (true);
261                 assert (_window);
262                 /* XXX this is a hack - the window object should really
263                    ensure its components are all visible. sigh.
264                 */
265                 _window->show_all();
266                 /* we'd like to just call this and nothing else */
267                 _window->present ();
268
269                 if (_width != -1 && _height != -1) {
270                         _window->set_default_size (_width, _height);
271                 }
272                 if (_x_off != -1 && _y_off != -1) {
273                         _window->move (_x_off, _y_off);
274                 }
275
276         } else {
277                 if (_window->is_mapped()) {
278                         save_pos_and_size();
279                 }
280                 vistracker->cycle_visibility ();
281                 if (_window->is_mapped()) {
282                         if (_width != -1 && _height != -1) {
283                                 _window->set_default_size (_width, _height);
284                         }
285                         if (_x_off != -1 && _y_off != -1) {
286                                 _window->move (_x_off, _y_off);
287                         }
288                 }
289         }
290 }
291
292 XMLNode&
293 ProxyBase::get_state () const
294 {
295         XMLNode* node = new XMLNode (X_("Window"));
296         char buf[32];   
297
298         node->add_property (X_("name"), _name);
299
300         if (_window && vistracker) {
301                 
302                 /* we have a window, so use current state */
303
304                 _visible = vistracker->partially_visible ();
305                 _window->get_position (_x_off, _y_off);
306                 _window->get_size (_width, _height);
307         }
308
309         node->add_property (X_("visible"), _visible? X_("yes") : X_("no"));
310         
311         snprintf (buf, sizeof (buf), "%d", _x_off);
312         node->add_property (X_("x-off"), buf);
313         snprintf (buf, sizeof (buf), "%d", _y_off);
314         node->add_property (X_("y-off"), buf);
315         snprintf (buf, sizeof (buf), "%d", _width);
316         node->add_property (X_("x-size"), buf);
317         snprintf (buf, sizeof (buf), "%d", _height);
318         node->add_property (X_("y-size"), buf);
319
320         return *node;
321 }
322
323 void
324 ProxyBase::drop_window ()
325 {
326         if (_window) {
327                 _window->hide ();
328                 delete _window;
329                 _window = 0;
330                 delete vistracker;
331                 vistracker = 0;
332         }
333 }
334
335 void
336 ProxyBase::use_window (Gtk::Window& win)
337 {
338         drop_window ();
339         _window = &win;
340         setup ();
341 }
342
343 void
344 ProxyBase::setup ()
345 {
346         assert (_window);
347
348         vistracker = new Gtkmm2ext::VisibilityTracker (*_window);
349         _window->signal_delete_event().connect (sigc::mem_fun (*this, &ProxyBase::handle_win_event));
350
351         if (_width != -1 || _height != -1 || _x_off != -1 || _y_off != -1) {
352                 /* cancel any mouse-based positioning */
353                 _window->set_position (Gtk::WIN_POS_NONE);
354         }
355
356         if (_width != -1 && _height != -1) {
357                 _window->set_default_size (_width, _height);
358         }
359
360         if (_x_off != -1 && _y_off != -1) {
361                 _window->move (_x_off, _y_off);
362         }
363         set_session(_session);
364 }
365         
366 void
367 ProxyBase::show ()
368 {
369         Gtk::Window* win = get (true);
370         win->show ();
371 }
372
373 void
374 ProxyBase::maybe_show ()
375 {
376         if (_visible) {
377                 show ();
378         }
379 }
380
381 void
382 ProxyBase::show_all ()
383 {
384         Gtk::Window* win = get (true);
385         win->show_all ();
386 }
387
388
389 void
390 ProxyBase::present ()
391 {
392         Gtk::Window* win = get (true);
393         win->show_all ();
394         win->present ();
395
396         /* turn off any mouse-based positioning */
397         _window->set_position (Gtk::WIN_POS_NONE);
398 }
399
400 void
401 ProxyBase::hide ()
402 {
403         Gtk::Window* win = get (false);
404         if (win) {
405                 save_pos_and_size();
406                 win->hide ();
407         }
408 }
409
410 bool
411 ProxyBase::handle_win_event (GdkEventAny *ev)
412 {
413         save_pos_and_size();
414         return 0;
415 }
416
417 void
418 ProxyBase::save_pos_and_size ()
419 {
420         _window->get_position (_x_off, _y_off);
421         _window->get_size (_width, _height);
422 }
423 /*-----------------------*/
424
425 ProxyTemporary::ProxyTemporary (const string& name, Gtk::Window* win)
426         : ProxyBase (name, string())
427 {
428         _window = win;
429 }
430
431 ProxyTemporary::~ProxyTemporary ()
432 {
433 }
434
435
436 ARDOUR::SessionHandlePtr*
437 ProxyTemporary::session_handle()
438 {
439         /* may return null */
440         ArdourWindow* aw = dynamic_cast<ArdourWindow*> (_window);
441         if (aw) { return aw; }
442         ArdourDialog* ad = dynamic_cast<ArdourDialog*> (_window);
443         if (ad) { return ad; }
444         return 0;
445 }