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