rollback to 3428, before the mysterious removal of libs/* at 3431/3432
[ardour.git] / libs / gtkmm2ext / gtkmm2ext / dndtreeview.h
1 /*
2     Copyright (C) 2000-2007 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 #ifndef __gtkmm2ext_dndtreeview_h__
21 #define __gtkmm2ext_dndtreeview_h__
22
23 #include <stdint.h>
24 #include <string>
25 #include <gtkmm/treeview.h>
26 #include <gtkmm/treeselection.h>
27 #include <gtkmm/selectiondata.h>
28
29 namespace Gtkmm2ext {
30
31 template<class DataType>
32 struct SerializedObjectPointers {
33     uint32_t size;
34     uint32_t cnt;
35     char     type[32];
36     DataType data[0];
37 };
38
39 class DnDTreeViewBase : public Gtk::TreeView 
40 {
41   private:
42   public:
43         DnDTreeViewBase ();
44         ~DnDTreeViewBase() {}
45
46         void add_drop_targets (std::list<Gtk::TargetEntry>&);
47         void add_object_drag (int column, std::string type_name);
48         
49         void on_drag_leave(const Glib::RefPtr<Gdk::DragContext>& context, guint time) {
50                 suggested_action = context->get_suggested_action();
51                 TreeView::on_drag_leave (context, time);
52         }
53
54         bool on_drag_motion(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, guint time) {
55                 suggested_action = context->get_suggested_action();
56                 return TreeView::on_drag_motion (context, x, y, time);
57         }
58
59         bool on_drag_drop(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, guint time);
60
61   protected:
62         std::list<Gtk::TargetEntry> draggable;
63         Gdk::DragAction             suggested_action;
64         int                         data_column;
65 };
66
67 template<class DataType>
68 class DnDTreeView : public DnDTreeViewBase
69 {
70   public:
71         DnDTreeView() {} 
72         ~DnDTreeView() {}
73
74         sigc::signal<void,std::string,uint32_t,const DataType*> signal_object_drop;
75
76         void on_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context, Gtk::SelectionData& selection_data, guint info, guint time) {
77                 if (selection_data.get_target() == "GTK_TREE_MODEL_ROW") {
78                         
79                         TreeView::on_drag_data_get (context, selection_data, info, time);
80                         
81                 } else if (data_column >= 0) {
82                         
83                         Gtk::TreeSelection::ListHandle_Path selection = get_selection()->get_selected_rows ();
84                         SerializedObjectPointers<DataType>* sr = serialize_pointers (get_model(), &selection, selection_data.get_target());
85                         selection_data.set (8, (guchar*)sr, sr->size);
86                 }
87         }
88
89         void on_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time) {
90                 if (suggested_action) {
91                         /* this is a drag motion callback. just update the status to
92                            say that we are still dragging, and that's it.
93                         */
94                         suggested_action = Gdk::DragAction (0);
95                         TreeView::on_drag_data_received (context, x, y, selection_data, info, time);
96                         return;
97                 }
98                 
99                 if (selection_data.get_target() == "GTK_TREE_MODEL_ROW") {
100                         
101                         TreeView::on_drag_data_received (context, x, y, selection_data, info, time);
102                         
103                 } else if (data_column >= 0) {
104                         
105                         /* object D-n-D */
106                         
107                         const void* data = selection_data.get_data();
108                         const SerializedObjectPointers<DataType>* sr = reinterpret_cast<const SerializedObjectPointers<DataType> *>(data);
109                         
110                         if (sr) {
111                                 signal_object_drop (sr->type, sr->cnt, sr->data);
112                         }
113                         
114                 } else {
115                         /* some kind of target type added by the app, which will be handled by a signal handler */
116                 }
117         }
118
119   private:
120
121         SerializedObjectPointers<DataType>* serialize_pointers (Glib::RefPtr<Gtk::TreeModel> model, 
122                                                                 Gtk::TreeSelection::ListHandle_Path* selection,
123                                                                 Glib::ustring type) {
124
125                 /* this nasty chunk of code is here because X's DnD protocol (probably other graphics UI's too) 
126                    requires that we package up the entire data collection for DnD in a single contiguous region
127                    (so that it can be trivially copied between address spaces). We don't know the type of DataType so
128                    we have to mix-and-match C and C++ programming techniques here to get the right result.
129
130                    The C trick is to use the "someType foo[0];" declaration trick to create a zero-sized array at the
131                    end of a SerializedObjectPointers<DataType object. Then we allocate a raw memory buffer that extends
132                    past that array and thus provides space for however many DataType items we actually want to pass
133                    around.
134
135                    The C++ trick is to use the placement operator new() syntax to initialize that extra
136                    memory properly.
137                 */
138                 
139                 uint32_t cnt = selection->size();
140                 uint32_t sz = (sizeof (DataType) * cnt) + sizeof (SerializedObjectPointers<DataType>);
141
142                 char* buf = new char[sz];
143                 SerializedObjectPointers<DataType>* sr = (SerializedObjectPointers<DataType>*) buf;
144
145                 for (uint32_t i = 0; i < cnt; ++i) {
146                         new ((void *) &sr->data[i]) DataType ();
147                 }
148                 
149                 sr->cnt = cnt;
150                 sr->size = sz;
151                 snprintf (sr->type, sizeof (sr->type), "%s", type.c_str());
152                 
153                 cnt = 0;
154                 
155                 for (Gtk::TreeSelection::ListHandle_Path::iterator x = selection->begin(); x != selection->end(); ++x, ++cnt) {
156                         model->get_iter (*x)->get_value (data_column, sr->data[cnt]);
157                 }
158
159                 return sr;
160         }
161 };
162
163 } // namespace
164  
165 #endif /* __gtkmm2ext_dndtreeview_h__ */