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