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