Items no longer need a parent group (they require a Canvas pointer instead), so all constructors have been rationalized and have two variants, one with a parent and one with a canvas. All Items now inherit from Fill and Outline, to banish diagonal inheritance and virtual base classes and all that. There were zero changes to the Ardour GUI arising from these changes.
396 lines
7.8 KiB
C++
396 lines
7.8 KiB
C++
/*
|
|
Copyright (C) 2011-2013 Paul Davis
|
|
Author: Carl Hetherington <cth@carlh.net>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <iostream>
|
|
#include <cairomm/context.h>
|
|
|
|
#include "pbd/stacktrace.h"
|
|
#include "pbd/compose.h"
|
|
|
|
#include "canvas/group.h"
|
|
#include "canvas/types.h"
|
|
#include "canvas/debug.h"
|
|
#include "canvas/item.h"
|
|
#include "canvas/canvas.h"
|
|
|
|
using namespace std;
|
|
using namespace ArdourCanvas;
|
|
|
|
int Group::default_items_per_cell = 64;
|
|
|
|
|
|
Group::Group (Canvas* canvas)
|
|
: Item (canvas)
|
|
, _lut (0)
|
|
{
|
|
}
|
|
|
|
Group::Group (Group* group)
|
|
: Item (group)
|
|
, _lut (0)
|
|
{
|
|
}
|
|
|
|
Group::Group (Group* group, Duple const& p)
|
|
: Item (group, p)
|
|
, _lut (0)
|
|
{
|
|
}
|
|
|
|
Group::~Group ()
|
|
{
|
|
clear_items (true);
|
|
}
|
|
|
|
/** @param area Area to draw in window coordinates.
|
|
* @param context Context, set up with its origin at this group's position.
|
|
*/
|
|
void
|
|
Group::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
|
|
{
|
|
ensure_lut ();
|
|
vector<Item*> items = _lut->get (area);
|
|
|
|
#ifdef CANVAS_DEBUG
|
|
if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
|
|
cerr << string_compose ("%1GROUP %2 @ %7 render %5 @ %6 %3 items out of %4\n",
|
|
_canvas->render_indent(), (name.empty() ? string ("[unnamed]") : name), items.size(), _items.size(), area, _position, this);
|
|
}
|
|
#endif
|
|
|
|
++render_depth;
|
|
|
|
for (vector<Item*>::const_iterator i = items.begin(); i != items.end(); ++i) {
|
|
|
|
if (!(*i)->visible ()) {
|
|
#ifdef CANVAS_DEBUG
|
|
if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
|
|
cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] invisible - skipped\n";
|
|
}
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
boost::optional<Rect> item_bbox = (*i)->bounding_box ();
|
|
|
|
if (!item_bbox) {
|
|
#ifdef CANVAS_DEBUG
|
|
if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
|
|
cerr << _canvas->render_indent() << "Item " << (*i)->whatami() << " [" << (*i)->name << "] empty - skipped\n";
|
|
}
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
Rect item = (*i)->item_to_window (item_bbox.get());
|
|
boost::optional<Rect> d = item.intersection (area);
|
|
|
|
if (d) {
|
|
Rect draw = d.get();
|
|
if (draw.width() && draw.height()) {
|
|
#ifdef CANVAS_DEBUG
|
|
if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
|
|
if (dynamic_cast<Group*>(*i) == 0) {
|
|
cerr << _canvas->render_indent() << "render "
|
|
<< ' '
|
|
<< (*i)
|
|
<< ' '
|
|
<< (*i)->whatami()
|
|
<< ' '
|
|
<< (*i)->name
|
|
<< " item "
|
|
<< item_bbox.get()
|
|
<< " window = "
|
|
<< item
|
|
<< " intersect = "
|
|
<< draw
|
|
<< " @ "
|
|
<< _position
|
|
<< endl;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
(*i)->render (area, context);
|
|
++render_count;
|
|
}
|
|
|
|
} else {
|
|
|
|
#ifdef CANVAS_DEBUG
|
|
if (DEBUG_ENABLED(PBD::DEBUG::CanvasRender)) {
|
|
cerr << string_compose ("%1skip render of %2 %3, no intersection between %4 and %5\n", _canvas->render_indent(), (*i)->whatami(),
|
|
(*i)->name, item, area);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
--render_depth;
|
|
}
|
|
|
|
void
|
|
Group::scroll_to (Duple const& d)
|
|
{
|
|
Item::scroll_to (d);
|
|
|
|
for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ++i) {
|
|
(*i)->scroll_to (d);
|
|
}
|
|
}
|
|
|
|
void
|
|
Group::compute_bounding_box () const
|
|
{
|
|
Rect bbox;
|
|
bool have_one = false;
|
|
|
|
for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
|
|
|
|
boost::optional<Rect> item_bbox = (*i)->bounding_box ();
|
|
|
|
if (!item_bbox) {
|
|
continue;
|
|
}
|
|
|
|
Rect group_bbox = (*i)->item_to_parent (item_bbox.get ());
|
|
if (have_one) {
|
|
bbox = bbox.extend (group_bbox);
|
|
} else {
|
|
bbox = group_bbox;
|
|
have_one = true;
|
|
}
|
|
}
|
|
|
|
if (!have_one) {
|
|
_bounding_box = boost::optional<Rect> ();
|
|
} else {
|
|
_bounding_box = bbox;
|
|
}
|
|
|
|
_bounding_box_dirty = false;
|
|
}
|
|
|
|
void
|
|
Group::add (Item* i)
|
|
{
|
|
/* XXX should really notify canvas about this */
|
|
|
|
_items.push_back (i);
|
|
i->reparent (this);
|
|
invalidate_lut ();
|
|
_bounding_box_dirty = true;
|
|
}
|
|
|
|
void
|
|
Group::remove (Item* i)
|
|
{
|
|
|
|
if (i->parent() != this) {
|
|
return;
|
|
}
|
|
|
|
/* we cannot call bounding_box() here because that will iterate over
|
|
_items, one of which (the argument, i) may be in the middle of
|
|
deletion, making it impossible to call compute_bounding_box()
|
|
on it.
|
|
*/
|
|
|
|
if (_bounding_box) {
|
|
_pre_change_bounding_box = _bounding_box;
|
|
} else {
|
|
_pre_change_bounding_box = Rect();
|
|
}
|
|
|
|
i->unparent ();
|
|
_items.remove (i);
|
|
invalidate_lut ();
|
|
_bounding_box_dirty = true;
|
|
|
|
end_change ();
|
|
}
|
|
|
|
void
|
|
Group::clear (bool with_delete)
|
|
{
|
|
begin_change ();
|
|
|
|
clear_items (with_delete);
|
|
|
|
invalidate_lut ();
|
|
_bounding_box_dirty = true;
|
|
|
|
end_change ();
|
|
}
|
|
|
|
void
|
|
Group::clear_items (bool with_delete)
|
|
{
|
|
for (list<Item*>::iterator i = _items.begin(); i != _items.end(); ) {
|
|
|
|
list<Item*>::iterator tmp = i;
|
|
Item *item = *i;
|
|
|
|
++tmp;
|
|
|
|
/* remove from list before doing anything else, because we
|
|
* don't want to find the item in _items during any activity
|
|
* driven by unparent-ing or deletion.
|
|
*/
|
|
|
|
_items.erase (i);
|
|
item->unparent ();
|
|
|
|
if (with_delete) {
|
|
delete item;
|
|
}
|
|
|
|
i = tmp;
|
|
}
|
|
}
|
|
|
|
void
|
|
Group::raise_child_to_top (Item* i)
|
|
{
|
|
if (!_items.empty()) {
|
|
if (_items.back() == i) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
_items.remove (i);
|
|
_items.push_back (i);
|
|
invalidate_lut ();
|
|
}
|
|
|
|
void
|
|
Group::raise_child (Item* i, int levels)
|
|
{
|
|
list<Item*>::iterator j = find (_items.begin(), _items.end(), i);
|
|
assert (j != _items.end ());
|
|
|
|
++j;
|
|
_items.remove (i);
|
|
|
|
while (levels > 0 && j != _items.end ()) {
|
|
++j;
|
|
--levels;
|
|
}
|
|
|
|
_items.insert (j, i);
|
|
invalidate_lut ();
|
|
}
|
|
|
|
void
|
|
Group::lower_child_to_bottom (Item* i)
|
|
{
|
|
if (!_items.empty()) {
|
|
if (_items.front() == i) {
|
|
return;
|
|
}
|
|
}
|
|
_items.remove (i);
|
|
_items.push_front (i);
|
|
invalidate_lut ();
|
|
}
|
|
|
|
void
|
|
Group::ensure_lut () const
|
|
{
|
|
if (!_lut) {
|
|
_lut = new DumbLookupTable (*this);
|
|
}
|
|
}
|
|
|
|
void
|
|
Group::invalidate_lut () const
|
|
{
|
|
delete _lut;
|
|
_lut = 0;
|
|
}
|
|
|
|
void
|
|
Group::child_changed ()
|
|
{
|
|
invalidate_lut ();
|
|
_bounding_box_dirty = true;
|
|
|
|
if (_parent) {
|
|
_parent->child_changed ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Group::add_items_at_point (Duple const point, vector<Item const *>& items) const
|
|
{
|
|
boost::optional<Rect> const bbox = bounding_box ();
|
|
|
|
/* Point is in window coordinate system */
|
|
|
|
if (!bbox || !item_to_window (bbox.get()).contains (point)) {
|
|
return;
|
|
}
|
|
|
|
/* now recurse and add any items within our group that contain point */
|
|
|
|
ensure_lut ();
|
|
vector<Item*> our_items = _lut->items_at_point (point);
|
|
|
|
if (!our_items.empty()) {
|
|
/* this adds this group itself to the list of items at point */
|
|
Item::add_items_at_point (point, items);
|
|
}
|
|
|
|
for (vector<Item*>::iterator i = our_items.begin(); i != our_items.end(); ++i) {
|
|
(*i)->add_items_at_point (point, items);
|
|
}
|
|
}
|
|
|
|
void
|
|
Group::dump (ostream& o) const
|
|
{
|
|
#ifdef CANVAS_DEBUG
|
|
o << _canvas->indent();
|
|
o << "Group " << this << " [" << name << ']';
|
|
o << " @ " << position();
|
|
o << " Items: " << _items.size();
|
|
o << " Visible ? " << _visible;
|
|
|
|
boost::optional<Rect> bb = bounding_box();
|
|
|
|
if (bb) {
|
|
o << endl << _canvas->indent() << " bbox: " << bb.get();
|
|
o << endl << _canvas->indent() << " CANVAS bbox: " << item_to_canvas (bb.get());
|
|
} else {
|
|
o << " bbox unset";
|
|
}
|
|
|
|
o << endl;
|
|
#endif
|
|
|
|
ArdourCanvas::dump_depth++;
|
|
|
|
for (list<Item*>::const_iterator i = _items.begin(); i != _items.end(); ++i) {
|
|
o << **i;
|
|
}
|
|
|
|
ArdourCanvas::dump_depth--;
|
|
}
|