remove a couple of boost::signals2 trouble spots; fix some --strict compile time...
[ardour.git] / libs / gtkmm2ext / gtkmm2ext / dndvbox.h
1 /*
2     Copyright (C) 2009 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/box.h>
21
22 namespace Gtkmm2ext {
23
24 /** Parent class for children of a DnDVBox */   
25 class DnDVBoxChild
26 {
27 public:
28         virtual ~DnDVBoxChild () {}
29         
30         /** @return The widget that is to be put into the DnDVBox */
31         virtual Gtk::Widget& widget () = 0;
32         
33         /** @return An EventBox containing the widget that should be used for selection, dragging etc. */
34         virtual Gtk::EventBox& action_widget () = 0;
35
36         /** @return Text to use in the icon that is dragged */
37         virtual std::string drag_text () const = 0;
38 };
39
40 /** A VBox whose contents can be dragged and dropped */
41 template <class T>
42 class DnDVBox : public Gtk::EventBox
43 {
44 public:
45         DnDVBox () : _drag_icon (0), _expecting_unwanted_button_event (false)
46         {
47                 _targets.push_back (Gtk::TargetEntry ("processor"));
48
49                 add (_internal_vbox);
50                 add_events (Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
51
52                 signal_button_press_event().connect (bind (mem_fun (*this, &DnDVBox::button_press), (T *) 0));
53                 signal_button_release_event().connect (bind (mem_fun (*this, &DnDVBox::button_release), (T *) 0));
54                 
55                 drag_dest_set (_targets);
56                 signal_drag_data_received().connect (mem_fun (*this, &DnDVBox::drag_data_received));
57         }
58         
59         virtual ~DnDVBox ()
60         {
61                 clear ();
62                 
63                 delete _drag_icon;
64         }
65
66         /** Add a child at the end of the widget.  The DnDVBox will take responsibility for deleting the child */
67         void add_child (T* child)
68         {
69                 child->action_widget().drag_source_set (_targets);
70                 child->action_widget().signal_drag_begin().connect (sigc::bind (mem_fun (*this, &DnDVBox::drag_begin), child));
71                 child->action_widget().signal_drag_data_get().connect (sigc::bind (mem_fun (*this, &DnDVBox::drag_data_get), child));
72                 child->action_widget().signal_drag_end().connect (sigc::bind (mem_fun (*this, &DnDVBox::drag_end), child));
73                 child->action_widget().signal_button_press_event().connect (sigc::bind (mem_fun (*this, &DnDVBox::button_press), child));
74                 child->action_widget().signal_button_release_event().connect (sigc::bind (mem_fun (*this, &DnDVBox::button_release), child));
75                 
76                 _internal_vbox.pack_start (child->widget(), false, false);
77                 
78                 _children.push_back (child);
79                 child->widget().show_all ();
80         }
81
82         /** @return Children, sorted into the order that they are currently being displayed in the widget */
83         std::list<T*> children ()
84         {
85                 std::list<T*> sorted_children;
86
87                 std::list<Gtk::Widget*> widget_children = _internal_vbox.get_children ();
88                 for (std::list<Gtk::Widget*>::iterator i = widget_children.begin(); i != widget_children.end(); ++i) {
89                         T* c = child_from_widget (*i);
90
91                         if (c) {
92                                 sorted_children.push_back (c);
93                         }
94                 }
95
96                 return sorted_children;
97         }
98
99         /** @return Selected children */
100         std::list<T*> selection () const {
101                 return _selection;
102         }
103
104         /** @param Child
105          *  @return true if the child is selected, otherwise false */
106         bool selected (T* child) const {
107                 return (find (_selection.begin(), _selection.end(), child) != _selection.end());
108         }
109
110         /** Clear all children from the widget */
111         void clear ()
112         {
113                 _selection.clear ();
114
115                 for (typename std::list<T*>::iterator i = _children.begin(); i != _children.end(); ++i) {
116                         _internal_vbox.remove ((*i)->widget());
117                         delete *i;
118                 }
119
120                 _children.clear ();
121         }
122
123
124         void select_all ()
125         {
126                 clear_selection ();
127                 for (typename std::list<T*>::iterator i = _children.begin(); i != _children.end(); ++i) {
128                         add_to_selection (*i);
129                 }
130
131                 SelectionChanged (); /* EMIT SIGNAL */
132         }
133
134         void select_none ()
135         {
136                 clear_selection ();
137
138                 SelectionChanged (); /* EMIT SIGNAL */
139         }
140
141         /** @param x x coordinate.
142          *  @param y y coordinate.
143          *  @return Pair consisting of the child under (x, y) (or 0) and the index of the child under (x, y) (or -1)
144          */
145         std::pair<T*, int> get_child_at_position (int /*x*/, int y) const
146         {
147                 std::list<Gtk::Widget const *> children = _internal_vbox.get_children ();
148                 std::list<Gtk::Widget const *>::iterator i = children.begin();
149                 
150                 int n = 0;
151                 while (i != children.end()) {
152                         Gdk::Rectangle const a = (*i)->get_allocation ();
153                         if (y >= a.get_y() && y <= (a.get_y() + a.get_height())) {
154                                 break;
155                         }
156                         ++i;
157                         ++n;
158                 }
159                 
160                 if (i == children.end()) {
161                         return std::make_pair ((T *) 0, -1);
162                 }
163
164                 return std::make_pair (child_from_widget (*i), n);
165         }       
166
167         /** Children have been reordered by a drag */
168         sigc::signal<void> Reordered;
169
170         /** A button has been pressed over the widget */
171         sigc::signal<bool, GdkEventButton*, T*> ButtonPress;
172
173         /** A button has been release over the widget */
174         sigc::signal<bool, GdkEventButton*, T*> ButtonRelease;
175
176         /** A child has been dropped onto this DnDVBox from another one;
177          *  Parameters are the source DnDVBox, our child which the other one was dropped on (or 0) and the DragContext.
178          */
179         sigc::signal<void, DnDVBox*, T*, Glib::RefPtr<Gdk::DragContext> const & > DropFromAnotherBox;
180         sigc::signal<void> SelectionChanged;
181
182 private:
183         void drag_begin (Glib::RefPtr<Gdk::DragContext> const & context, T* child)
184         {
185                 /* make up an icon for the drag */
186                 Gtk::Window* _drag_icon = new Gtk::Window (Gtk::WINDOW_POPUP);
187                 _drag_icon->set_name (get_name ());
188                 Gtk::Label* l = new Gtk::Label (child->drag_text ());
189                 l->set_padding (4, 4);
190                 _drag_icon->add (*Gtk::manage (l));
191                 _drag_icon->show_all_children ();
192                 int w, h;
193                 _drag_icon->get_size (w, h);
194                 _drag_icon->drag_set_as_icon (context, w / 2, h);
195                 
196                 _drag_source = this;
197         }
198         
199         void drag_data_get (Glib::RefPtr<Gdk::DragContext> const &, Gtk::SelectionData & selection_data, guint, guint, T* child)
200         {
201                 selection_data.set (selection_data.get_target(), 8, (const guchar *) &child, sizeof (&child));
202         }
203         
204         void drag_data_received (
205                 Glib::RefPtr<Gdk::DragContext> const & context, int x, int y, Gtk::SelectionData const & selection_data, guint /*info*/, guint time
206                 )
207         {
208                 /* work out where it was dropped */
209                 std::pair<T*, int> const drop = get_child_at_position (x, y);
210                 
211                 if (_drag_source == this) {
212
213                         /* dropped from ourselves onto ourselves */
214                         
215                         T* child = *((T **) selection_data.get_data());
216                         
217                         /* reorder child widgets accordingly */
218                         if (drop.first == 0) {
219                                 _internal_vbox.reorder_child (child->widget(), -1);
220                         } else {
221                                 _internal_vbox.reorder_child (child->widget(), drop.second);
222                         }
223                         
224                 } else {
225                         
226                         /* drag started in another DnDVBox; raise a signal to say what happened */
227                         
228                         std::list<T*> dropped = _drag_source->selection ();
229                         DropFromAnotherBox (_drag_source, drop.first, context);
230                 }
231                 
232                 context->drag_finish (false, false, time);
233         }
234         
235         void drag_end (Glib::RefPtr<Gdk::DragContext> const &, T *)
236         {
237                 delete _drag_icon;
238                 _drag_icon = 0;
239
240                 Reordered (); /* EMIT SIGNAL */
241         }
242         
243         bool button_press (GdkEventButton* ev, T* child)
244         {
245                 if (_expecting_unwanted_button_event == true && child == 0) {
246                         _expecting_unwanted_button_event = false;
247                         return true;
248                 }
249
250                 if (child) {
251                         _expecting_unwanted_button_event = true;
252                 }
253                         
254                 if (ev->button == 1 || ev->button == 3) {
255                         if (!selected (child)) {
256                                 clear_selection ();
257                                 if (child) {
258                                         add_to_selection (child);
259                                 }
260                                 SelectionChanged (); /* EMIT SIGNAL */
261                         } else {
262                                 /* XXX THIS NEEDS GENERALIZING FOR OS X */
263                                 if (ev->button == 1 && (ev->state & Gdk::CONTROL_MASK)) {
264                                         if (child && selected (child)) {
265                                                 remove_from_selection (child);
266                                                 SelectionChanged (); /* EMIT SIGNAL */
267                                         }
268                                 }
269                         }
270                 }
271
272
273                 return ButtonPress (ev, child); /* EMIT SIGNAL */
274         }
275         
276         bool button_release (GdkEventButton* ev, T* child)
277         {
278                 if (_expecting_unwanted_button_event == true && child == 0) {
279                         _expecting_unwanted_button_event = false;
280                         return true;
281                 }
282
283                 if (child) {
284                         _expecting_unwanted_button_event = true;
285                 }
286
287                 return ButtonRelease (ev, child); /* EMIT SIGNAL */
288         }
289
290         void clear_selection ()
291         {
292                 for (typename std::list<T*>::iterator i = _selection.begin(); i != _selection.end(); ++i) {
293                         (*i)->action_widget().set_state (Gtk::STATE_NORMAL);
294                 }
295                 _selection.clear ();
296         }
297         
298         void add_to_selection (T* child)
299         {
300                 child->action_widget().set_state (Gtk::STATE_SELECTED);
301                 _selection.push_back (child);
302         }
303                 
304         
305         void remove_from_selection (T* child)
306         {
307                 typename std::list<T*>::iterator x = find (_selection.begin(), _selection.end(), child);
308                 if (x != _selection.end()) {
309                         child->action_widget().set_state (Gtk::STATE_NORMAL);
310                         _selection.erase (x);
311                 }
312         }
313                 
314         T* child_from_widget (Gtk::Widget const * w) const
315         {
316                 typename std::list<T*>::const_iterator i = _children.begin();
317                 while (i != _children.end() && &(*i)->widget() != w) {
318                         ++i;
319                 }
320                 
321                 if (i == _children.end()) {
322                         return 0;
323                 }
324
325                 return *i;
326         }
327         
328         Gtk::VBox _internal_vbox;
329         std::list<Gtk::TargetEntry> _targets;
330         std::list<T*> _children;
331         std::list<T*> _selection;
332         Gtk::Window* _drag_icon;
333         bool _expecting_unwanted_button_event;
334
335         static DnDVBox* _drag_source;
336 };
337
338 template <class T>
339 DnDVBox<T>* DnDVBox<T>::_drag_source = 0;
340         
341 }