2 Copyright (C) 2011 Paul Davis
3 Author: Carl Hetherington <cth@carlh.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 /** @file canvas/canvas.cc
22 * @brief Implementation of the main canvas classes.
26 #include <gtkmm/adjustment.h>
28 #include "pbd/xml++.h"
29 #include "pbd/compose.h"
30 #include "pbd/stacktrace.h"
32 #include "canvas/canvas.h"
33 #include "canvas/debug.h"
36 using namespace ArdourCanvas;
38 /** Construct a new Canvas */
46 /** Construct a new Canvas from an XML tree
47 * @param tree XML Tree.
49 Canvas::Canvas (XMLTree const * tree)
55 /* XXX: little bit hacky */
56 _root.set_state (tree->root()->child ("Group"));
58 XMLNodeList const & children = tree->root()->children ();
59 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
60 if ((*i)->name() == ("Render")) {
63 atof ((*i)->property ("x0")->value().c_str()),
64 atof ((*i)->property ("y0")->value().c_str()),
65 atof ((*i)->property ("x1")->value().c_str()),
66 atof ((*i)->property ("x1")->value().c_str())
73 /** Render an area of the canvas.
74 * @param area Area in canvas coordinates.
75 * @param context Cairo context to render to.
78 Canvas::render (Rect const & area, Cairo::RefPtr<Cairo::Context> const & context) const
80 cerr << "CANVAS @ " << this << endl;
82 cerr << "-------------------------\n";
84 checkpoint ("render", "-> render");
89 /* clip to the requested area */
90 context->rectangle (area.x0, area.y0, area.width(), area.height());
93 boost::optional<Rect> root_bbox = _root.bounding_box();
95 /* the root has no bounding box, so there's nothing to render */
96 checkpoint ("render", "no root bbox");
101 boost::optional<Rect> draw = root_bbox.get().intersection (area);
103 /* there's a common area between the root and the requested
106 checkpoint ("render", "... root");
107 _root.render (*draw, context);
111 _renders.push_back (area);
116 cout << "Rendered " << render_count << "\n";
117 checkpoint ("render", "<- render");
121 operator<< (ostream& o, Canvas& c)
128 Canvas::indent() const
132 for (int n = 0; n < ArdourCanvas::dump_depth; ++n) {
140 Canvas::dump (ostream& o) const
146 /** Called when an item has been shown or hidden.
147 * @param item Item that has been shown or hidden.
150 Canvas::item_shown_or_hidden (Item* item)
152 boost::optional<Rect> bbox = item->bounding_box ();
154 queue_draw_item_area (item, bbox.get ());
158 /** Called when an item has changed, but not moved.
159 * @param item Item that has changed.
160 * @param pre_change_bounding_box The bounding box of item before the change,
161 * in the item's coordinates.
164 Canvas::item_changed (Item* item, boost::optional<Rect> pre_change_bounding_box)
166 if (pre_change_bounding_box) {
167 /* request a redraw of the item's old bounding box */
168 queue_draw_item_area (item, pre_change_bounding_box.get ());
171 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
172 if (post_change_bounding_box) {
173 /* request a redraw of the item's new bounding box */
174 queue_draw_item_area (item, post_change_bounding_box.get ());
178 /** Called when an item has moved.
179 * @param item Item that has moved.
180 * @param pre_change_parent_bounding_box The bounding box of the item before
181 * the move, in its parent's coordinates.
184 Canvas::item_moved (Item* item, boost::optional<Rect> pre_change_parent_bounding_box)
186 if (pre_change_parent_bounding_box) {
187 /* request a redraw of where the item used to be; we have to use the
188 parent's coordinates here as item bounding boxes do not change
191 queue_draw_item_area (item->parent(), pre_change_parent_bounding_box.get ());
194 boost::optional<Rect> post_change_bounding_box = item->bounding_box ();
195 if (post_change_bounding_box) {
196 /* request a redraw of where the item now is */
197 queue_draw_item_area (item, post_change_bounding_box.get ());
201 /** Request a redraw of a particular area in an item's coordinates.
203 * @param area Area to redraw in the item's coordinates.
206 Canvas::queue_draw_item_area (Item* item, Rect area)
208 request_redraw (item->item_to_canvas (area));
211 /** @return An XML description of the canvas and its objects */
213 Canvas::get_state () const
215 XMLTree* tree = new XMLTree ();
216 XMLNode* node = new XMLNode ("Canvas");
217 node->add_child_nocopy (*_root.get_state ());
219 for (list<Rect>::const_iterator i = _renders.begin(); i != _renders.end(); ++i) {
220 XMLNode* render = new XMLNode ("Render");
221 render->add_property ("x0", string_compose ("%1", i->x0));
222 render->add_property ("y0", string_compose ("%1", i->y0));
223 render->add_property ("x1", string_compose ("%1", i->x1));
224 render->add_property ("y1", string_compose ("%1", i->y1));
225 node->add_child_nocopy (*render);
228 tree->set_root (node);
232 /** Construct a GtkCanvas */
233 GtkCanvas::GtkCanvas ()
237 /* these are the events we want to know about */
238 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK);
241 /** Construct a GtkCanvas from an XML tree.
242 * @param tree XML Tree.
244 GtkCanvas::GtkCanvas (XMLTree const * tree)
249 /* these are the events we want to know about */
250 add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK);
253 /** Handler for button presses on the canvas.
254 * @param ev GDK event.
257 GtkCanvas::button_handler (GdkEventButton* ev)
259 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas button press %1 %1\n", ev->x, ev->y));
260 /* The Duple that we are passing in here is in canvas coordinates */
261 return deliver_event (Duple (ev->x, ev->y), reinterpret_cast<GdkEvent*> (ev));
264 /** Handler for pointer motion events on the canvas.
265 * @param ev GDK event.
266 * @return true if the motion event was handled, otherwise false.
269 GtkCanvas::motion_notify_handler (GdkEventMotion* ev)
272 /* if we have a grabbed item, it gets just the motion event,
273 since no enter/leave events can have happened.
275 return _grabbed_item->Event (reinterpret_cast<GdkEvent*> (ev));
278 /* This is in canvas coordinates */
279 Duple point (ev->x, ev->y);
281 /* find the items at the new mouse position */
282 vector<Item const *> items;
283 _root.add_items_at_point (point, items);
285 Item const * new_item = items.empty() ? 0 : items.back ();
287 if (_current_item && _current_item != new_item) {
289 GdkEventCrossing leave_event;
290 leave_event.type = GDK_LEAVE_NOTIFY;
291 leave_event.x = ev->x;
292 leave_event.y = ev->y;
293 _current_item->Event (reinterpret_cast<GdkEvent*> (&leave_event));
296 if (new_item && _current_item != new_item) {
298 GdkEventCrossing enter_event;
299 enter_event.type = GDK_ENTER_NOTIFY;
300 enter_event.x = ev->x;
301 enter_event.y = ev->y;
302 new_item->Event (reinterpret_cast<GdkEvent*> (&enter_event));
305 _current_item = new_item;
307 /* Now deliver the motion event. It may seem a little inefficient
308 to recompute the items under the event, but the enter notify/leave
309 events may have deleted canvas items so it is important to
310 recompute the list in deliver_event.
312 return deliver_event (point, reinterpret_cast<GdkEvent*> (ev));
315 /** Deliver an event to the appropriate item; either the grabbed item, or
316 * one of the items underneath the event.
317 * @param point Position that the event has occurred at, in canvas coordinates.
318 * @param event The event.
321 GtkCanvas::deliver_event (Duple point, GdkEvent* event)
324 /* we have a grabbed item, so everything gets sent there */
325 return _grabbed_item->Event (event);
328 /* find the items that exist at the event's position */
329 vector<Item const *> items;
330 _root.add_items_at_point (point, items);
332 /* run through the items under the event, from top to bottom, until one claims the event */
333 vector<Item const *>::const_reverse_iterator i = items.rbegin ();
334 while (i != items.rend()) {
336 if ((*i)->ignore_events ()) {
341 if ((*i)->Event (event)) {
342 /* this item has just handled the event */
344 PBD::DEBUG::CanvasEvents,
345 string_compose ("canvas event handled by %1\n", (*i)->name.empty() ? "[unknown]" : (*i)->name)
352 PBD::DEBUG::CanvasEvents,
353 string_compose ("canvas event ignored by %1\n", (*i)->name.empty() ? "[unknown]" : (*i)->name)
360 if (PBD::debug_bits & PBD::DEBUG::CanvasEvents) {
361 while (i != items.rend()) {
362 DEBUG_TRACE (PBD::DEBUG::CanvasEvents, string_compose ("canvas event not seen by %1\n", (*i)->name.empty() ? "[unknown]" : (*i)->name));
370 /** Called when an item is being destroyed.
371 * @param item Item being destroyed.
372 * @param bounding_box Last known bounding box of the item.
375 GtkCanvas::item_going_away (Item* item, boost::optional<Rect> bounding_box)
378 queue_draw_item_area (item, bounding_box.get ());
381 if (_current_item == item) {
385 if (_grabbed_item == item) {
390 /** Construct an ImageCanvas.
391 * @param size Size in pixels.
393 ImageCanvas::ImageCanvas (Duple size)
394 : _surface (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, size.x, size.y))
396 _context = Cairo::Context::create (_surface);
399 /** Construct an ImageCanvas from an XML tree.
400 * @param tree XML Tree.
401 * @param size Size in pixels.
403 ImageCanvas::ImageCanvas (XMLTree const * tree, Duple size)
405 , _surface (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, size.x, size.y))
407 _context = Cairo::Context::create (_surface);
410 /** Render the canvas to our pixbuf.
411 * @param area Area to render, in canvas coordinates.
414 ImageCanvas::render_to_image (Rect const & area) const
416 render (area, _context);
419 /** Write our pixbuf to a PNG file.
420 * @param f PNG file name.
423 ImageCanvas::write_to_png (string const & f)
426 _surface->write_to_png (f);
429 /** @return Our Cairo context */
430 Cairo::RefPtr<Cairo::Context>
431 ImageCanvas::context ()
436 /** Handler for GDK expose events.
438 * @return true if the event was handled.
441 GtkCanvas::on_expose_event (GdkEventExpose* ev)
443 Cairo::RefPtr<Cairo::Context> c = get_window()->create_cairo_context ();
444 render (Rect (ev->area.x, ev->area.y, ev->area.x + ev->area.width, ev->area.y + ev->area.height), c);
448 /** @return Our Cairo context, or 0 if we don't have one */
449 Cairo::RefPtr<Cairo::Context>
450 GtkCanvas::context ()
452 Glib::RefPtr<Gdk::Window> w = get_window ();
454 return Cairo::RefPtr<Cairo::Context> ();
457 return w->create_cairo_context ();
460 /** Handler for GDK button press events.
462 * @return true if the event was handled.
465 GtkCanvas::on_button_press_event (GdkEventButton* ev)
467 /* Coordinates in the event will be canvas coordinates, correctly adjusted
468 for scroll if this GtkCanvas is in a GtkCanvasViewport.
470 return button_handler (ev);
473 /** Handler for GDK button release events.
475 * @return true if the event was handled.
478 GtkCanvas::on_button_release_event (GdkEventButton* ev)
480 /* Coordinates in the event will be canvas coordinates, correctly adjusted
481 for scroll if this GtkCanvas is in a GtkCanvasViewport.
483 return button_handler (ev);
486 /** Handler for GDK motion events.
488 * @return true if the event was handled.
491 GtkCanvas::on_motion_notify_event (GdkEventMotion* ev)
493 /* Coordinates in the event will be canvas coordinates, correctly adjusted
494 for scroll if this GtkCanvas is in a GtkCanvasViewport.
496 return motion_notify_handler (ev);
499 /** Called to request a redraw of our canvas.
500 * @param area Area to redraw, in canvas coordinates.
503 GtkCanvas::request_redraw (Rect const & area)
505 queue_draw_area (floor (area.x0), floor (area.y0), ceil (area.x1) - floor (area.x0), ceil (area.y1) - floor (area.y0));
508 /** Called to request that we try to get a particular size for ourselves.
509 * @param size Size to request, in pixels.
512 GtkCanvas::request_size (Duple size)
516 if (req.x > INT_MAX) {
520 if (req.y > INT_MAX) {
524 set_size_request (req.x, req.y);
527 /** `Grab' an item, so that all events are sent to that item until it is `ungrabbed'.
528 * This is typically used for dragging items around, so that they are grabbed during
530 * @param item Item to grab.
533 GtkCanvas::grab (Item* item)
535 /* XXX: should this be doing gdk_pointer_grab? */
536 _grabbed_item = item;
539 /** `Ungrab' any item that was previously grabbed */
543 /* XXX: should this be doing gdk_pointer_ungrab? */
547 /** Create a GtkCanvaSViewport.
548 * @param hadj Adjustment to use for horizontal scrolling.
549 * @param vadj Adjustment to use for vertica scrolling.
551 GtkCanvasViewport::GtkCanvasViewport (Gtk::Adjustment& hadj, Gtk::Adjustment& vadj)
552 : Viewport (hadj, vadj)
557 /** Handler for when GTK asks us what minimum size we want.
558 * @param req Requsition to fill in.
561 GtkCanvasViewport::on_size_request (Gtk::Requisition* req)
567 /** Convert window coordinates to canvas coordinates by taking into account
568 * where we are scrolled to.
569 * @param wx Window x.
570 * @param wy Window y.
571 * @param cx Filled in with canvas x.
572 * @param cy Filled in with canvas y.
575 GtkCanvasViewport::window_to_canvas (int wx, int wy, Coord& cx, Coord& cy) const
577 cx = wx + get_hadjustment()->get_value ();
578 cy = wy + get_vadjustment()->get_value ();
581 /** @return The visible area of the canvas, in canvas coordinates */
583 GtkCanvasViewport::visible_area () const
585 Distance const xo = get_hadjustment()->get_value ();
586 Distance const yo = get_vadjustment()->get_value ();
587 return Rect (xo, yo, xo + get_allocation().get_width (), yo + get_allocation().get_height ());