Move more Gtkmm2ext widgets into libwidget
[ardour.git] / libs / widgets / tabbable.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/notebook.h>
22 #include <gtkmm/window.h>
23 #include <gtkmm/stock.h>
24
25 #include "pbd/stacktrace.h"
26
27 #include "gtkmm2ext/gtk_ui.h"
28 #include "gtkmm2ext/utils.h"
29 #include "gtkmm2ext/visibility_tracker.h"
30
31 #include "widgets/tabbable.h"
32
33 #include "pbd/i18n.h"
34
35 using std::string;
36 using namespace Gtk;
37 using namespace Gtkmm2ext;
38 using namespace ArdourWidgets;
39
40 Tabbable::Tabbable (Widget& w, const string& name, bool tabbed_by_default)
41         : WindowProxy (name)
42         , _contents (w)
43         , _parent_notebook (0)
44         , tab_requested_by_state (tabbed_by_default)
45 {
46 }
47
48 Tabbable::~Tabbable ()
49 {
50         if (_window) {
51                 delete _window;
52                 _window = 0;
53         }
54 }
55
56 void
57 Tabbable::add_to_notebook (Notebook& notebook, const string& tab_title)
58 {
59         _parent_notebook = &notebook;
60
61         if (tab_requested_by_state) {
62                 attach ();
63         }
64 }
65
66 Window*
67 Tabbable::use_own_window (bool and_pack_it)
68 {
69         Gtk::Window* win = get (true);
70
71         if (and_pack_it) {
72                 Gtk::Container* parent = _contents.get_parent();
73                 if (parent) {
74                         _contents.hide ();
75                         parent->remove (_contents);
76                 }
77                 _own_notebook.append_page (_contents);
78                 _contents.show ();
79         }
80
81         return win;
82
83 }
84
85 bool
86 Tabbable::window_visible () const
87 {
88         if (!_window) {
89                 return false;
90         }
91
92         return _window->is_visible();
93 }
94
95 Window*
96 Tabbable::get (bool create)
97 {
98         if (_window) {
99                 return _window;
100         }
101
102         if (!create) {
103                 return 0;
104         }
105
106         /* From here on, we're creating the window
107          */
108
109         if ((_window = new Window (WINDOW_TOPLEVEL)) == 0) {
110                 return 0;
111         }
112
113         _window->add (_own_notebook);
114         _own_notebook.show ();
115         _own_notebook.set_show_tabs (false);
116
117         _window->signal_map().connect (sigc::mem_fun (*this, &Tabbable::window_mapped));
118         _window->signal_unmap().connect (sigc::mem_fun (*this, &Tabbable::window_unmapped));
119
120         /* do other window-related setup */
121
122         setup ();
123
124         /* window should be ready for derived classes to do something with it */
125
126         return _window;
127 }
128
129 void
130 Tabbable::show_own_window (bool and_pack_it)
131 {
132         Gtk::Widget* parent = _contents.get_parent();
133         Gtk::Allocation alloc;
134
135         if (parent) {
136                 alloc = parent->get_allocation();
137         }
138
139         (void) use_own_window (and_pack_it);
140
141         if (parent) {
142                 _window->set_default_size (alloc.get_width(), alloc.get_height());
143         }
144
145         tab_requested_by_state = false;
146
147         _window->present ();
148 }
149
150 Gtk::Notebook*
151 Tabbable::tab_root_drop ()
152 {
153         /* This is called after a drop of a tab onto the root window. Its
154          * responsibility xois to return the notebook that this Tabbable's
155          * contents should be packed into before the drop handling is
156          * completed. It is not responsible for actually taking care of this
157          * packing.
158          */
159
160         show_own_window (false);
161         return &_own_notebook;
162 }
163
164 void
165 Tabbable::show_window ()
166 {
167         make_visible ();
168
169         if (_window && (current_toplevel() == _window)) {
170                 if (!_visible) { /* was hidden, update status */
171                         set_pos_and_size ();
172                 }
173         }
174 }
175
176 /** If this Tabbable is currently parented by a tab, ensure that the tab is the
177  * current one. If it is parented by a window, then toggle the visibility of
178  * that window.
179  */
180 void
181 Tabbable::change_visibility ()
182 {
183         if (tabbed()) {
184                 _parent_notebook->set_current_page (_parent_notebook->page_num (_contents));
185                 return;
186         }
187
188         if (tab_requested_by_state) {
189                 /* should be tabbed, but currently isn't parented by a notebook */
190                 return;
191         }
192
193         if (_window && (current_toplevel() == _window)) {
194                 /* Use WindowProxy method which will rotate then hide */
195                 toggle();
196         }
197 }
198
199 void
200 Tabbable::make_visible ()
201 {
202         if (_window && (current_toplevel() == _window)) {
203                 set_pos ();
204                 _window->present ();
205         } else {
206
207                 if (!tab_requested_by_state) {
208                         show_own_window (true);
209                 } else {
210                         show_tab ();
211                 }
212         }
213 }
214
215 void
216 Tabbable::make_invisible ()
217 {
218         if (_window && (current_toplevel() == _window)) {
219                 _window->hide ();
220         } else {
221                 hide_tab ();
222         }
223 }
224
225 void
226 Tabbable::detach ()
227 {
228         show_own_window (true);
229 }
230
231 void
232 Tabbable::attach ()
233 {
234         if (!_parent_notebook) {
235                 return;
236         }
237
238         if (tabbed()) {
239                 /* already tabbed */
240                 return;
241         }
242
243
244         if (_window && current_toplevel() == _window) {
245                 /* unpack Tabbable from parent, put it back in the main tabbed
246                  * notebook
247                  */
248
249                 save_pos_and_size ();
250
251                 _contents.hide ();
252                 _contents.get_parent()->remove (_contents);
253
254                 /* leave the window around */
255
256                 _window->hide ();
257         }
258
259         _parent_notebook->append_page (_contents);
260         _parent_notebook->set_tab_detachable (_contents);
261         _parent_notebook->set_tab_reorderable (_contents);
262         _parent_notebook->set_current_page (_parent_notebook->page_num (_contents));
263         _contents.show ();
264
265         /* have to force this on, which is semantically correct, since
266          * the user has effectively asked for it.
267          */
268
269         tab_requested_by_state = true;
270         StateChange (*this);
271 }
272
273 bool
274 Tabbable::delete_event_handler (GdkEventAny *ev)
275 {
276         _window->hide();
277
278         return true;
279 }
280
281 bool
282 Tabbable::tabbed () const
283 {
284         if (_window && (current_toplevel() == _window)) {
285                 return false;
286         }
287
288         if (_parent_notebook && (_parent_notebook->page_num (_contents) >= 0)) {
289                 return true;
290         }
291
292         return false;
293 }
294
295 void
296 Tabbable::hide_tab ()
297 {
298         if (tabbed()) {
299                 _contents.hide();
300                 _parent_notebook->remove_page (_contents);
301                 StateChange (*this);
302         }
303 }
304
305 void
306 Tabbable::show_tab ()
307 {
308         if (!window_visible() && _parent_notebook) {
309                 if (_contents.get_parent() == 0) {
310                         tab_requested_by_state = true;
311                         add_to_notebook (*_parent_notebook, _tab_title);
312                 }
313                 _parent_notebook->set_current_page (_parent_notebook->page_num (_contents));
314                 _contents.show ();
315                 current_toplevel()->present ();
316         }
317 }
318
319 Gtk::Window*
320 Tabbable::current_toplevel () const
321 {
322         return dynamic_cast<Gtk::Window*> (contents().get_toplevel());
323 }
324
325 string
326 Tabbable::xml_node_name()
327 {
328         return WindowProxy::xml_node_name();
329 }
330
331 bool
332 Tabbable::tabbed_by_default() const
333 {
334         return tab_requested_by_state;
335 }
336
337 XMLNode&
338 Tabbable::get_state()
339 {
340         XMLNode& node (WindowProxy::get_state());
341
342         node.set_property (X_("tabbed"),  tabbed());
343
344         return node;
345 }
346
347 int
348 Tabbable::set_state (const XMLNode& node, int version)
349 {
350         int ret;
351
352         if ((ret = WindowProxy::set_state (node, version)) != 0) {
353                 return ret;
354         }
355
356         if (_visible) {
357                 show_own_window (true);
358         }
359
360         XMLNodeList children = node.children ();
361         XMLNode* window_node = node.child ("Window");
362
363         if (window_node) {
364                 window_node->get_property (X_("tabbed"), tab_requested_by_state);
365         }
366
367         if (!_visible) {
368                 if (tab_requested_by_state) {
369                         attach ();
370                 } else {
371                         /* this does nothing if not tabbed */
372                         hide_tab ();
373                 }
374         }
375
376         return ret;
377 }
378
379 void
380 Tabbable::window_mapped ()
381 {
382         StateChange (*this);
383 }
384
385 void
386 Tabbable::window_unmapped ()
387 {
388         StateChange (*this);
389 }