Merge remote-tracking branch 'origin/master' into export-dialog
[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         delete _window;
198 }
199
200 void
201 ProxyBase::set_state (const XMLNode& node)
202 {
203         XMLNodeList children = node.children ();
204
205         XMLNodeList::const_iterator i = children.begin ();
206
207         while (i != children.end()) {
208                 XMLProperty* prop = (*i)->property (X_("name"));
209                 if ((*i)->name() == X_("Window") && prop && prop->value() == _name) {
210                         break;
211                 }
212
213                 ++i;
214         }
215
216         if (i != children.end()) {
217
218                 XMLProperty* prop;
219
220                 if ((prop = (*i)->property (X_("visible"))) != 0) {
221                         _visible = PBD::string_is_affirmative (prop->value ());
222                 }
223
224                 if ((prop = (*i)->property (X_("x-off"))) != 0) {
225                         _x_off = atoi (prop->value());
226                 }
227                 if ((prop = (*i)->property (X_("y-off"))) != 0) {
228                         _y_off = atoi (prop->value());
229                 }
230                 if ((prop = (*i)->property (X_("x-size"))) != 0) {
231                         _width = atoi (prop->value());
232                 }
233                 if ((prop = (*i)->property (X_("y-size"))) != 0) {
234                         _height = atoi (prop->value());
235                 }
236         }
237
238         /* if we have a window already, reset its properties */
239
240         if (_window) {
241                 setup ();
242         }
243 }
244
245 void
246 ProxyBase::set_action (Glib::RefPtr<Gtk::Action> act)
247 {
248         _action = act;
249 }
250
251 std::string
252 ProxyBase::action_name() const 
253 {
254         return string_compose (X_("toggle-%1"), _name);
255 }
256
257 void
258 ProxyBase::toggle() 
259 {
260         if (!_window) {
261                 (void) get (true);
262                 assert (_window);
263                 /* XXX this is a hack - the window object should really
264                    ensure its components are all visible. sigh.
265                 */
266                 _window->show_all();
267                 /* we'd like to just call this and nothing else */
268                 _window->present ();
269
270                 if (_width != -1 && _height != -1) {
271                         _window->set_default_size (_width, _height);
272                 }
273                 if (_x_off != -1 && _y_off != -1) {
274                         _window->move (_x_off, _y_off);
275                 }
276
277         } else {
278                 if (_window->is_mapped()) {
279                         save_pos_and_size();
280                 }
281                 vistracker->cycle_visibility ();
282                 if (_window->is_mapped()) {
283                         if (_width != -1 && _height != -1) {
284                                 _window->set_default_size (_width, _height);
285                         }
286                         if (_x_off != -1 && _y_off != -1) {
287                                 _window->move (_x_off, _y_off);
288                         }
289                 }
290         }
291 }
292
293 XMLNode&
294 ProxyBase::get_state () const
295 {
296         XMLNode* node = new XMLNode (X_("Window"));
297         char buf[32];   
298
299         node->add_property (X_("name"), _name);
300
301         if (_window && vistracker) {
302                 
303                 /* we have a window, so use current state */
304
305                 _visible = vistracker->partially_visible ();
306                 if (_visible) {
307                         _window->get_position (_x_off, _y_off);
308                         _window->get_size (_width, _height);
309                 }
310         }
311
312         node->add_property (X_("visible"), _visible? X_("yes") : X_("no"));
313         
314         snprintf (buf, sizeof (buf), "%d", _x_off);
315         node->add_property (X_("x-off"), buf);
316         snprintf (buf, sizeof (buf), "%d", _y_off);
317         node->add_property (X_("y-off"), buf);
318         snprintf (buf, sizeof (buf), "%d", _width);
319         node->add_property (X_("x-size"), buf);
320         snprintf (buf, sizeof (buf), "%d", _height);
321         node->add_property (X_("y-size"), buf);
322
323         return *node;
324 }
325
326 void
327 ProxyBase::drop_window ()
328 {
329         if (_window) {
330                 _window->hide ();
331                 delete _window;
332                 _window = 0;
333                 delete vistracker;
334                 vistracker = 0;
335         }
336 }
337
338 void
339 ProxyBase::use_window (Gtk::Window& win)
340 {
341         drop_window ();
342         _window = &win;
343         setup ();
344 }
345
346 void
347 ProxyBase::setup ()
348 {
349         assert (_window);
350
351         vistracker = new Gtkmm2ext::VisibilityTracker (*_window);
352         _window->signal_delete_event().connect (sigc::mem_fun (*this, &ProxyBase::delete_event_handler));
353
354         if (_width != -1 || _height != -1 || _x_off != -1 || _y_off != -1) {
355                 /* cancel any mouse-based positioning */
356                 _window->set_position (Gtk::WIN_POS_NONE);
357         }
358
359         if (_width != -1 && _height != -1) {
360                 _window->set_default_size (_width, _height);
361         }
362
363         if (_x_off != -1 && _y_off != -1) {
364                 _window->move (_x_off, _y_off);
365         }
366         set_session(_session);
367 }
368         
369 void
370 ProxyBase::show ()
371 {
372         get (true);
373         assert (_window);
374         _window->show ();
375 }
376
377 void
378 ProxyBase::maybe_show ()
379 {
380         if (_visible) {
381                 show ();
382         }
383 }
384
385 void
386 ProxyBase::show_all ()
387 {
388         get (true);
389         assert (_window);
390         _window->show_all ();
391 }
392
393 void
394 ProxyBase::present ()
395 {
396         get (true);
397         assert (_window);
398
399         _window->show_all ();
400         _window->present ();
401
402         /* turn off any mouse-based positioning */
403         _window->set_position (Gtk::WIN_POS_NONE);
404 }
405
406 void
407 ProxyBase::hide ()
408 {
409         if (_window) {
410                 save_pos_and_size();
411                 _window->hide ();
412         }
413 }
414
415 bool
416 ProxyBase::delete_event_handler (GdkEventAny* /*ev*/)
417 {
418         hide();
419         return true;
420 }
421
422 void
423 ProxyBase::save_pos_and_size ()
424 {
425         if (_window) {
426                 _window->get_position (_x_off, _y_off);
427                 _window->get_size (_width, _height);
428         }
429 }
430 /*-----------------------*/
431
432 ProxyTemporary::ProxyTemporary (const string& name, Gtk::Window* win)
433         : ProxyBase (name, string())
434 {
435         _window = win;
436 }
437
438 ProxyTemporary::~ProxyTemporary ()
439 {
440 }
441
442
443 ARDOUR::SessionHandlePtr*
444 ProxyTemporary::session_handle()
445 {
446         /* may return null */
447         ArdourWindow* aw = dynamic_cast<ArdourWindow*> (_window);
448         if (aw) { return aw; }
449         ArdourDialog* ad = dynamic_cast<ArdourDialog*> (_window);
450         if (ad) { return ad; }
451         return 0;
452 }