add tab-closing buttons to tabs.
[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/visibility_tracker.h"
28
29 #include "i18n.h"
30
31 using namespace Gtkmm2ext;
32 using namespace Gtk;
33 using std::string;
34
35 Tabbable::Tabbable (Widget& w, const string& name)
36         : WindowProxy (name)
37         , _contents (w)
38         , tab_close_image (Stock::CLOSE, ICON_SIZE_BUTTON)
39 {
40         _tab_box.pack_start (_tab_label, true, true);
41         _tab_box.pack_start (_tab_close_button, false, false);
42         _tab_close_button.add (tab_close_image);
43
44         _tab_close_button.signal_clicked().connect (sigc::mem_fun (*this, &Tabbable::tab_close_clicked));
45 }
46
47 Tabbable::~Tabbable ()
48 {
49         if (_window) {
50                 delete _window;
51                 _window = 0;
52         }
53 }
54
55 void
56 Tabbable::tab_close_clicked ()
57 {
58         /* for this to happen, the tab must be visible so we
59            can assume that the contents are displayed in the
60            parent notebook
61         */
62         
63         if (_parent_notebook) {
64                 _parent_notebook->remove_page (_contents);
65         }
66 }
67
68 void
69 Tabbable::add_to_notebook (Notebook& notebook, const string& tab_title)
70 {
71         _tab_label.set_text (tab_title);
72         _tab_box.show_all ();
73         
74         notebook.append_page (_contents, _tab_box);
75
76         Gtkmm2ext::UI::instance()->set_tip (_tab_label,
77                                             string_compose (_("Drag this tab to the desktop to show %1 in its own window\n\n"
78                                                               "To put the window back, click on its \"close\" button"), tab_title));
79         
80         notebook.set_tab_detachable (_contents);
81         notebook.set_tab_reorderable (_contents);
82
83         _parent_notebook = &notebook;
84         _tab_title = tab_title;
85 }
86
87 Window*
88 Tabbable::use_own_window (bool and_pack_it)
89 {
90         Gtk::Window* win = get (true);
91
92         if (and_pack_it) {
93                 Gtk::Container* parent = _contents.get_parent();
94                 if (parent) {
95                         parent->remove (_contents);
96                 }
97                 _own_notebook.append_page (_contents, _tab_box);
98         }
99
100         return win;
101
102 }
103
104 bool
105 Tabbable::window_visible ()
106 {
107         if (!own_window()) {
108                 return false;
109         }
110
111         return visible();
112 }
113
114 Window*
115 Tabbable::get (bool create)
116 {
117         if (_window) {
118                 return _window;
119         }
120
121         if (!create) {
122                 return 0;
123         }
124
125         /* From here on, we're creating the window 
126          */
127         
128         if ((_window = new Window (WINDOW_TOPLEVEL)) == 0) {
129                 return 0;
130         }
131
132         _window->add (_own_notebook);
133         _own_notebook.show ();
134         _own_notebook.set_show_tabs (false);
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 Gtk::Notebook*
146 Tabbable::tab_root_drop ()
147 {
148         Gtk::Allocation alloc;
149
150         alloc = _contents.get_parent()->get_allocation();
151         
152         (void) use_own_window (false);
153         
154         /* This is called after a drop of a tab onto the root window. Its
155          * responsibility is to return the notebook that this Tabbable's
156          * contents should be packed into before the drop handling is
157          * completed. It is not responsible for actually taking care of this
158          * packing.
159          */
160
161         _window->set_default_size (alloc.get_width(), alloc.get_height());
162         _window->show_all ();
163         _window->present ();
164
165         return &_own_notebook;
166 }
167
168 void
169 Tabbable::show_window ()
170 {
171         make_visible ();
172
173         if (_window && (current_toplevel() == _window)) {
174                 if (!_visible) { /* was hidden, update status */
175                         set_pos_and_size ();
176                 }
177         }
178 }
179
180 bool
181 Tabbable::delete_event_handler (GdkEventAny *ev)
182 {
183         Window* toplevel = dynamic_cast<Window*> (_contents.get_toplevel());
184
185         if (_window == toplevel) {
186
187                 /* unpack Tabbable from parent, put it back in the main tabbed
188                  * notebook
189                  */
190
191                 save_pos_and_size ();
192
193                 _contents.get_parent()->remove (_contents);
194
195                 /* leave the window around */
196
197                 _window->hide ();
198                 
199                 if (_parent_notebook) {
200
201                         _parent_notebook->append_page (_contents, _tab_box);
202                         _parent_notebook->set_tab_detachable (_contents);
203                         _parent_notebook->set_tab_reorderable (_contents);
204                         _parent_notebook->set_current_page (_parent_notebook->page_num (_contents));
205                 }
206
207                 /* don't let anything else handle this */
208                 
209                 return true;
210         } 
211
212         /* nothing to do */
213         return false;
214 }
215
216 bool
217 Tabbable::is_tabbed () const
218 {
219         Window* toplevel = (Window*) _contents.get_toplevel();
220
221         if (_window && (toplevel == _window)) {
222                 return false;
223         }
224
225         if (_parent_notebook && _contents.get_parent()) {
226                 return true;
227         }
228         
229         return false;
230 }
231
232 void
233 Tabbable::show_tab ()
234 {
235         if (!window_visible() && _parent_notebook) {
236                 if (_contents.get_parent() == 0) {
237                         add_to_notebook (*_parent_notebook, _tab_title);
238                 }
239                 _parent_notebook->set_current_page (_parent_notebook->page_num (_contents));
240         }
241 }
242
243 Gtk::Window*
244 Tabbable::current_toplevel () const
245 {
246         return dynamic_cast<Gtk::Window*> (contents().get_toplevel());
247 }
248
249 string
250 Tabbable::xml_node_name()
251 {
252         return WindowProxy::xml_node_name();
253 }
254
255 XMLNode&
256 Tabbable::get_state()
257 {
258         XMLNode& node (WindowProxy::get_state());
259
260         return node;
261 }
262
263 int
264 Tabbable::set_state (const XMLNode& node, int version)
265 {
266         int ret;
267
268         if ((ret = WindowProxy::set_state (node, version)) == 0) {
269                 if (_visible) {
270                         if (use_own_window (true) == 0) {
271                                 ret = -1;
272                         }
273                 }
274         }
275
276         return ret;
277 }
278
279 void
280 Tabbable::make_visible ()
281 {
282         if (_window && (current_toplevel() == _window)) {
283                 _window->present ();
284         } else {
285                 show_tab ();
286         }
287 }