e59a610aa2695b584d1653c2144acc90ef5af58e
[ardour.git] / libs / gtkmm2ext / tearoff.cc
1 /*
2     Copyright (C) 2003 Paul Barton-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     $Id$
19 */
20
21 #include <cmath>
22 #include <iostream>
23
24 #include "pbd/xml++.h"
25
26 #include "gtkmm2ext/tearoff.h"
27 #include "gtkmm2ext/utils.h"
28
29 #include "i18n.h"
30
31 using namespace Gtkmm2ext;
32 using namespace Gtk;
33 using namespace Gdk;
34 using namespace Glib;
35 using namespace std;
36
37 TearOff::TearOff (Widget& c, bool allow_resize)
38         : contents (c)
39         , own_window (Gtk::WINDOW_TOPLEVEL)
40         , tearoff_arrow (ARROW_DOWN, SHADOW_OUT)
41         , close_arrow (ARROW_UP, SHADOW_OUT)
42         , dragging (false)
43         , _visible (true)
44         , _torn (false)
45         , _can_be_torn_off (true)
46          
47 {
48         own_window_width = 0;
49         own_window_height = 0;
50         own_window_xpos = 0;
51         own_window_ypos = 0;
52
53         tearoff_event_box.add (tearoff_arrow);
54         tearoff_event_box.set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
55         tearoff_event_box.signal_button_release_event().connect (mem_fun (*this, &TearOff::tearoff_click));
56
57         close_event_box.add (close_arrow);
58         close_event_box.set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
59         close_event_box.signal_button_release_event().connect (mem_fun (*this, &TearOff::close_click));
60         
61
62         VBox* box1;
63         box1 = manage (new VBox);
64         box1->pack_start (close_event_box, false, false, 2);
65         
66         window_box.pack_end (*box1, false, false, 2);
67
68         own_window.add_events (KEY_PRESS_MASK|KEY_RELEASE_MASK|BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK|POINTER_MOTION_MASK|POINTER_MOTION_HINT_MASK);
69         own_window.set_resizable (allow_resize);
70         own_window.set_type_hint (WINDOW_TYPE_HINT_TOOLBAR);
71
72         own_window.add (window_box);
73         
74         own_window.signal_button_press_event().connect (mem_fun (*this, &TearOff::window_button_press));
75         own_window.signal_button_release_event().connect (mem_fun (*this, &TearOff::window_button_release));
76         own_window.signal_motion_notify_event().connect (mem_fun (*this, &TearOff::window_motion));
77         own_window.signal_delete_event().connect (mem_fun (*this, &TearOff::window_delete_event));
78         own_window.signal_realize().connect (sigc::mem_fun (*this, &TearOff::own_window_realized));
79         own_window.signal_configure_event().connect (sigc::mem_fun (*this, &TearOff::own_window_configured), false);
80
81         tearoff_arrow.set_name ("TearOffArrow");
82         close_arrow.set_name ("TearOffArrow");
83
84         VBox* box2;
85         box2 = manage (new VBox);
86         box2->pack_start (tearoff_event_box, false, false, 2);
87
88         pack_start (contents);
89         pack_start (*box2, false, false, 2);
90 }
91
92 TearOff::~TearOff ()
93 {
94 }
95
96 void
97 TearOff::set_can_be_torn_off (bool yn)
98 {
99         if (yn != _can_be_torn_off) {
100                 if (yn) {
101                         tearoff_arrow.set_no_show_all (false);
102                         tearoff_arrow.show ();
103                 } else {
104                         tearoff_arrow.set_no_show_all (true);
105                         tearoff_arrow.hide ();
106                 }
107                 _can_be_torn_off = yn;
108         }
109 }
110
111 void
112 TearOff::set_visible (bool yn)
113 {
114         /* don't change visibility if torn off */
115
116         if (_torn) {
117                 return;
118         }
119
120         if (_visible != yn) {
121                 _visible = yn;
122                 if (yn) {
123                         show_all();
124                         Visible ();
125                 } else {
126                         hide ();
127                         Hidden ();
128                 }
129         }
130 }
131
132 gint
133 TearOff::tearoff_click (GdkEventButton* /*ev*/)
134 {
135         tear_it_off ();
136         return true;
137 }
138
139 void
140 TearOff::tear_it_off ()
141 {
142         if (!_can_be_torn_off) {
143                 return;
144         }
145                 
146         if (torn_off()) {
147                 return;
148         }
149
150         remove (contents);
151         window_box.pack_start (contents);
152         own_window.set_name (get_name());
153         close_event_box.set_name (get_name());
154         own_window.show_all ();
155         own_window.present ();
156         hide ();
157
158         _torn = true;
159
160         Detach ();
161 }        
162
163 gint
164 TearOff::close_click (GdkEventButton* /*ev*/)
165 {
166         put_it_back ();
167         return true;
168 }               
169
170 void
171 TearOff::put_it_back ()
172 {
173         if (!torn_off()) {
174                 return;
175         }
176
177         window_box.remove (contents);
178         pack_start (contents);
179         reorder_child (contents, 0);
180         own_window.hide ();
181         show_all ();
182
183         _torn = false;
184
185         Attach ();
186 }
187
188 gint
189 TearOff::window_button_press (GdkEventButton* ev)
190 {
191         if (dragging || ev->button != 1) {
192                 dragging = false;
193                 own_window.remove_modal_grab();
194                 return true;
195         }
196
197         dragging = true;
198         drag_x = ev->x_root;
199         drag_y = ev->y_root;
200
201         own_window.add_modal_grab();
202
203         return true;
204 }
205
206 gint
207 TearOff::window_button_release (GdkEventButton* /*ev*/)
208 {
209         dragging = false;
210         own_window.remove_modal_grab();
211         return true;
212 }
213
214 gint
215 TearOff::window_delete_event (GdkEventAny* /*ev*/)
216 {
217         return close_click(0);
218 }
219
220 gint
221 TearOff::window_motion (GdkEventMotion* ev)
222 {
223         gint x;
224         gint y;
225         gint mx, my;
226         double x_delta;
227         double y_delta;
228         RefPtr<Gdk::Window> win (own_window.get_window());
229         
230         own_window.get_pointer (mx, my);
231
232         if (!dragging) {
233                 return true;
234         }
235
236         if (!(ev->state & GDK_BUTTON1_MASK)) {
237                 dragging = false;
238                 own_window.remove_modal_grab();
239                 return true;
240         }
241
242         x_delta = ev->x_root - drag_x;
243         y_delta = ev->y_root - drag_y;
244
245         win->get_root_origin (x, y);
246         win->move ((gint) floor (x + x_delta), (gint) floor (y + y_delta));
247         
248         drag_x = ev->x_root;
249         drag_y = ev->y_root;
250         
251         return true;
252 }
253
254 bool
255 TearOff::torn_off() const
256 {
257         return _torn;
258 }
259
260 void
261 TearOff::add_state (XMLNode& node) const
262 {
263         node.add_property ("tornoff", (_torn ? "yes" : "no"));
264
265         if (own_window_width > 0) {
266                 char buf[32];
267
268                 snprintf (buf, sizeof (buf), "%d", own_window_width);
269                 node.add_property ("width", buf);
270                 snprintf (buf, sizeof (buf), "%d", own_window_height);
271                 node.add_property ("height", buf);
272                 snprintf (buf, sizeof (buf), "%d", own_window_xpos);
273                 node.add_property ("xpos", buf);
274                 snprintf (buf, sizeof (buf), "%d", own_window_ypos);
275                 node.add_property ("ypos", buf);
276         }
277 }        
278
279 void
280 TearOff::set_state (const XMLNode& node)
281 {
282         Glib::RefPtr<Gdk::Window> win;
283         const XMLProperty* prop;
284
285         if ((prop = node.property (X_("tornoff"))) == 0) {
286                 return;
287         }
288
289         if (prop->value() == "yes") {
290                 tear_it_off ();
291         } else {
292                 put_it_back ();
293         }
294
295         if ((prop = node.property (X_("width"))) != 0) {
296                 sscanf (prop->value().c_str(), "%d", &own_window_width);
297         }
298         if ((prop = node.property (X_("height"))) != 0) {
299                 sscanf (prop->value().c_str(), "%d", &own_window_height);
300         }
301         if ((prop = node.property (X_("xpos"))) != 0) {
302                 sscanf (prop->value().c_str(), "%d", &own_window_xpos);
303         }
304         if ((prop = node.property (X_("ypos"))) != 0) {
305                 sscanf (prop->value().c_str(), "%d", &own_window_ypos);
306         }
307
308         if (own_window.is_realized()) {
309                 own_window.set_default_size (own_window_width, own_window_height);
310                 own_window.move (own_window_xpos, own_window_ypos);
311         }
312         /* otherwise do it once the window is realized, see below */
313 }        
314
315 void
316 TearOff::own_window_realized ()
317 {
318         own_window.get_window()->set_decorations (WMDecoration (DECOR_BORDER|DECOR_RESIZEH));
319
320         if (own_window_width > 0) {
321                 own_window.set_default_size (own_window_width, own_window_height);
322                 own_window.move (own_window_xpos, own_window_ypos);
323         }
324 }
325
326 bool
327 TearOff::own_window_configured (GdkEventConfigure*)
328 {
329         Glib::RefPtr<const Gdk::Window> win;
330
331         win = own_window.get_window ();
332         
333         if (win) {
334                 win->get_size (own_window_width, own_window_height);
335                 win->get_position (own_window_xpos, own_window_ypos);
336         }
337
338         return false;
339 }
340
341 void
342 TearOff::hide_visible ()
343 {
344         if (torn_off()) {
345                 own_window.hide ();
346         }
347
348         hide ();
349 }
350
351