ardour
dndvbox.h
Go to the documentation of this file.
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 #include "gtkmm2ext/visibility.h"
23 #include "gtkmm2ext/widget_state.h"
24 
25 namespace Gtkmm2ext {
26 
28 class /*LIBGTKMM2EXT_API*/ DnDVBoxChild
29 {
30 public:
31  virtual ~DnDVBoxChild () {}
32 
34  virtual Gtk::Widget& widget () = 0;
35 
37  virtual Gtk::EventBox& action_widget () = 0;
38 
40  virtual std::string drag_text () const = 0;
41 
43  virtual void set_visual_state (VisualState, bool onoff) = 0;
44 
46  virtual bool is_selectable () const = 0;
47 };
48 
50 template <class T>
51 class /*LIBGTKMM2EXT_API*/ DnDVBox : public Gtk::EventBox
52 {
53 public:
55  {
56  _targets.push_back (Gtk::TargetEntry ("processor"));
57 
58  add (_internal_vbox);
59  add_events (
60  Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK |
61  Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK |
62  Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
63  );
64 
65  signal_button_press_event().connect (sigc::bind (mem_fun (*this, &DnDVBox::button_press), (T *) 0));
66  signal_button_release_event().connect (sigc::bind (mem_fun (*this, &DnDVBox::button_release), (T *) 0));
67  signal_drag_motion().connect (mem_fun (*this, &DnDVBox::drag_motion));
68  signal_drag_leave().connect (mem_fun (*this, &DnDVBox::drag_leave));
69 
70  _internal_vbox.show ();
71 
72  drag_dest_set (_targets);
73  signal_drag_data_received().connect (mem_fun (*this, &DnDVBox::drag_data_received));
74  }
75 
76  virtual ~DnDVBox ()
77  {
78  clear ();
79 
80  delete _drag_icon;
81  }
82 
84  void add_child (T* child)
85  {
86  child->action_widget().drag_source_set (_targets);
87  child->action_widget().signal_drag_begin().connect (sigc::bind (mem_fun (*this, &DnDVBox::drag_begin), child));
88  child->action_widget().signal_drag_data_get().connect (sigc::bind (mem_fun (*this, &DnDVBox::drag_data_get), child));
89  child->action_widget().signal_drag_end().connect (sigc::bind (mem_fun (*this, &DnDVBox::drag_end), child));
90  child->action_widget().signal_button_press_event().connect (sigc::bind (mem_fun (*this, &DnDVBox::button_press), child));
91  child->action_widget().signal_button_release_event().connect (sigc::bind (mem_fun (*this, &DnDVBox::button_release), child));
92 
93  _internal_vbox.pack_start (child->widget(), false, false);
94 
95  _children.push_back (child);
96  child->widget().show ();
97  }
98 
100  std::list<T*> children ()
101  {
102  std::list<T*> sorted_children;
103 
104  std::list<Gtk::Widget*> widget_children = _internal_vbox.get_children ();
105  for (std::list<Gtk::Widget*>::iterator i = widget_children.begin(); i != widget_children.end(); ++i) {
106  T* c = child_from_widget (*i);
107 
108  if (c) {
109  sorted_children.push_back (c);
110  }
111  }
112 
113  return sorted_children;
114  }
115 
117  std::list<T*> selection () const {
118  return _selection;
119  }
120 
125  void set_active (T* c) {
126  T* old_active = _active;
127  _active = c;
128  if (old_active) {
129  setup_child_state (old_active);
130  }
131  if (_active) {
133  }
134  }
135 
139  bool selected (T* child) const {
140  return (find (_selection.begin(), _selection.end(), child) != _selection.end());
141  }
142 
144  void clear ()
145  {
146  _selection.clear ();
147 
148  for (typename std::list<T*>::iterator i = _children.begin(); i != _children.end(); ++i) {
149  _internal_vbox.remove ((*i)->widget());
150  delete *i;
151  }
152 
153  _children.clear ();
154  _active = 0;
155  }
156 
157  void select_all ()
158  {
159  clear_selection ();
160  for (typename std::list<T*>::iterator i = _children.begin(); i != _children.end(); ++i) {
161  add_to_selection (*i);
162  }
163 
164  SelectionChanged (); /* EMIT SIGNAL */
165  }
166 
167  void select_none ()
168  {
169  clear_selection ();
170 
171  SelectionChanged (); /* EMIT SIGNAL */
172  }
173 
177  std::pair<T*, double> get_child_at_position (int y) const
178  {
179  T* before;
180  T* after;
181 
182  std::pair<T*, double> r;
183 
184  r.second = get_children_around_position (y, &before, &r.first, &after);
185 
186  return r;
187  }
188 
189  void set_spacing (int s) {
190  _internal_vbox.set_spacing (s);
191  }
192 
194  {
195  if (_placeholder) {
196  _internal_vbox.remove (*_placeholder);
197  _placeholder = 0;
198  }
199  }
200 
205  int add_placeholder (double y)
206  {
208  }
209 
211  sigc::signal<void> Reordered;
212 
214  sigc::signal<bool, GdkEventButton*, T*> ButtonPress;
215 
217  sigc::signal<bool, GdkEventButton*, T*> ButtonRelease;
218 
222  sigc::signal<void, DnDVBox*, T*, Glib::RefPtr<Gdk::DragContext> const & > DropFromAnotherBox;
223  sigc::signal<void> SelectionChanged;
224 
225 private:
226 
230  double bottom_of_child_ignoring_placeholder (T* child) const
231  {
232  Gtk::Allocation const a = child->widget().get_allocation ();
233  double bottom = a.get_y() + a.get_height();
234 
235  if (_placeholder) {
236  Gtk::Allocation const b = _placeholder->get_allocation ();
237  if (b.get_y() < a.get_y()) {
238  bottom -= (b.get_height () + _internal_vbox.get_spacing ());
239  }
240  }
241 
242  return bottom;
243  }
244 
252  double get_children_around_position (int y, T** before, T** at, T** after) const
253  {
254  if (_children.empty()) {
255  *before = *at = *after = 0;
256  return -1;
257  }
258 
259  *before = 0;
260 
261  typename std::list<T*>::const_iterator j = _children.begin ();
262 
263  /* index of current child */
264  int i = 0;
265  /* top of current child */
266  double top = 0;
267  /* bottom of current child */
268  double bottom = bottom_of_child_ignoring_placeholder (*j);
269 
270  while (y >= bottom && j != _children.end()) {
271 
272  top = bottom;
273 
274  *before = *j;
275  ++i;
276  ++j;
277 
278  if (j != _children.end()) {
280  }
281  }
282 
283  if (j == _children.end()) {
284  *at = 0;
285  *after = 0;
286  return -1;
287  }
288 
289  *at = *j;
290 
291  ++j;
292  *after = j != _children.end() ? *j : 0;
293 
294  return i + ((y - top) / (bottom - top));
295  }
296 
297  void drag_begin (Glib::RefPtr<Gdk::DragContext> const & context, T* child)
298  {
299  _drag_child = child;
300 
301  /* make up an icon for the drag */
302  _drag_icon = new Gtk::Window (Gtk::WINDOW_POPUP);
303 
304  Gtk::Allocation a = child->action_widget().get_allocation ();
305  _drag_icon->set_size_request (a.get_width(), a.get_height());
306 
307  _drag_icon->signal_expose_event().connect (sigc::mem_fun (*this, &DnDVBox::icon_expose));
308  _drag_icon->set_name (get_name ());
309 
310  /* make the icon transparent if possible */
311  Glib::RefPtr<Gdk::Screen const> s = _drag_icon->get_screen ();
312  Glib::RefPtr<Gdk::Colormap const> c = s->get_rgba_colormap ();
313  if (c) {
314  _drag_icon->set_colormap (c);
315  }
316 
317  int w, h;
318  _drag_icon->get_size (w, h);
319  _drag_icon->drag_set_as_icon (context, w / 2, h / 2);
320 
321  _drag_source = this;
322  }
323 
324  /* Draw the drag icon */
325  bool icon_expose (GdkEventExpose*)
326  {
327  /* Just grab the child's widget and use that */
328 
329  int w, h;
330  _drag_icon->get_size (w, h);
331 
332  cairo_t* cr = gdk_cairo_create (_drag_icon->get_window()->gobj ());
333 
334  Glib::RefPtr<Gdk::Pixmap> p = _drag_child->action_widget().get_snapshot();
335  gdk_cairo_set_source_pixmap (cr, p->gobj(), 0, 0);
336  cairo_rectangle (cr, 0, 0, w, h);
337  cairo_fill (cr);
338  cairo_destroy (cr);
339 
340  return false;
341  }
342 
343  void drag_data_get (Glib::RefPtr<Gdk::DragContext> const &, Gtk::SelectionData & selection_data, guint, guint, T* child)
344  {
345  selection_data.set (selection_data.get_target(), 8, (const guchar *) &child, sizeof (&child));
346  }
347 
349  Glib::RefPtr<Gdk::DragContext> const & context, int /*x*/, int y, Gtk::SelectionData const & selection_data, guint /*info*/, guint time
350  )
351  {
352  /* work out where it was dropped */
353  std::pair<T*, double> const drop = get_child_at_position (y);
354 
355  if (_drag_source == this) {
356 
357  /* dropped from ourselves onto ourselves */
358 
359  T* child = *((T * const *) selection_data.get_data());
360 
361  if (drop.first == 0) {
362  _internal_vbox.reorder_child (child->widget(), -1);
363  } else {
364 
365  /* where in the list this child should be dropped */
366  int target = drop.second + 0.5;
367 
368  /* find out whether the child was `picked up' from before the drop position */
369  int n = 0;
370  typename std::list<T*>::const_iterator i = _children.begin ();
371  while (i != _children.end() && *i != child && n < target) {
372  ++i;
373  ++n;
374  }
375 
376  /* if so, adjust the drop position to account for this */
377  if (n < target) {
378  --target;
379  }
380 
381  _internal_vbox.reorder_child (child->widget(), target);
382  }
383 
384  } else {
385 
386  /* drag started in another DnDVBox; raise a signal to say what happened */
387 
388  std::list<T*> dropped = _drag_source->selection ();
389  DropFromAnotherBox (_drag_source, drop.first, context);
390  }
391 
392  context->drag_finish (false, false, time);
393  }
394 
395  void drag_end (Glib::RefPtr<Gdk::DragContext> const &, T *)
396  {
397  delete _drag_icon;
398  _drag_icon = 0;
399 
400  _drag_child = 0;
402 
403  Reordered (); /* EMIT SIGNAL */
404  }
405 
411  {
412  if (_placeholder == 0) {
413  _placeholder = manage (new Gtk::Label (""));
414  _internal_vbox.pack_start (*_placeholder, false, false);
415  _placeholder->show ();
416  }
417 
418  /* round up the index, unless we're off the end of the children */
419  int const n = c < 0 ? -1 : int (c + 0.5);
420  _internal_vbox.reorder_child (*_placeholder, n);
421  return n;
422  }
423 
424  bool drag_motion (Glib::RefPtr<Gdk::DragContext> const &, int /*x*/, int y, guint)
425  {
426  if (_children.empty ()) {
427  return false;
428  }
429 
430  T* before;
431  T* at;
432  T* after;
433 
434  /* decide where we currently are */
435  double const c = get_children_around_position (y, &before, &at, &after);
436 
437  /* whether we're in the top or bottom half of the child that we're over */
438  bool top_half = (c - int (c)) < 0.5;
439 
440  /* Note that when checking on whether to remove a placeholder, we never do
441  so if _drag_child is 0 as this means that the child being dragged is
442  coming from a different DnDVBox, so it will never be the same as any
443  of our children.
444  */
445 
446  if (top_half && _drag_child && (before == _drag_child || at == _drag_child)) {
447  /* dropping here would have no effect, so remove the visual cue */
449  return false;
450  }
451 
452  if (!top_half && _drag_child && (at == _drag_child || after == _drag_child)) {
453  /* dropping here would have no effect, so remove the visual cue */
455  return false;
456  }
457 
459  return false;
460  }
461 
462  void drag_leave (Glib::RefPtr<Gdk::DragContext> const &, guint)
463  {
465  }
466 
467  bool button_press (GdkEventButton* ev, T* child)
468  {
469  if (_expecting_unwanted_button_event == true && child == 0) {
471  return true;
472  }
473 
474  if (child) {
476  }
477 
478  if (ev->button == 1 || ev->button == 3) {
479 
480  if (!selected (child)) {
481 
482  if ((ev->state & Gdk::SHIFT_MASK) && !_selection.empty()) {
483 
484  /* Shift-click; select all between the clicked child and any existing selections */
485 
486  bool selecting = false;
487  bool done = false;
488  for (typename std::list<T*>::const_iterator i = _children.begin(); i != _children.end(); ++i) {
489 
490  bool const was_selected = selected (*i);
491 
492  if (selecting && !was_selected) {
493  add_to_selection (*i);
494  }
495 
496  if (!selecting && !done) {
497  if (selected (*i)) {
498  selecting = true;
499  } else if (*i == child) {
500  selecting = true;
501  add_to_selection (child);
502  }
503  } else if (selecting) {
504  if (was_selected || *i == child) {
505  selecting = false;
506  done = true;
507  }
508  }
509  }
510 
511  } else {
512 
513  if ((ev->state & Gdk::CONTROL_MASK) == 0) {
514  clear_selection ();
515  }
516 
517  if (child) {
518  add_to_selection (child);
519  }
520 
521  }
522 
523  SelectionChanged (); /* EMIT SIGNAL */
524 
525  } else {
526  /* XXX THIS NEEDS GENERALIZING FOR OS X */
527  if (ev->button == 1 && (ev->state & Gdk::CONTROL_MASK)) {
528  if (child && selected (child)) {
529  remove_from_selection (child);
530  SelectionChanged (); /* EMIT SIGNAL */
531  }
532  }
533  }
534  }
535 
536  return ButtonPress (ev, child); /* EMIT SIGNAL */
537  }
538 
539  bool button_release (GdkEventButton* ev, T* child)
540  {
541  if (_expecting_unwanted_button_event == true && child == 0) {
543  return true;
544  }
545 
546  if (child) {
548  }
549 
550  return ButtonRelease (ev, child); /* EMIT SIGNAL */
551  }
552 
554  void setup_child_state (T* c)
555  {
556  assert (c);
557  c->set_visual_state (Selected, (selected (c) || (_active == c)));
558  }
559 
561  {
562  std::list<T*> old_selection = _selection;
563  _selection.clear ();
564  for (typename std::list<T*>::iterator i = old_selection.begin(); i != old_selection.end(); ++i) {
565  setup_child_state (*i);
566  }
567  }
568 
569  void add_to_selection (T* child)
570  {
571  if ( !child->is_selectable() )
572  return;
573  _selection.push_back (child);
574  setup_child_state (child);
575  }
576 
577  void remove_from_selection (T* child)
578  {
579  typename std::list<T*>::iterator x = find (_selection.begin(), _selection.end(), child);
580  if (x != _selection.end()) {
581  T* c = *x;
582  _selection.erase (x);
583  setup_child_state (c);
584  }
585  }
586 
587  T* child_from_widget (Gtk::Widget const * w) const
588  {
589  typename std::list<T*>::const_iterator i = _children.begin();
590  while (i != _children.end() && &(*i)->widget() != w) {
591  ++i;
592  }
593 
594  if (i == _children.end()) {
595  return 0;
596  }
597 
598  return *i;
599  }
600 
601  Gtk::VBox _internal_vbox;
602  std::list<Gtk::TargetEntry> _targets;
603  std::list<T*> _children;
604  std::list<T*> _selection;
606  Gtk::Window* _drag_icon;
611  Gtk::Label* _placeholder;
614 
616 
617 };
618 
619 template <class T>
621 
622 }
virtual bool is_selectable() const =0
std::list< T * > selection() const
Definition: dndvbox.h:117
std::list< T * > _selection
Definition: dndvbox.h:604
int create_or_update_placeholder(double c)
Definition: dndvbox.h:410
double get_children_around_position(int y, T **before, T **at, T **after) const
Definition: dndvbox.h:252
std::list< Gtk::TargetEntry > _targets
Definition: dndvbox.h:602
void remove_from_selection(T *child)
Definition: dndvbox.h:577
T * child_from_widget(Gtk::Widget const *w) const
Definition: dndvbox.h:587
void clear_selection()
Definition: dndvbox.h:560
void add_child(T *child)
Definition: dndvbox.h:84
void drag_end(Glib::RefPtr< Gdk::DragContext > const &, T *)
Definition: dndvbox.h:395
virtual void set_visual_state(VisualState, bool onoff)=0
bool drag_motion(Glib::RefPtr< Gdk::DragContext > const &, int, int y, guint)
Definition: dndvbox.h:424
void select_none()
Definition: dndvbox.h:167
std::pair< T *, double > get_child_at_position(int y) const
Definition: dndvbox.h:177
sigc::signal< void, DnDVBox *, T *, Glib::RefPtr< Gdk::DragContext > const & > DropFromAnotherBox
Definition: dndvbox.h:222
virtual std::string drag_text() const =0
virtual Gtk::EventBox & action_widget()=0
bool icon_expose(GdkEventExpose *)
Definition: dndvbox.h:325
std::list< T * > _children
Definition: dndvbox.h:603
void select_all()
Definition: dndvbox.h:157
void set_active(T *c)
Definition: dndvbox.h:125
int add_placeholder(double y)
Definition: dndvbox.h:205
Selected
Definition: widget_state.h:21
void drag_data_received(Glib::RefPtr< Gdk::DragContext > const &context, int, int y, Gtk::SelectionData const &selection_data, guint, guint time)
Definition: dndvbox.h:348
bool button_release(GdkEventButton *ev, T *child)
Definition: dndvbox.h:539
sigc::signal< void > Reordered
Definition: dndvbox.h:211
virtual Gtk::Widget & widget()=0
void drag_data_get(Glib::RefPtr< Gdk::DragContext > const &, Gtk::SelectionData &selection_data, guint, guint, T *child)
Definition: dndvbox.h:343
void drag_leave(Glib::RefPtr< Gdk::DragContext > const &, guint)
Definition: dndvbox.h:462
virtual ~DnDVBox()
Definition: dndvbox.h:76
void remove_placeholder()
Definition: dndvbox.h:193
double bottom_of_child_ignoring_placeholder(T *child) const
Definition: dndvbox.h:230
std::list< T * > children()
Definition: dndvbox.h:100
Gtk::VBox _internal_vbox
Definition: dndvbox.h:601
void set_spacing(int s)
Definition: dndvbox.h:189
virtual ~DnDVBoxChild()
Definition: dndvbox.h:31
Gtk::Label * _placeholder
Definition: dndvbox.h:611
Gtk::Window * _drag_icon
Definition: dndvbox.h:606
bool button_press(GdkEventButton *ev, T *child)
Definition: dndvbox.h:467
bool _expecting_unwanted_button_event
Definition: dndvbox.h:607
static DnDVBox * _drag_source
Definition: dndvbox.h:615
void drag_begin(Glib::RefPtr< Gdk::DragContext > const &context, T *child)
Definition: dndvbox.h:297
sigc::signal< bool, GdkEventButton *, T * > ButtonRelease
Definition: dndvbox.h:217
void setup_child_state(T *c)
Definition: dndvbox.h:554
bool selected(T *child) const
Definition: dndvbox.h:139
sigc::signal< bool, GdkEventButton *, T * > ButtonPress
Definition: dndvbox.h:214
void add_to_selection(T *child)
Definition: dndvbox.h:569
sigc::signal< void > SelectionChanged
Definition: dndvbox.h:223