| 1 | /* This file is part of FlowCanvas. |
|---|
| 2 | * Copyright (C) 2007-2009 David Robillard <http://drobilla.net> |
|---|
| 3 | * |
|---|
| 4 | * FlowCanvas is free software; you can redistribute it and/or modify it under the |
|---|
| 5 | * terms of the GNU General Public License as published by the Free Software |
|---|
| 6 | * Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 7 | * version. |
|---|
| 8 | * |
|---|
| 9 | * FlowCanvas is distributed in the hope that it will be useful, but WITHOUT ANY |
|---|
| 10 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 11 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. |
|---|
| 12 | * |
|---|
| 13 | * You should have received a copy of the GNU General Public License along |
|---|
| 14 | * with this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 15 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
|---|
| 16 | */ |
|---|
| 17 | |
|---|
| 18 | #include <algorithm> |
|---|
| 19 | #include <cassert> |
|---|
| 20 | #include <cmath> |
|---|
| 21 | #include <string> |
|---|
| 22 | |
|---|
| 23 | #include <libgnomecanvasmm.h> |
|---|
| 24 | |
|---|
| 25 | #include "Canvas.hpp" |
|---|
| 26 | #include "Connectable.hpp" |
|---|
| 27 | #include "Connection.hpp" |
|---|
| 28 | #include "Ellipse.hpp" |
|---|
| 29 | |
|---|
| 30 | namespace FlowCanvas { |
|---|
| 31 | |
|---|
| 32 | |
|---|
| 33 | Connection::Connection(boost::shared_ptr<Canvas> canvas, |
|---|
| 34 | boost::shared_ptr<Connectable> source, |
|---|
| 35 | boost::shared_ptr<Connectable> dest, |
|---|
| 36 | uint32_t color, |
|---|
| 37 | bool show_arrowhead) |
|---|
| 38 | : Gnome::Canvas::Group(*canvas->root()) |
|---|
| 39 | , _canvas(canvas) |
|---|
| 40 | , _source(source) |
|---|
| 41 | , _dest(dest) |
|---|
| 42 | , _bpath(*this) |
|---|
| 43 | , _path(gnome_canvas_path_def_new()) |
|---|
| 44 | , _handle(NULL) |
|---|
| 45 | , _color(color) |
|---|
| 46 | , _handle_style(HANDLE_NONE) |
|---|
| 47 | , _selected(false) |
|---|
| 48 | , _show_arrowhead(show_arrowhead) |
|---|
| 49 | { |
|---|
| 50 | _bpath.property_width_units() = 2.0; |
|---|
| 51 | set_color(color); |
|---|
| 52 | |
|---|
| 53 | update_location(); |
|---|
| 54 | raise_to_top(); |
|---|
| 55 | } |
|---|
| 56 | |
|---|
| 57 | |
|---|
| 58 | Connection::~Connection() |
|---|
| 59 | { |
|---|
| 60 | gnome_canvas_path_def_unref(_path); |
|---|
| 61 | } |
|---|
| 62 | |
|---|
| 63 | |
|---|
| 64 | void |
|---|
| 65 | Connection::set_color(uint32_t color) |
|---|
| 66 | { |
|---|
| 67 | _color = color; |
|---|
| 68 | _bpath.property_outline_color_rgba() = _color; |
|---|
| 69 | if (_handle) { |
|---|
| 70 | if (_handle->text) { |
|---|
| 71 | _handle->text->property_fill_color_rgba() = _color; |
|---|
| 72 | } |
|---|
| 73 | if (_handle->shape) { |
|---|
| 74 | _handle->shape->property_fill_color_rgba() = 0x000000FF; |
|---|
| 75 | _handle->shape->property_outline_color_rgba() = _color; |
|---|
| 76 | } |
|---|
| 77 | } |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | |
|---|
| 81 | /** Updates the path of the connection to match it's ports if they've moved. |
|---|
| 82 | */ |
|---|
| 83 | void |
|---|
| 84 | Connection::update_location() |
|---|
| 85 | { |
|---|
| 86 | boost::shared_ptr<Connectable> src = _source.lock(); |
|---|
| 87 | boost::shared_ptr<Connectable> dst = _dest.lock(); |
|---|
| 88 | |
|---|
| 89 | if (!src || !dst) |
|---|
| 90 | return; |
|---|
| 91 | |
|---|
| 92 | bool straight = (boost::dynamic_pointer_cast<Ellipse>(src) |
|---|
| 93 | || boost::dynamic_pointer_cast<Ellipse>(dst)); |
|---|
| 94 | |
|---|
| 95 | const Gnome::Art::Point src_point = src->src_connection_point(); |
|---|
| 96 | const Gnome::Art::Point dst_point = dst->dst_connection_point(src_point); |
|---|
| 97 | |
|---|
| 98 | const double src_x = src_point.get_x(); |
|---|
| 99 | const double src_y = src_point.get_y(); |
|---|
| 100 | const double dst_x = dst_point.get_x(); |
|---|
| 101 | const double dst_y = dst_point.get_y(); |
|---|
| 102 | |
|---|
| 103 | if (straight) { |
|---|
| 104 | |
|---|
| 105 | gnome_canvas_path_def_reset(_path); |
|---|
| 106 | gnome_canvas_path_def_moveto(_path, src_x, src_y); |
|---|
| 107 | gnome_canvas_path_def_lineto(_path, dst_x, dst_y); |
|---|
| 108 | double dx = src_x - dst_x; |
|---|
| 109 | double dy = src_y - dst_y; |
|---|
| 110 | |
|---|
| 111 | if (_handle) { |
|---|
| 112 | _handle->property_x() = src_x - dx/2.0; |
|---|
| 113 | _handle->property_y() = src_y - dy/2.0; |
|---|
| 114 | _handle->move(0, 0); |
|---|
| 115 | } |
|---|
| 116 | |
|---|
| 117 | if (_show_arrowhead) { |
|---|
| 118 | |
|---|
| 119 | const double h = sqrt(dx*dx + dy*dy); |
|---|
| 120 | |
|---|
| 121 | dx = dx / h * 10; |
|---|
| 122 | dy = dy / h * 10; |
|---|
| 123 | |
|---|
| 124 | gnome_canvas_path_def_lineto(_path, |
|---|
| 125 | dst_x + dx - dy/1.5, |
|---|
| 126 | dst_y + dy + dx/1.5); |
|---|
| 127 | |
|---|
| 128 | gnome_canvas_path_def_moveto(_path, dst_x, dst_y); |
|---|
| 129 | |
|---|
| 130 | gnome_canvas_path_def_lineto(_path, |
|---|
| 131 | dst_x + dx + dy/1.5, |
|---|
| 132 | dst_y + dy - dx/1.5); |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | } else { |
|---|
| 136 | |
|---|
| 137 | const double join_x = (src_x + dst_x)/2.0; |
|---|
| 138 | const double join_y = (src_y + dst_y)/2.0; |
|---|
| 139 | |
|---|
| 140 | double dx = fabs(dst_x - src_x); |
|---|
| 141 | double dy = fabs(dst_y - src_y); |
|---|
| 142 | |
|---|
| 143 | Gnome::Art::Point src_vec = src->connection_point_vector(dx/3.0, dy/3.0); |
|---|
| 144 | Gnome::Art::Point dst_vec = dst->connection_point_vector(dx/3.0, dy/3.0); |
|---|
| 145 | |
|---|
| 146 | double src_point_dx = src_vec.get_x(); |
|---|
| 147 | double src_point_dy = src_vec.get_y(); |
|---|
| 148 | double dst_point_dx = dst_vec.get_x(); |
|---|
| 149 | double dst_point_dy = dst_vec.get_y(); |
|---|
| 150 | |
|---|
| 151 | // Path 1 (src_x, src_y) -> (join_x, join_y) |
|---|
| 152 | // Control point 1 |
|---|
| 153 | const double src_x1 = src_x + src_point_dx; |
|---|
| 154 | const double src_y1 = src_y + src_point_dy; |
|---|
| 155 | // Control point 2 |
|---|
| 156 | const double src_x2 = (join_x + src_x1) / 2.0; |
|---|
| 157 | const double src_y2 = (join_y + src_y1) / 2.0; |
|---|
| 158 | |
|---|
| 159 | // Path 2, (join_x, join_y) -> (dst_x, dst_y) |
|---|
| 160 | // Control point 1 |
|---|
| 161 | const double dst_x1 = dst_x - dst_point_dx; |
|---|
| 162 | const double dst_y1 = dst_y - dst_point_dy; |
|---|
| 163 | // Control point 2 |
|---|
| 164 | const double dst_x2 = (join_x + dst_x1) / 2.0; |
|---|
| 165 | const double dst_y2 = (join_y + dst_y1) / 2.0; |
|---|
| 166 | |
|---|
| 167 | // libgnomecanvasmm + GTK 2.8 screwed up the Path API; use the C one. |
|---|
| 168 | gnome_canvas_path_def_reset(_path); |
|---|
| 169 | gnome_canvas_path_def_moveto(_path, src_x, src_y); |
|---|
| 170 | gnome_canvas_path_def_curveto(_path, src_x1, src_y1, src_x2, src_y2, join_x, join_y); |
|---|
| 171 | gnome_canvas_path_def_curveto(_path, dst_x2, dst_y2, dst_x1, dst_y1, dst_x, dst_y); |
|---|
| 172 | |
|---|
| 173 | // Uncomment to see control point path as straight lines |
|---|
| 174 | /*gnome_canvas_path_def_reset(_path); |
|---|
| 175 | gnome_canvas_path_def_moveto(_path, src_x, src_y); |
|---|
| 176 | gnome_canvas_path_def_lineto(_path, src_x1, src_y1); |
|---|
| 177 | gnome_canvas_path_def_lineto(_path, src_x2, src_y2); |
|---|
| 178 | gnome_canvas_path_def_lineto(_path, join_x, join_y); |
|---|
| 179 | gnome_canvas_path_def_lineto(_path, dst_x2, dst_y2); |
|---|
| 180 | gnome_canvas_path_def_lineto(_path, dst_x1, dst_y1); |
|---|
| 181 | gnome_canvas_path_def_lineto(_path, dst_x, dst_y);*/ |
|---|
| 182 | |
|---|
| 183 | if (_show_arrowhead) { |
|---|
| 184 | |
|---|
| 185 | const double h = sqrt(dx*dx + dy*dy); |
|---|
| 186 | |
|---|
| 187 | dx = dx / h * 10; |
|---|
| 188 | dy = dy / h * 10; |
|---|
| 189 | |
|---|
| 190 | gnome_canvas_path_def_lineto(_path, |
|---|
| 191 | dst_x - 12, |
|---|
| 192 | dst_y - 4); |
|---|
| 193 | |
|---|
| 194 | gnome_canvas_path_def_moveto(_path, dst_x, dst_y); |
|---|
| 195 | |
|---|
| 196 | gnome_canvas_path_def_lineto(_path, |
|---|
| 197 | dst_x - 12, |
|---|
| 198 | dst_y + 4); |
|---|
| 199 | } |
|---|
| 200 | } |
|---|
| 201 | |
|---|
| 202 | GnomeCanvasBpath* c_obj = _bpath.gobj(); |
|---|
| 203 | gnome_canvas_item_set(GNOME_CANVAS_ITEM(c_obj), "bpath", _path, NULL); |
|---|
| 204 | } |
|---|
| 205 | |
|---|
| 206 | |
|---|
| 207 | /** Set label text displayed next to the edge. |
|---|
| 208 | * |
|---|
| 209 | * Passing the empty string will remove the label. |
|---|
| 210 | */ |
|---|
| 211 | void |
|---|
| 212 | Connection::set_label(const std::string& str) |
|---|
| 213 | { |
|---|
| 214 | if (str != "") { |
|---|
| 215 | if (!_handle) |
|---|
| 216 | _handle = new Handle(*this); |
|---|
| 217 | |
|---|
| 218 | if (!_handle->text) { |
|---|
| 219 | _handle->text = new Gnome::Canvas::Text(*_handle, 0, 0, str); |
|---|
| 220 | _handle->text->property_size_set() = true; |
|---|
| 221 | _handle->text->property_size() = 9000; |
|---|
| 222 | _handle->text->property_weight_set() = true; |
|---|
| 223 | _handle->text->property_weight() = 200; |
|---|
| 224 | _handle->text->property_fill_color_rgba() = _color; |
|---|
| 225 | _handle->text->show(); |
|---|
| 226 | } else { |
|---|
| 227 | _handle->text->property_text() = str; |
|---|
| 228 | } |
|---|
| 229 | |
|---|
| 230 | if (_handle->shape) |
|---|
| 231 | show_handle(true); |
|---|
| 232 | |
|---|
| 233 | _handle->text->raise(1); |
|---|
| 234 | update_location(); |
|---|
| 235 | } else if (_handle) { |
|---|
| 236 | delete _handle->text; |
|---|
| 237 | _handle->text = NULL; |
|---|
| 238 | } |
|---|
| 239 | } |
|---|
| 240 | |
|---|
| 241 | |
|---|
| 242 | void |
|---|
| 243 | Connection::show_handle(bool show) |
|---|
| 244 | { |
|---|
| 245 | if (show) { |
|---|
| 246 | if (!_handle) |
|---|
| 247 | _handle = new Handle(*this); |
|---|
| 248 | |
|---|
| 249 | double handle_width = 8.0; |
|---|
| 250 | double handle_height = 8.0; |
|---|
| 251 | if (_handle->text) { |
|---|
| 252 | handle_width = _handle->text->property_text_width(); |
|---|
| 253 | handle_height = _handle->text->property_text_height(); |
|---|
| 254 | } |
|---|
| 255 | |
|---|
| 256 | // FIXME: slow |
|---|
| 257 | delete _handle->shape; |
|---|
| 258 | |
|---|
| 259 | if (_handle_style != HANDLE_NONE) { |
|---|
| 260 | if (_handle_style == HANDLE_RECT) |
|---|
| 261 | _handle->shape = new Gnome::Canvas::Rect(*_handle, |
|---|
| 262 | -handle_width/2.0 - 1.0, -handle_height/2.0, |
|---|
| 263 | handle_width/2.0 + 1.0, handle_height/2.0); |
|---|
| 264 | else// if (_handle_style == HANDLE_CIRCLE) |
|---|
| 265 | _handle->shape = new Gnome::Canvas::Ellipse(*_handle, |
|---|
| 266 | -handle_width/2.0 - 1.0, -handle_height/2.0, |
|---|
| 267 | handle_width/2.0 + 1.0, handle_height/2.0); |
|---|
| 268 | } |
|---|
| 269 | |
|---|
| 270 | _handle->shape->property_fill_color_rgba() = 0x000000FF; |
|---|
| 271 | _handle->shape->property_outline_color_rgba() = _color; |
|---|
| 272 | _handle->shape->show(); |
|---|
| 273 | _handle->show(); |
|---|
| 274 | |
|---|
| 275 | } else { |
|---|
| 276 | delete _handle; |
|---|
| 277 | _handle = NULL; |
|---|
| 278 | } |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | |
|---|
| 282 | void |
|---|
| 283 | Connection::set_highlighted(bool b) |
|---|
| 284 | { |
|---|
| 285 | if (b) |
|---|
| 286 | _bpath.property_outline_color_rgba() = 0xFF0000FF; |
|---|
| 287 | else |
|---|
| 288 | _bpath.property_outline_color_rgba() = _color; |
|---|
| 289 | } |
|---|
| 290 | |
|---|
| 291 | |
|---|
| 292 | void |
|---|
| 293 | Connection::set_selected(bool selected) |
|---|
| 294 | { |
|---|
| 295 | _selected = selected; |
|---|
| 296 | |
|---|
| 297 | if (selected) { |
|---|
| 298 | _bpath.property_dash() = _canvas.lock()->select_dash(); |
|---|
| 299 | } else { |
|---|
| 300 | _bpath.property_dash() = NULL; |
|---|
| 301 | } |
|---|
| 302 | } |
|---|
| 303 | |
|---|
| 304 | |
|---|
| 305 | /** Overloaded Gnome::Canvas::Item::raise_to_top to ensure src and dst |
|---|
| 306 | * are still above connections (to hide the part behind connected Ellipses). |
|---|
| 307 | */ |
|---|
| 308 | void |
|---|
| 309 | Connection::raise_to_top() |
|---|
| 310 | { |
|---|
| 311 | Gnome::Canvas::Item::raise_to_top(); |
|---|
| 312 | |
|---|
| 313 | // Raise source above us |
|---|
| 314 | boost::shared_ptr<Item> item = boost::dynamic_pointer_cast<Item>(_source.lock()); |
|---|
| 315 | if (item) |
|---|
| 316 | item->raise_to_top(); |
|---|
| 317 | |
|---|
| 318 | // Raise dest above us |
|---|
| 319 | item = boost::dynamic_pointer_cast<Item>(_dest.lock()); |
|---|
| 320 | if (item) |
|---|
| 321 | item->raise_to_top(); |
|---|
| 322 | |
|---|
| 323 | /* Raise the roof |
|---|
| 324 | \o/ |
|---|
| 325 | | |
|---|
| 326 | / \ |
|---|
| 327 | */ |
|---|
| 328 | } |
|---|
| 329 | |
|---|
| 330 | |
|---|
| 331 | void |
|---|
| 332 | Connection::select_tick() |
|---|
| 333 | { |
|---|
| 334 | _bpath.property_dash() = _canvas.lock()->select_dash(); |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | |
|---|
| 338 | void |
|---|
| 339 | Connection::zoom(double z) |
|---|
| 340 | { |
|---|
| 341 | if (_handle && _handle->text) { |
|---|
| 342 | _handle->text->property_size() = static_cast<int>(floor((double)9000.0f * z)); |
|---|
| 343 | } |
|---|
| 344 | } |
|---|
| 345 | |
|---|
| 346 | |
|---|
| 347 | } // namespace FlowCanvas |
|---|
| 348 | |
|---|