diff options
author | David Robillard <d@drobilla.net> | 2008-01-13 17:45:17 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2008-01-13 17:45:17 +0000 |
commit | 2db91c126edc8fcaeda711d35369970576715719 (patch) | |
tree | 5a3a16c77acb7909334c6eb97fd0578c96f276ec /gtk2_ardour/lineset.cc | |
parent | f8e3d652e946a169db0c15e3d1a0d9ed15464e0c (diff) |
Apply MIDI keyboard and "scroomer" patch.
git-svn-id: svn://localhost/ardour2/trunk@2908 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'gtk2_ardour/lineset.cc')
-rw-r--r-- | gtk2_ardour/lineset.cc | 782 |
1 files changed, 782 insertions, 0 deletions
diff --git a/gtk2_ardour/lineset.cc b/gtk2_ardour/lineset.cc new file mode 100644 index 0000000000..46c06705bb --- /dev/null +++ b/gtk2_ardour/lineset.cc @@ -0,0 +1,782 @@ +/* + Copyright (C) 2007 Paul Davis + 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 "lineset.h" +#include "rgb_macros.h" + +#include <libgnomecanvas/libgnomecanvas.h> +#include <libgnomecanvasmm/group.h> +#include <libgnomecanvasmm/canvas.h> + +#include <algorithm> +#include <cmath> +#include <iostream> + +using namespace std; + +namespace Gnome { +namespace Canvas { + +LinesetClass Lineset::lineset_class; + +static const char* overlap_error_str = "Lineset error: Line overlap"; + +Lineset::Line::Line(double c, double w, uint32_t color) + : coord(c) + , width(w) { + UINT_TO_RGBA (color, &r, &g, &b, &a); +} + +/* Constructor for dummy lines that are used only with the coordinate */ +Lineset::Line::Line(double c) + : coord(c) { +} + +void +Lineset::Line::set_color(uint32_t color) { + UINT_TO_RGBA (color, &r, &g, &b, &a); +} + +const Glib::Class& +LinesetClass::init() { + if(!gtype_) { + class_init_func_ = &LinesetClass::class_init_function; + register_derived_type(Item::get_type()); + } + + return *this; +} + +void +LinesetClass::class_init_function(void* g_class, void* class_data) { +} + +Lineset::Lineset(Group& parent, Orientation o) + : Glib::ObjectBase("GnomeCanvasLineset") + , Item(Glib::ConstructParams(lineset_class.init())) + , cached_pos(lines.end()) + , orientation(o) + , x1(*this, "x1", 0.0) + , y1(*this, "y1", 0.0) + , x2(*this, "x2", 0.0) + , y2(*this, "y2", 0.0) + , in_update(false) + , update_region1(1.0) + , update_region2(0.0) + , bounds_changed(false) + , covered1(1.0) // covered1 > covered2 ==> nothing's covered + , covered2(0.0) { + + item_construct(parent); + + property_x1().signal_changed().connect(mem_fun(*this, &Lineset::bounds_need_update)); + property_y1().signal_changed().connect(mem_fun(*this, &Lineset::bounds_need_update)); + property_x2().signal_changed().connect(mem_fun(*this, &Lineset::bounds_need_update)); + property_y2().signal_changed().connect(mem_fun(*this, &Lineset::bounds_need_update)); +} + +Lineset::~Lineset() { +} + +bool +Lineset::line_compare(const Line& a, const Line& b) { + return a.coord < b.coord; +} + +void +Lineset::print_lines() { + for(Lines::iterator it = lines.begin(); it != lines.end(); ++it) { + cerr << " " << it->coord << " " << it->width << " " << (int)it->r << " " << (int)it->g << " " << (int)it->b << " " << (int)it->a << endl; + } +} + +void +Lineset::move_line(double coord, double dest) { + if(coord == dest) { + return; + } + + Lines::iterator it = line_at(coord); + + if(it != lines.end()) { + + + double width = it->width; + it->coord = dest; + + Lines::iterator ins = lower_bound(lines.begin(), lines.end(), *it, line_compare); + + lines.insert(ins, *it); + lines.erase(it); + + if(coord > dest) { + region_needs_update(dest, coord + width); + } + else { + region_needs_update(coord, dest + width); + } + } +} + +void +Lineset::change_line_width(double coord, double width) { + Lines::iterator it = line_at(coord); + + if(it != lines.end()) { + Line& l = *it; + ++it; + + if(it != lines.end()) { + if(l.coord + width > it->coord) { + cerr << overlap_error_str << endl; + return; + } + } + + l.width = width; + region_needs_update(coord, coord + width); + } +} + +void +Lineset::change_line_color(double coord, uint32_t color) { + Lines::iterator it = line_at(coord); + + if(it != lines.end()) { + it->set_color(color); + region_needs_update(it->coord, it->coord + it->width); + } +} + +void +Lineset::add_line(double coord, double width, uint32_t color) { + Line l(coord, width, color); + + Lines::iterator it = std::lower_bound(lines.begin(), lines.end(), l, line_compare); + + /* overlap checking */ + if(it != lines.end()) { + if(l.coord + l.width > it->coord) { + cerr << overlap_error_str << endl; + return; + } + } + if(it != lines.begin()) { + --it; + if(l.coord < it->coord + it->width) { + cerr << overlap_error_str << endl; + return; + } + ++it; + } + + lines.insert(it, l); + region_needs_update(coord, coord + width); +} + +void +Lineset::remove_line(double coord) { + Lines::iterator it = line_at(coord); + + if(it != lines.end()) { + double start = it->coord; + double end = start + it->width; + + lines.erase(it); + + region_needs_update(start, end); + } +} + +void +Lineset::remove_lines(double c1, double c2) { + if(!lines.empty()) { + region_needs_update(c1, c2); + } +} + +void +Lineset::remove_until(double coord) { + if(!lines.empty()) { + double first = lines.front().coord; + + // code + + region_needs_update(first, coord); + } +} + +void +Lineset::remove_from(double coord) { + if(!lines.empty()) { + double last = lines.back().coord + lines.back().width; + + // code + + region_needs_update(coord, last); + } +} + +void +Lineset::clear() { + if(!lines.empty()) { + double coord1 = lines.front().coord; + double coord2 = lines.back().coord + lines.back().width; + + lines.clear(); + region_needs_update(coord1, coord2); + } +} + +/* + * this function is optimized to work faster if we access elements that are adjacent to each other. + * so if a large number of lines are modified, it is wise to modify them in sorted order. + */ +Lineset::Lines::iterator +Lineset::line_at(double coord) { + if(cached_pos != lines.end()) { + if(coord < cached_pos->coord) { + /* backward search */ + while(--cached_pos != lines.end()) { + if(cached_pos->coord <= coord) { + if(cached_pos->coord + cached_pos->width < coord) { + /* coord is between two lines */ + return lines.end(); + } + else { + return cached_pos; + } + } + } + } + else { + /* forward search */ + while(cached_pos != lines.end()) { + if(cached_pos->coord > coord) { + /* we searched past the line that we want, so now see + if the previous line includes the coordinate */ + --cached_pos; + if(cached_pos->coord + cached_pos->width >= coord) { + return cached_pos; + } + else { + return lines.end(); + } + } + ++cached_pos; + } + } + } + else { + /* initialize the cached position */ + Line dummy(coord); + + cached_pos = lower_bound(lines.begin(), lines.end(), dummy, line_compare); + + /* The iterator found should point to the element after the one we want. */ + --cached_pos; + + if(cached_pos != lines.end()) { + if(cached_pos->coord <= coord) { + if(cached_pos->coord + cached_pos->width >= coord) { + return cached_pos; + } + else { + return lines.end(); + } + } + else { + return lines.end(); + } + } + else { + return lines.end(); + } + } + + return lines.end(); +} + +void +Lineset::redraw_request(ArtIRect& r) { + get_canvas()->request_redraw(r.x0, r.y0, r.x1, r.y1); +} + +void +Lineset::redraw_request(ArtDRect& r) { + int x0, y0, x1, y1; + Canvas& cv = *get_canvas(); + + //cerr << "redraw request: " << r.x0 << " " << r.y0 << " " << r.x1 << " " << r.y1 << endl; + + cv.w2c(r.x0, r.y0, x0, y0); + cv.w2c(r.x1, r.y1, x1, y1); + cv.request_redraw(x0, y0, x1, y1); +} + +void +Lineset::update_lines(bool need_redraw) { + //cerr << "update_lines need_redraw=" << need_redraw << endl; + if(!need_redraw) { + update_region1 = 1.0; + update_region2 = 0.0; + return; + } + + if(update_region2 > update_region1) { + ArtDRect redraw; + Lineset::bounds_vfunc(&redraw.x0, &redraw.y0, &redraw.x1, &redraw.y1); + i2w(redraw.x0, redraw.y0); + i2w(redraw.x1, redraw.y1); + + if(orientation == Vertical) { + redraw.x1 = redraw.x0 + update_region2; + redraw.x0 += update_region1; + } + else { + redraw.y1 = redraw.y0 + update_region2; + redraw.y0 += update_region1; + } + redraw_request(redraw); + update_region1 = 1.0; + update_region2 = 0.0; + } + + // if we need to calculate what becomes visible, use some of this + //cv.c2w (0, 0, world_v[X1], world_v[Y1]); + //cv.c2w (cv.get_width(), cv.get_height(), world_v[X2], world_v[Y2]); +} + +/* + * return false if a full redraw request has been made. + * return true if nothing or only parts of the rect area has been requested for redraw + */ +bool +Lineset::update_bounds() { + GnomeCanvasItem* item = GNOME_CANVAS_ITEM(gobj()); + ArtDRect old_b; + ArtDRect new_b; + ArtDRect redraw; + Canvas& cv = *get_canvas(); + + /* store the old bounding box */ + old_b.x0 = item->x1; + old_b.y0 = item->y1; + old_b.x1 = item->x2; + old_b.y1 = item->y2; + Lineset::bounds_vfunc(&new_b.x0, &new_b.y0, &new_b.x1, &new_b.y1); + + i2w(new_b.x0, new_b.y0); + i2w(new_b.x1, new_b.y1); + + item->x1 = new_b.x0; + item->y1 = new_b.y0; + item->x2 = new_b.x1; + item->y2 = new_b.y1; + + /* Update bounding box used in rendering function */ + cv.w2c(new_b.x0, new_b.y0, bbox.x0, bbox.y0); + cv.w2c(new_b.x1, new_b.y1, bbox.x1, bbox.y1); + + /* + * if the first primary axis property (x1 for Vertical, y1 for Horizontal) changed, we must redraw everything, + * because lines are positioned relative to this coordinate. Please excuse the confusion resulting from + * gnome canvas coordinate numbering (1, 2) and libart's (0, 1). + */ + if(orientation == Vertical) { + if(new_b.x0 == old_b.x0) { + /* No need to update everything */ + if(new_b.y0 != old_b.y0) { + redraw.x0 = old_b.x0; + redraw.y0 = min(old_b.y0, new_b.y0); + redraw.x1 = old_b.x1; + redraw.y1 = max(old_b.y0, new_b.y0); + redraw_request(redraw); + } + if(new_b.y1 != old_b.y1) { + redraw.x0 = old_b.x0; + redraw.y0 = min(old_b.y1, new_b.y1); + redraw.x1 = old_b.x1; + redraw.y1 = max(old_b.y1, new_b.y1); + redraw_request(redraw); + } + + if(new_b.x1 > old_b.x1) { + // we have a larger area ==> possibly more lines + request_lines(old_b.x1, new_b.x1); + redraw.x0 = old_b.x1; + redraw.y0 = min(old_b.y0, new_b.y0); + redraw.x1 = new_b.x1; + redraw.y1 = max(old_b.y1, new_b.y1); + redraw_request(redraw); + } + else if(new_b.x1 < old_b.x1) { + remove_lines(new_b.x1, old_b.x1); + redraw.x0 = new_b.x1; + redraw.y0 = min(old_b.y0, new_b.y0); + redraw.x1 = old_b.x1; + redraw.y1 = max(old_b.y1, new_b.y1); + redraw_request(redraw); + } + return true; + } + else { + /* update everything */ + //cerr << "update everything" << endl; + art_drect_union(&redraw, &old_b, &new_b); + redraw_request(redraw); + return false; + } + } + else { + if(new_b.y0 == old_b.y0) { + /* No need to update everything */ + if(new_b.x0 != old_b.x0) { + redraw.y0 = old_b.y0; + redraw.x0 = min(old_b.x0, new_b.x0); + redraw.y1 = old_b.y1; + redraw.x1 = max(old_b.x0, new_b.x0); + redraw_request(redraw); + } + if(new_b.x1 != old_b.x1) { + redraw.y0 = old_b.y0; + redraw.x0 = min(old_b.x1, new_b.x1); + redraw.y1 = old_b.y1; + redraw.x1 = max(old_b.x1, new_b.x1); + redraw_request(redraw); + } + + if(new_b.y1 > old_b.y1) { + // we have a larger area ==> possibly more lines + request_lines(old_b.y1, new_b.y1); + redraw.y0 = old_b.y1; + redraw.x0 = min(old_b.x0, new_b.x0); + redraw.y1 = new_b.y1; + redraw.x1 = max(old_b.x1, new_b.x1); + redraw_request(redraw); + } + else if(new_b.y1 < old_b.y1) { + remove_lines(new_b.y1, old_b.y1); + redraw.y0 = new_b.y1; + redraw.x0 = min(old_b.x0, new_b.x0); + redraw.y1 = old_b.y1; + redraw.x1 = max(old_b.x1, new_b.x1); + redraw_request(redraw); + } + return true; + } + else { + /* update everything */ + art_drect_union(&redraw, &old_b, &new_b); + redraw_request(redraw); + return false; + } + } +} + +/* + * Some key concepts + * don't allow modifying line data outside the update function. We don't want any line data outside the visible view range, + * and view range is only "known" in the update function + */ + +/* + * what to do here? + * 1. find out if any line data has been modified since last update. + * N. find out if the item moved. if it moved, the old bbox and the new bbox need to be updated. + */ +void +Lineset::update_vfunc(double* affine, ArtSVP* clip_path, int flags) { + GnomeCanvasItem* item = GNOME_CANVAS_ITEM(gobj()); + bool lines_need_redraw = true; + + /* + * need to call gnome_canvas_item_update here, to unset the need_update flag. + * but a call to Gnome::Canvas::Item::update_vfunc results in infinite recursion. + * that function is declared in gnome_canvas.c so no way to call it directly: + * Item::update_vfunc(affine, clip_path, flags); + * So just copy the code from that function. This has to be a bug or + * something I haven't figured out. + */ + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_UPDATE); + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_AFFINE); + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_CLIP); + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_VIS); + + //cerr << "update {" << endl; + in_update = true; + + // ahh. We must update bounds no matter what. If the group position changed, + // there is no way that we are notified of that. + + //if(bounds_changed) { + lines_need_redraw = update_bounds(); + bounds_changed = false; + //} + + update_lines(lines_need_redraw); + + in_update = false; + //cerr << "}" << endl; +} + +void +Lineset::draw_vfunc(const Glib::RefPtr<Gdk::Drawable>& drawable, int x, int y, int width, int height) { + cerr << "please don't use the GnomeCanvasLineset item in a non-aa Canvas" << endl; + abort(); +} + +inline void +Lineset::paint_vert(GnomeCanvasBuf* buf, Lineset::Line& line, int x1, int y1, int x2, int y2) { + if(line.width == 1.0) { + PAINT_VERTA(buf, line.r, line.g, line.b, line.a, x1, y1, y2); + } + else { + PAINT_BOX(buf, line.r, line.g, line.b, line.a, x1, y1, x2, y2); + } +} + +inline void +Lineset::paint_horiz(GnomeCanvasBuf* buf, Lineset::Line& line, int x1, int y1, int x2, int y2) { + if(line.width == 1.0) { + PAINT_HORIZA(buf, line.r, line.g, line.b, line.a, x1, x2, y1); + } + else { + PAINT_BOX(buf, line.r, line.g, line.b, line.a, x1, y1, x2, y2); + } +} + +void +Lineset::render_vfunc(GnomeCanvasBuf* buf) { + ArtIRect rect; + int pos0, pos1, offset; + + if (buf->is_bg) { + gnome_canvas_buf_ensure_buf (buf); + buf->is_bg = FALSE; + } + + /* get the rect that we are rendering to */ + art_irect_intersect(&rect, &bbox, &buf->rect); + +#if 0 + /* DEBUG render bounding box for this region. should result in the full + bounding box when all rendering regions are finished */ + PAINT_BOX(buf, 0xaa, 0xaa, 0xff, 0xbb, rect.x0, rect.y0, rect.x1, rect.y1); +#endif + +#if 0 + /* harlequin debugging, shows the rect that is actually drawn, distinct from + rects from other render cycles */ + gint r, g, b, a; + r = random() % 0xff; + g = random() % 0xff; + b = random() % 0xff; + PAINT_BOX(buf, r, g, b, 0x33, rect.x0, rect.y0, rect.x1, rect.y1); +#endif + + if(lines.empty()) { + return; + } + + Lines::iterator it = lines.begin(); + Lines::iterator end = --lines.end(); + + /** + * The first and the last line in this render have to be handled separately from those in between, because those lines + * may be cut off at the ends. + */ + + if(orientation == Vertical) { + offset = bbox.x0; + + // skip parts of lines that are to the right of the buffer, and paint the last line visible + for(; end != lines.end(); --end) { + pos0 = ((int) floor(end->coord)) + offset; + + if(pos0 < rect.x1) { + pos1 = min((pos0 + (int) floor(end->width)), rect.x1); + if(pos0 < rect.x0 && pos1 < rect.x0) { + return; + } + + paint_vert(buf, *end, pos0, rect.y0, pos1, rect.y1); + break; + } + } + + if(end == lines.end()) { + return; + } + + // skip parts of lines that are to the left of the buffer + for(; it != end; ++it) { + pos0 = ((int) floor(it->coord)) + offset; + pos1 = pos0 + ((int) floor(it->width)); + + if(pos1 > rect.x0) { + pos0 = max(pos0, rect.x0); + paint_vert(buf, *it, pos0, rect.y0, pos1, rect.y1); + ++it; + break; + } + } + + // render what's between the first and last lines + for(; it != end; ++it) { + pos0 = ((int) floor(it->coord)) + offset; + pos1 = pos0 + ((int) floor(it->width)); + + paint_vert(buf, *it, pos0, rect.y0, pos1, rect.y1); + } + } + else { + offset = bbox.y0; + + // skip parts of lines that are to the right of the buffer, and paint the last line visible + for(; end != lines.end(); --end) { + pos0 = ((int) floor(end->coord)) + offset; + + if(pos0 < rect.y1) { + pos1 = min((pos0 + (int) floor(end->width)), rect.y1); + if(pos0 < rect.y0 && pos1 < rect.y0) { + return; + } + + paint_horiz(buf, *end, rect.x0, pos0, rect.x1, pos1); + break; + } + } + + if(end == lines.end()) { + return; + } + + // skip parts of lines that are to the left of the buffer + for(; it != end; ++it) { + pos0 = ((int) floor(it->coord)) + offset; + pos1 = pos0 + ((int) floor(it->width)); + + if(pos1 > rect.y0) { + pos0 = max(pos0, rect.y0); + paint_horiz(buf, *it, rect.x0, pos0, rect.x1, pos1); + ++it; + break; + } + } + + // render what's between the first and last lines + for(; it != end; ++it) { + pos0 = ((int) floor(it->coord)) + offset; + pos1 = pos0 + ((int) floor(it->width)); + paint_horiz(buf, *it, rect.x0, pos0, rect.x1, pos1); + } + } +} + +void +Lineset::bounds_vfunc(double* _x1, double* _y1, double* _x2, double* _y2) { + *_x1 = x1; + *_y1 = y1; + *_x2 = x2 + 1; + *_y2 = y2 + 1; +} + + +double +Lineset::point_vfunc(double x, double y, int cx, int cy, GnomeCanvasItem** actual_item) { + double x1, y1, x2, y2; + double dx, dy; + + Lineset::bounds_vfunc(&x1, &y1, &x2, &y2); + + *actual_item = gobj(); + + if (x < x1) { + dx = x1 - x; + } + else if(x > x2) { + dx = x - x2; + } + else { + dx = 0.0; + } + + if (y < y1) { + dy = y1 - y; + } + else if(y > y2) { + dy = y - y2; + } + else { + if (dx == 0.0) { + // point is inside + return 0.0; + } + else { + dy = 0.0; + } + } + + return sqrt (dx * dx + dy * dy); +} + +/* If not overrided emit the signal */ +void +Lineset::request_lines(double c1, double c2) { + signal_request_lines(*this, c1, c2); +} + +void +Lineset::bounds_need_update() { + bounds_changed = true; + + if(!in_update) { + request_update(); + } +} + +void +Lineset::region_needs_update(double coord1, double coord2) { + if(update_region1 > update_region2) { + update_region1 = coord1; + update_region2 = coord2; + } + else { + update_region1 = min(update_region1, coord1); + update_region2 = max(update_region2, coord2); + } + + if(!in_update) { + request_update(); + } +} + +/* + * These have been defined to avoid endless recursion with gnomecanvasmm. + * Don't know why this happens + */ +bool Lineset::on_event(GdkEvent* p1) { + return false; +} +void Lineset::realize_vfunc() { } +void Lineset::unrealize_vfunc() { } +void Lineset::map_vfunc() { } +void Lineset::unmap_vfunc() { } + +} /* namespace Canvas */ +} /* namespace Gnome */ |