summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2008-01-13 17:45:17 +0000
committerDavid Robillard <d@drobilla.net>2008-01-13 17:45:17 +0000
commit2db91c126edc8fcaeda711d35369970576715719 (patch)
tree5a3a16c77acb7909334c6eb97fd0578c96f276ec
parentf8e3d652e946a169db0c15e3d1a0d9ed15464e0c (diff)
Apply MIDI keyboard and "scroomer" patch.
git-svn-id: svn://localhost/ardour2/trunk@2908 d708f5d6-7413-0410-9779-e7cbd77b26cf
-rw-r--r--gtk2_ardour/SConscript3
-rw-r--r--gtk2_ardour/ardour2_ui_default.conf6
-rw-r--r--gtk2_ardour/canvas.h1
-rw-r--r--gtk2_ardour/canvas_vars.h4
-rw-r--r--gtk2_ardour/editor.cc1
-rw-r--r--gtk2_ardour/lineset.cc782
-rw-r--r--gtk2_ardour/lineset.h208
-rw-r--r--gtk2_ardour/midi_region_view.cc14
-rw-r--r--gtk2_ardour/midi_region_view.h2
-rw-r--r--gtk2_ardour/midi_scroomer.cc164
-rw-r--r--gtk2_ardour/midi_scroomer.h39
-rw-r--r--gtk2_ardour/midi_streamview.cc124
-rw-r--r--gtk2_ardour/midi_streamview.h15
-rw-r--r--gtk2_ardour/midi_time_axis.cc11
-rw-r--r--gtk2_ardour/midi_time_axis.h6
-rw-r--r--gtk2_ardour/piano_roll_header.cc653
-rw-r--r--gtk2_ardour/piano_roll_header.h104
-rw-r--r--gtk2_ardour/time_axis_view.cc24
-rw-r--r--gtk2_ardour/time_axis_view.h1
-rw-r--r--libs/gtkmm2ext/SConscript1
-rw-r--r--libs/gtkmm2ext/gtkmm2ext/scroomer.h88
-rw-r--r--libs/gtkmm2ext/scroomer.cc393
22 files changed, 2596 insertions, 48 deletions
diff --git a/gtk2_ardour/SConscript b/gtk2_ardour/SConscript
index 4d34a46176..f03286243b 100644
--- a/gtk2_ardour/SConscript
+++ b/gtk2_ardour/SConscript
@@ -123,12 +123,14 @@ automation_region_view.cc
bundle_manager.cc
midi_port_dialog.cc
midi_time_axis.cc
+midi_scroomer.cc
midi_streamview.cc
axis_view.cc
canvas-simpleline.c
simpleline.cc
canvas-simplerect.c
simplerect.cc
+lineset.cc
canvas-waveview.c
diamond.cc
canvas-midi-event.cc
@@ -190,6 +192,7 @@ opts.cc
panner.cc
panner2d.cc
panner_ui.cc
+piano_roll_header.cc
playlist_selector.cc
plugin_selector.cc
plugin_ui.cc
diff --git a/gtk2_ardour/ardour2_ui_default.conf b/gtk2_ardour/ardour2_ui_default.conf
index 80f53f7181..c45e4da9c6 100644
--- a/gtk2_ardour/ardour2_ui_default.conf
+++ b/gtk2_ardour/ardour2_ui_default.conf
@@ -7,7 +7,8 @@
<Option name="waveform" value="000000cc"/>
<Option name="clipped waveform" value="ff0000e5"/>
<Option name="region base" value="bfbfc1aa"/>
- <Option name="selected region base" value="8888ffaa"/>
+ <Option name="selected region base" value="b591a8ff"/>
+ <Option name="midi frame base" value="698f9d6d"/>
<Option name="audio track base" value="c6d3d868"/>
<Option name="audio bus base" value="dbd1ea68"/>
<Option name="midi track base" value="c67e7e5f"/>
@@ -100,5 +101,8 @@
<Option name="midi note fill mid" value="eeee338a"/>
<Option name="midi note fill max" value="ee33338a"/>
<Option name="midi note selected outline" value="5566ffee"/>
+ <Option name="piano roll white" value="aa585865"/>
+ <Option name="piano roll black" value="88393b6b"/>
+ <Option name="piano roll black outline" value="b5b5b576"/>
</Canvas>
</Ardour>
diff --git a/gtk2_ardour/canvas.h b/gtk2_ardour/canvas.h
index 75cb7157a9..9fa737258c 100644
--- a/gtk2_ardour/canvas.h
+++ b/gtk2_ardour/canvas.h
@@ -33,6 +33,7 @@ namespace Gnome {
class Line;
class Points;
class ImageFrame;
+ class Lineset;
}
}
diff --git a/gtk2_ardour/canvas_vars.h b/gtk2_ardour/canvas_vars.h
index 7d291b7a5d..bc17a1d988 100644
--- a/gtk2_ardour/canvas_vars.h
+++ b/gtk2_ardour/canvas_vars.h
@@ -2,6 +2,7 @@ CANVAS_VARIABLE(canvasvar_WaveForm, "waveform")
CANVAS_VARIABLE(canvasvar_WaveFormClip, "clipped waveform")
CANVAS_VARIABLE(canvasvar_FrameBase, "region base")
CANVAS_VARIABLE(canvasvar_SelectedFrameBase, "selected region base")
+CANVAS_VARIABLE(canvasvar_MidiFrameBase, "midi frame base")
CANVAS_VARIABLE(canvasvar_AudioTrackBase, "audio track base")
CANVAS_VARIABLE(canvasvar_AudioBusBase, "audio bus base")
CANVAS_VARIABLE(canvasvar_MidiTrackBase, "midi track base")
@@ -94,3 +95,6 @@ CANVAS_VARIABLE(canvasvar_MidiNoteFillMin, "midi note fill min")
CANVAS_VARIABLE(canvasvar_MidiNoteFillMid, "midi note fill mid")
CANVAS_VARIABLE(canvasvar_MidiNoteFillMax, "midi note fill max")
CANVAS_VARIABLE(canvasvar_MidiNoteSelectedOutline, "midi note selected outline")
+CANVAS_VARIABLE(canvasvar_PianoRollWhite, "piano roll white")
+CANVAS_VARIABLE(canvasvar_PianoRollBlack, "piano roll black")
+CANVAS_VARIABLE(canvasvar_PianoRollBlackOutline, "piano roll black outline")
diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc
index 41261b7079..3ff52d69e7 100644
--- a/gtk2_ardour/editor.cc
+++ b/gtk2_ardour/editor.cc
@@ -82,6 +82,7 @@
#include "actions.h"
#include "sfdb_ui.h"
#include "gui_thread.h"
+#include "simpleline.h"
#ifdef FFT_ANALYSIS
#include "analysis_window.h"
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 */
diff --git a/gtk2_ardour/lineset.h b/gtk2_ardour/lineset.h
new file mode 100644
index 0000000000..a5d0516139
--- /dev/null
+++ b/gtk2_ardour/lineset.h
@@ -0,0 +1,208 @@
+/*
+ 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.
+*/
+
+#ifndef __gnome_canvas_lineset_h__
+#define __gnome_canvas_lineset_h__
+
+#include <libgnomecanvasmm/item.h>
+
+namespace Gnome {
+namespace Canvas {
+
+class LinesetClass : public Glib::Class {
+public:
+ const Glib::Class& init();
+ static void class_init_function(void* g_class, void* class_data);
+};
+
+/**
+ * A canvas item that displays a list of lines vertically or horizontally,
+ * spanning the entire size of the item.
+ */
+class Lineset : public Item {
+public:
+ enum Orientation {
+ Vertical,
+ Horizontal
+ };
+
+ Lineset(Group& parent, Orientation);
+ virtual ~Lineset();
+
+ Glib::PropertyProxy<double> property_x1() { return x1.get_proxy(); }
+ Glib::PropertyProxy<double> property_y1() { return y1.get_proxy(); }
+ Glib::PropertyProxy<double> property_x2() { return x2.get_proxy(); }
+ Glib::PropertyProxy<double> property_y2() { return y2.get_proxy(); }
+
+ /*
+ * Note: every line operation takes a coord parameter, as an index to
+ * the line it modifies. The index will identify a line if it is between
+ * line.coord and line.coord + line.width.
+ */
+
+ /**
+ * Move a line to a new position
+ * for this to work (to move the desired line) it is important that
+ * lines have unique coordinates. This also applies to every line
+ * accessing functions below
+ */
+ void move_line(double coord, double dest);
+
+ /**
+ * Change the width of a line. Only allow it if the new width doesn't
+ * overlap the next line (see below)
+ */
+ void change_line_width(double coord, double width);
+
+ /**
+ * Change the color of a line
+ */
+ void change_line_color(double coord, uint32_t color);
+
+ /**
+ * this function adds a line to draw.
+ * width is an offset, so that coord + width specifies the end of the line.
+ * lines should not overlap, as no layering information is provided.
+ * however, line_coord[i] + line_width[i] == line_coord[i+1] is
+ * be legal, as the coordinates are real numbers and represents
+ * real world coordinates. Two real world object sharing coordinates for start
+ * and end are not overlapping.
+ */
+ void add_line(double coord, double width, uint32_t color);
+
+ /**
+ * remove the line at coord
+ */
+ void remove_line(double coord);
+
+ /**
+ * remove all lines in a coordinate range
+ */
+ void remove_lines(double c1, double c2);
+
+ /**
+ * remove all lines with a coordinate lower than coord
+ */
+ void remove_until(double coord);
+
+ /**
+ * remove all lines with a coordinate equal to or higher than coord
+ */
+ void remove_from(double coord);
+
+ /**
+ * remove all lines
+ */
+ void clear();
+
+ /**
+ * this is a request of information on lines in a coordinate range.
+ * for every line visible in the provided coordinate range,
+ * call add_line() on it.
+ * This is called when the area between c1 and c2 becomes visible, when
+ * previously outside any possible view. So the number of calls to this
+ * function will be kept at a minimum.
+ */
+ virtual void request_lines(double c1, double c2);
+
+ /**
+ * instead of overriding the update_lines function one can connect to this
+ * and add lines externally instead. If add_lines() is overrided, this
+ * signal will not be emitted.
+ */
+ sigc::signal<void, Lineset&, double, double> signal_request_lines;
+
+ /* overrided from Gnome::Canvas::Item */
+ void update_vfunc(double* affine, ArtSVP* clip_path, int flags);
+ void realize_vfunc();
+ void unrealize_vfunc();
+ void map_vfunc();
+ void unmap_vfunc();
+ void draw_vfunc(const Glib::RefPtr<Gdk::Drawable>& drawable, int x, int y, int width, int height);
+ void render_vfunc(GnomeCanvasBuf* buf);
+ double point_vfunc(double x, double y, int cx, int cy, GnomeCanvasItem** actual_item);
+ void bounds_vfunc(double* x1, double* y1, double* x2, double* y2);
+ bool on_event(GdkEvent* p1);
+
+ /* debug */
+ void print_lines();
+
+protected:
+ struct Line {
+ Line(double c, double w, uint32_t color);
+ Line(double c);
+
+ void set_color(uint32_t color);
+
+ double coord;
+ double width;
+ unsigned char r;
+ unsigned char g;
+ unsigned char b;
+ unsigned char a;
+ };
+
+ static inline void paint_vert(GnomeCanvasBuf* buf, Lineset::Line& line, int x1, int y1, int x2, int y2);
+ static inline void paint_horiz(GnomeCanvasBuf* buf, Lineset::Line& line, int x1, int y1, int x2, int y2);
+
+ static bool line_compare(const Line& a, const Line& b);
+
+ typedef std::list<Line> Lines;
+ void bounds_need_update();
+ void region_needs_update(double coord1, double coord2);
+ bool update_bounds();
+ void update_lines(bool need_redraw);
+ void redraw_request(ArtIRect&);
+ void redraw_request(ArtDRect&);
+
+ Lines::iterator line_at(double coord);
+
+ /* store that last accessed line so adjacent lines are found faster */
+ Lines::iterator cached_pos;
+
+ static LinesetClass lineset_class;
+ Orientation orientation;
+ Lines lines;
+
+ /* properties */
+ Glib::Property<double> x1;
+ Glib::Property<double> y1;
+ Glib::Property<double> x2;
+ Glib::Property<double> y2;
+
+ /* cached bounding box in canvas coordinates*/
+ ArtIRect bbox;
+
+private:
+ Lineset();
+ Lineset(const Lineset&);
+
+ bool in_update;
+
+ /* a range that needs update update1 > update2 ==> no update needed */
+ double update_region1;
+ double update_region2;
+ bool bounds_changed;
+
+ double covered1;
+ double covered2;
+};
+
+} /* namespace Canvas */
+} /* namespace Gnome */
+
+#endif /* __gnome_canvas_lineset_h__ */
diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc
index ec326f1e2e..177adbf38b 100644
--- a/gtk2_ardour/midi_region_view.cc
+++ b/gtk2_ardour/midi_region_view.cc
@@ -69,7 +69,10 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &
, _mouse_state(None)
, _pressed_button(0)
{
+ group->lower_to_bottom();
_note_group->raise_to_top();
+
+ frame->property_fill_color_rgba() = 0xff000033;
}
MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color, TimeAxisViewItem::Visibility visibility)
@@ -795,3 +798,14 @@ MidiRegionView::switch_source(boost::shared_ptr<Source> src)
display_model(msrc->model());
}
+void
+MidiRegionView::set_frame_color()
+{
+ if (frame) {
+ if (_selected && should_show_selection) {
+ frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
+ } else {
+ frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
+ }
+ }
+}
diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h
index 20c7671f5b..a709f523a3 100644
--- a/gtk2_ardour/midi_region_view.h
+++ b/gtk2_ardour/midi_region_view.h
@@ -72,6 +72,8 @@ class MidiRegionView : public RegionView
void set_y_position_and_height (double, double);
+ void set_frame_color();
+
void redisplay_model();
GhostRegion* add_ghost (AutomationTimeAxisView&);
diff --git a/gtk2_ardour/midi_scroomer.cc b/gtk2_ardour/midi_scroomer.cc
new file mode 100644
index 0000000000..a38911d98c
--- /dev/null
+++ b/gtk2_ardour/midi_scroomer.cc
@@ -0,0 +1,164 @@
+/*
+ Copyright (C) 2008 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 "midi_scroomer.h"
+
+#include <cairomm/context.h>
+
+#include <iostream>
+
+using namespace Gtkmm2ext;
+using namespace Gtk;
+using namespace std;
+
+//std::map<int, Glib::RefPtr<Gdk::Pixmap> > MidiScroomer::piano_pixmaps;
+
+MidiScroomer::MidiScroomer(Adjustment& adj)
+ : Gtkmm2ext::Scroomer(adj) {
+
+ adj.set_lower(0);
+ adj.set_upper(127);
+
+ /* set minimum view range to one octave */
+ set_min_page_size(12);
+}
+
+MidiScroomer::~MidiScroomer() {
+}
+
+bool
+MidiScroomer::on_expose_event(GdkEventExpose* ev) {
+ Cairo::RefPtr<Cairo::Context> cc = get_window()->create_cairo_context();
+ GdkRectangle comp_rect, clip_rect;
+ Component first_comp = point_in(ev->area.y);
+ Component last_comp = point_in(ev->area.y + ev->area.height);
+ int height = get_height();
+ int lnote, hnote;
+ double y2note = (double) 127 / height;
+ double note2y = (double) height / 127;
+ double note_width = 0.8 * get_width();
+ double note_height = 1.4 * note2y;
+ double black_shift = 0.1 * note2y;
+ double colors[6];
+
+ //cerr << ev->area.y << " " << ev->area.height << endl;
+
+ comp_rect.x = 0;
+ comp_rect.width = get_width();
+
+ for(int i = first_comp; i <= last_comp; ++i) {
+ Component comp = (Component) i;
+ set_comp_rect(comp_rect, comp);
+
+ if(gdk_rectangle_intersect(&comp_rect, &ev->area, &clip_rect)) {
+ get_colors(colors, comp);
+
+ cc->rectangle(clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height);
+ cc->set_source_rgb (colors[3], colors[4], colors[5]);
+ cc->fill_preserve();
+
+ cc->clip();
+ cc->set_source_rgb(colors[0], colors[1], colors[2]);
+ cc->set_line_width(note_height);
+
+ lnote = 127 - (int) floor((double) (clip_rect.y + clip_rect.height) * y2note) - 1;
+ hnote = 127 - (int) floor((double) clip_rect.y * y2note) + 1;
+
+ for(int note = lnote; note < hnote + 1; ++note) {
+ double y = height - note * note2y;
+ bool draw = false;
+
+ switch(note % 12) {
+ case 1:
+ case 6:
+ y -= black_shift;
+ draw = true;
+ break;
+ case 3:
+ case 10:
+ y += black_shift;
+ draw = true;
+ break;
+ case 8:
+ draw = true;
+ break;
+ default:
+ break;
+ }
+
+ if(draw) {
+ cc->set_line_width(1.4 * note2y);
+ cc->move_to(0, y);
+ cc->line_to(note_width, y);
+ cc->stroke();
+ }
+ }
+
+ cc->reset_clip();
+ }
+ }
+
+ return true;
+}
+
+void
+MidiScroomer::get_colors(double color[], Component comp) {
+ switch (comp) {
+ case TopBase:
+ case BottomBase:
+ color[0] = 0.24;
+ color[1] = 0.24;
+ color[2] = 0.24;
+ color[3] = 0.33;
+ color[4] = 0.33;
+ color[5] = 0.33;
+ break;
+ case Handle1:
+ case Handle2:
+ color[0] = 0.38;
+ color[1] = 0.38;
+ color[2] = 0.38;
+ color[3] = 0.91;
+ color[4] = 0.91;
+ color[5] = 0.91;
+ break;
+ case Slider:
+ color[0] = 0.38;
+ color[1] = 0.38;
+ color[2] = 0.38;
+ color[3] = 0.77;
+ color[4] = 0.77;
+ color[5] = 0.77;
+ break;
+ default:
+ break;
+ }
+}
+
+void
+MidiScroomer::on_size_request(Gtk::Requisition* r) {
+ r->width = 16;
+ r->height = 100;
+ //r->width = 32;
+ //r->height = 512;
+}
+
+void
+MidiScroomer::on_size_allocate(Gtk::Allocation& a) {
+ Scroomer::on_size_allocate(a);
+}
diff --git a/gtk2_ardour/midi_scroomer.h b/gtk2_ardour/midi_scroomer.h
new file mode 100644
index 0000000000..a0021de926
--- /dev/null
+++ b/gtk2_ardour/midi_scroomer.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (C) 2008 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.
+
+*/
+
+#ifndef __ardour_midi_scroomer_h__
+#define __ardour_midi_scroomer_h__
+
+#include <gtkmm2ext/scroomer.h>
+#include <gdkmm/pixbuf.h>
+
+class MidiScroomer : public Gtkmm2ext::Scroomer {
+public:
+ MidiScroomer(Gtk::Adjustment&);
+ ~MidiScroomer();
+
+ bool on_expose_event(GdkEventExpose*);
+
+ void on_size_request(Gtk::Requisition*);
+ void on_size_allocate(Gtk::Allocation&);
+
+ void get_colors(double color[], Component comp);
+};
+
+#endif /* __ardour_midi_scroomer_h__ */
diff --git a/gtk2_ardour/midi_streamview.cc b/gtk2_ardour/midi_streamview.cc
index 9e0f9f368f..172d30b7ef 100644
--- a/gtk2_ardour/midi_streamview.cc
+++ b/gtk2_ardour/midi_streamview.cc
@@ -46,7 +46,7 @@
#include "gui_thread.h"
#include "utils.h"
#include "simplerect.h"
-#include "simpleline.h"
+#include "lineset.h"
using namespace std;
using namespace ARDOUR;
@@ -55,7 +55,9 @@ using namespace Editing;
MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
: StreamView (tv)
+ , note_range_adjustment(0.0f, 0.0f, 0.0f)
, _range(ContentsRange)
+ , _range_sum_cache(-1.0)
, _lowest_note(60)
, _highest_note(60)
{
@@ -63,19 +65,20 @@ MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
stream_base_color = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
else
stream_base_color = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();
-
- canvas_rect->property_fill_color_rgba() = stream_base_color;
- canvas_rect->property_outline_color_rgba() = RGBA_BLACK;
use_rec_regions = tv.editor.show_waveforms_recording ();
-
- _note_line_group = new ArdourCanvas::Group (*canvas_group);
-
- for (uint8_t i=0; i < 127; ++i) {
- _note_lines[i] = new ArdourCanvas::SimpleLine(*_note_line_group,
- 0, note_to_y(i), 10, note_to_y(i));
- _note_lines[i]->property_color_rgba() = 0xEEEEEE55;
- }
+
+ _note_lines = new ArdourCanvas::Lineset(*canvas_group, ArdourCanvas::Lineset::Horizontal);
+
+ _note_lines->property_x1() = 0;
+ _note_lines->property_y1() = 0;
+ _note_lines->property_x2() = trackview().editor.frame_to_pixel (max_frames);
+ _note_lines->property_y2() = 0;
+
+ _note_lines->signal_event().connect (bind (mem_fun (_trackview.editor, &PublicEditor::canvas_stream_view_event), _note_lines, &_trackview));
+
+ note_range_adjustment.signal_value_changed().connect (mem_fun (*this, &MidiStreamView::note_range_adjustment_changed));
+ ColorsChanged.connect(mem_fun(*this, &MidiStreamView::draw_note_lines));
}
MidiStreamView::~MidiStreamView ()
@@ -159,7 +162,8 @@ void
MidiStreamView::display_diskstream (boost::shared_ptr<Diskstream> ds)
{
StreamView::display_diskstream(ds);
- draw_note_separators();
+ draw_note_lines();
+ NoteRangeChanged();
}
// FIXME: code duplication with AudioStreamView
@@ -216,7 +220,10 @@ MidiStreamView::redisplay_diskstream ()
region_layered (*i);
}
- draw_note_separators();
+ note_range_adjustment.set_page_size(_highest_note - _lowest_note);
+ note_range_adjustment.set_value(_lowest_note);
+ NoteRangeChanged();
+ draw_note_lines();
}
@@ -224,22 +231,45 @@ void
MidiStreamView::update_contents_y_position_and_height ()
{
StreamView::update_contents_y_position_and_height();
- draw_note_separators();
+ _note_lines->property_y2() = height;
+ draw_note_lines();
}
void
-MidiStreamView::draw_note_separators()
+MidiStreamView::draw_note_lines()
{
- for (uint8_t i=0; i < 127; ++i) {
- if (i >= _lowest_note-1 && i <= _highest_note) {
- _note_lines[i]->property_x1() = 0;
- _note_lines[i]->property_x2() = canvas_rect->property_x2() - 2;
- _note_lines[i]->property_y1() = note_to_y(i);
- _note_lines[i]->property_y2() = note_to_y(i);
- _note_lines[i]->show();
- } else {
- _note_lines[i]->hide();
+ double y;
+ double prev_y = contents_height();
+ uint32_t color;
+
+ _note_lines->clear();
+
+ for(int i = _lowest_note; i <= _highest_note; ++i) {
+ y = floor(note_to_y(i));
+
+ _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
+
+ switch(i % 12) {
+ case 1:
+ case 3:
+ case 6:
+ case 8:
+ case 10:
+ color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
+ break;
+ default:
+ color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
+ break;
}
+
+ if(i == _highest_note) {
+ _note_lines->add_line(y, prev_y - y, color);
+ }
+ else {
+ _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
+ }
+
+ prev_y = y;
}
}
@@ -258,6 +288,21 @@ MidiStreamView::set_note_range(VisibleNoteRange r)
redisplay_diskstream();
}
+void
+MidiStreamView::set_note_range(uint8_t lowest, uint8_t highest) {
+ if(_range == ContentsRange) {
+ _lowest_note = lowest;
+ _highest_note = highest;
+
+ list<RegionView *>::iterator i;
+ for (i = region_views.begin(); i != region_views.end(); ++i) {
+ (*i)->set_y_position_and_height(0, height); // apply note range
+ }
+ }
+
+ draw_note_lines();
+ NoteRangeChanged();
+}
void
MidiStreamView::update_bounds(uint8_t note_num)
@@ -544,12 +589,37 @@ MidiStreamView::color_handler ()
//case cMidiTrackBase:
if (_trackview.is_midi_track()) {
- canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
+ //canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
}
//case cMidiBusBase:
if (!_trackview.is_midi_track()) {
- canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
+ //canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
}
}
+void
+MidiStreamView::note_range_adjustment_changed() {
+ double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
+ int lowest = (int) floor(note_range_adjustment.get_value());
+ int highest;
+
+ if(sum == _range_sum_cache) {
+ cerr << "cached" << endl;
+ highest = (int) floor(sum);
+ }
+ else {
+ cerr << "recalc" << endl;
+ highest = lowest + (int) floor(note_range_adjustment.get_page_size());
+ _range_sum_cache = sum;
+ }
+
+ if(lowest == lowest_note() && highest == highest_note()) {
+ return;
+ }
+
+ cerr << "note range changed: " << lowest << " " << highest << endl;
+ //cerr << " val=" << v_zoom_adjustment.get_value() << " page=" << v_zoom_adjustment.get_page_size() << " sum=" << v_zoom_adjustment.get_value() + v_zoom_adjustment.get_page_size() << endl;
+
+ set_note_range(lowest, highest);
+}
diff --git a/gtk2_ardour/midi_streamview.h b/gtk2_ardour/midi_streamview.h
index fa414cfe9c..5c0ee47c30 100644
--- a/gtk2_ardour/midi_streamview.h
+++ b/gtk2_ardour/midi_streamview.h
@@ -24,10 +24,10 @@
#include <ardour/location.h>
#include "enums.h"
-#include "simplerect.h"
#include "streamview.h"
#include "time_axis_view_item.h"
#include "route_time_axis.h"
+#include "canvas.h"
namespace Gdk {
class Color;
@@ -66,8 +66,11 @@ class MidiStreamView : public StreamView
ContentsRange
};
+ Gtk::Adjustment note_range_adjustment;
+
VisibleNoteRange note_range() { return _range; }
void set_note_range(VisibleNoteRange r);
+ void set_note_range(uint8_t lowest, uint8_t highest);
uint8_t lowest_note() const { return (_range == FullRange) ? 0 : _lowest_note; }
uint8_t highest_note() const { return (_range == FullRange) ? 127 : _highest_note; }
@@ -93,6 +96,8 @@ class MidiStreamView : public StreamView
inline uint8_t contents_note_range() const
{ return _highest_note - _lowest_note + 1; }
+
+ sigc::signal<void> NoteRangeChanged;
private:
void setup_rec_box ();
@@ -104,15 +109,17 @@ class MidiStreamView : public StreamView
void display_diskstream (boost::shared_ptr<ARDOUR::Diskstream> ds);
void update_contents_y_position_and_height ();
- void draw_note_separators();
+ void draw_note_lines();
void color_handler ();
+ void note_range_adjustment_changed();
+
VisibleNoteRange _range;
+ double _range_sum_cache;
uint8_t _lowest_note;
uint8_t _highest_note;
- ArdourCanvas::Group* _note_line_group;
- ArdourCanvas::SimpleLine* _note_lines[127];
+ ArdourCanvas::Lineset* _note_lines;
};
#endif /* __ardour_midi_streamview_h__ */
diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc
index 21848fff59..b3892d2790 100644
--- a/gtk2_ardour/midi_time_axis.cc
+++ b/gtk2_ardour/midi_time_axis.cc
@@ -67,6 +67,8 @@
#include "simplerect.h"
#include "midi_streamview.h"
#include "utils.h"
+#include "midi_scroomer.h"
+#include "piano_roll_header.h"
#include <ardour/midi_track.h>
@@ -81,6 +83,8 @@ using namespace Editing;
MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session& sess, boost::shared_ptr<Route> rt, Canvas& canvas)
: AxisView(sess) // FIXME: won't compile without this, why??
, RouteTimeAxisView(ed, sess, rt, canvas)
+ , _range_scroomer(0)
+ , _piano_roll_header(0)
, _note_mode(Sustained)
, _note_mode_item(NULL)
, _percussion_mode_item(NULL)
@@ -112,6 +116,11 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session& sess, boost::shar
_route->processors_changed.connect (mem_fun(*this, &MidiTimeAxisView::processors_changed));
if (is_track()) {
+ _piano_roll_header = new PianoRollHeader(*midi_view());
+ _range_scroomer = new MidiScroomer(midi_view()->note_range_adjustment);
+
+ controls_hbox.pack_start(*_range_scroomer);
+ controls_hbox.pack_start(*_piano_roll_header);
controls_ebox.set_name ("MidiTrackControlsBaseUnselected");
controls_base_selected_name = "MidiTrackControlsBaseSelected";
@@ -318,5 +327,3 @@ MidiTimeAxisView::route_active_changed ()
}
}
}
-
-
diff --git a/gtk2_ardour/midi_time_axis.h b/gtk2_ardour/midi_time_axis.h
index f7ec164edc..15e487e00e 100644
--- a/gtk2_ardour/midi_time_axis.h
+++ b/gtk2_ardour/midi_time_axis.h
@@ -50,6 +50,8 @@ namespace ARDOUR {
class PublicEditor;
class MidiStreamView;
+class MidiScroomer;
+class PianoRollHeader;
class MidiTimeAxisView : public RouteTimeAxisView
{
@@ -67,7 +69,7 @@ class MidiTimeAxisView : public RouteTimeAxisView
void create_automation_child (ARDOUR::Parameter param, bool show);
ARDOUR::NoteMode note_mode() const { return _note_mode; }
-
+
private:
void append_extra_display_menu_items ();
@@ -83,6 +85,8 @@ class MidiTimeAxisView : public RouteTimeAxisView
Gtk::Menu _subplugin_menu;
+ MidiScroomer* _range_scroomer;
+ PianoRollHeader* _piano_roll_header;
ARDOUR::NoteMode _note_mode;
Gtk::RadioMenuItem* _note_mode_item;
Gtk::RadioMenuItem* _percussion_mode_item;
diff --git a/gtk2_ardour/piano_roll_header.cc b/gtk2_ardour/piano_roll_header.cc
new file mode 100644
index 0000000000..8be0bc86c1
--- /dev/null
+++ b/gtk2_ardour/piano_roll_header.cc
@@ -0,0 +1,653 @@
+/*
+ Copyright (C) 2008 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 <ardour/midi_track.h>
+
+#include "piano_roll_header.h"
+#include "midi_time_axis.h"
+#include "midi_streamview.h"
+
+#include <iostream>
+
+const int no_note = 0xff;
+
+using namespace std;
+
+PianoRollHeader::Color PianoRollHeader::white = PianoRollHeader::Color(0.77f, 0.78f, 0.76f);
+PianoRollHeader::Color PianoRollHeader::white_highlight = PianoRollHeader::Color(0.87f, 0.88f, 0.86f);
+PianoRollHeader::Color PianoRollHeader::white_shade_light = PianoRollHeader::Color(0.95f, 0.95f, 0.95f);
+PianoRollHeader::Color PianoRollHeader::white_shade_dark = PianoRollHeader::Color(0.56f, 0.56f, 0.56f);
+
+PianoRollHeader::Color PianoRollHeader::black = PianoRollHeader::Color(0.24f, 0.24f, 0.24f);
+PianoRollHeader::Color PianoRollHeader::black_highlight = PianoRollHeader::Color(0.30f, 0.30f, 0.30f);
+PianoRollHeader::Color PianoRollHeader::black_shade_light = PianoRollHeader::Color(0.46f, 0.46f, 0.46f);
+PianoRollHeader::Color PianoRollHeader::black_shade_dark = PianoRollHeader::Color(0.1f, 0.1f, 0.1f);
+
+PianoRollHeader::Color::Color()
+ : r(1.0f)
+ , g(1.0f)
+ , b(1.0f) {
+}
+
+PianoRollHeader::Color::Color(double _r, double _g, double _b)
+ : r(_r)
+ , g(_g)
+ , b(_b) {
+}
+
+inline void
+PianoRollHeader::Color::set(const PianoRollHeader::Color& c) {
+ r = c.r;
+ g = c.g;
+ b = c.b;
+}
+
+PianoRollHeader::PianoRollHeader(MidiStreamView& v)
+ : _view(v)
+ , _highlighted_note(no_note)
+ , _clicked_note(no_note) {
+
+ add_events (Gdk::BUTTON_PRESS_MASK |
+ Gdk::BUTTON_RELEASE_MASK |
+ Gdk::POINTER_MOTION_MASK |
+ Gdk::ENTER_NOTIFY_MASK |
+ Gdk::LEAVE_NOTIFY_MASK |
+ Gdk::SCROLL_MASK);
+
+ for(int i = 0; i < 128; ++i) {
+ _active_notes[i] = false;
+ }
+
+ _view.NoteRangeChanged.connect (mem_fun (*this, &PianoRollHeader::note_range_changed));
+}
+
+inline void
+create_path(Cairo::RefPtr<Cairo::Context> cr, double x[], double y[], int start, int stop) {
+ cr->move_to(x[start], y[start]);
+
+ for(int i = start+1; i <= stop; ++i) {
+ cr->line_to(x[i], y[i]);
+ }
+}
+
+inline void
+render_rect(Cairo::RefPtr<Cairo::Context> cr, int note, double x[], double y[],
+ PianoRollHeader::Color& bg, PianoRollHeader::Color& tl_shadow, PianoRollHeader::Color& br_shadow) {
+ cr->set_source_rgb(bg.r, bg.g, bg.b);
+ create_path(cr, x, y, 0, 4);
+ cr->fill();
+
+ cr->set_source_rgb(tl_shadow.r, tl_shadow.g, tl_shadow.b);
+ create_path(cr, x, y, 0, 2);
+ cr->stroke();
+
+ cr->set_source_rgb(br_shadow.r, br_shadow.g, br_shadow.b);
+ create_path(cr, x, y, 2, 4);
+ cr->stroke();
+}
+
+inline void
+render_cf(Cairo::RefPtr<Cairo::Context> cr, int note, double x[], double y[],
+ PianoRollHeader::Color& bg, PianoRollHeader::Color& tl_shadow, PianoRollHeader::Color& br_shadow) {
+ cr->set_source_rgb(bg.r, bg.g, bg.b);
+ create_path(cr, x, y, 0, 6);
+ cr->fill();
+
+ cr->set_source_rgb(tl_shadow.r, tl_shadow.g, tl_shadow.b);
+ create_path(cr, x, y, 0, 4);
+ cr->stroke();
+
+ cr->set_source_rgb(br_shadow.r, br_shadow.g, br_shadow.b);
+ create_path(cr, x, y, 4, 6);
+ cr->stroke();
+}
+
+inline void
+render_eb(Cairo::RefPtr<Cairo::Context> cr, int note, double x[], double y[],
+ PianoRollHeader::Color& bg, PianoRollHeader::Color& tl_shadow, PianoRollHeader::Color& br_shadow) {
+ cr->set_source_rgb(bg.r, bg.g, bg.b);
+ create_path(cr, x, y, 0, 6);
+ cr->fill();
+
+ cr->set_source_rgb(tl_shadow.r, tl_shadow.g, tl_shadow.b);
+ create_path(cr, x, y, 0, 2);
+ cr->stroke();
+ create_path(cr, x, y, 4, 5);
+ cr->stroke();
+
+ cr->set_source_rgb(br_shadow.r, br_shadow.g, br_shadow.b);
+ create_path(cr, x, y, 2, 4);
+ cr->stroke();
+ create_path(cr, x, y, 5, 6);
+ cr->stroke();
+}
+
+inline void
+render_dga(Cairo::RefPtr<Cairo::Context> cr, int note, double x[], double y[],
+ PianoRollHeader::Color& bg, PianoRollHeader::Color& tl_shadow, PianoRollHeader::Color& br_shadow) {
+ cr->set_source_rgb(bg.r, bg.g, bg.b);
+ create_path(cr, x, y, 0, 8);
+ cr->fill();
+
+ cr->set_source_rgb(tl_shadow.r, tl_shadow.g, tl_shadow.b);
+ create_path(cr, x, y, 0, 4);
+ cr->stroke();
+ create_path(cr, x, y, 6, 7);
+ cr->stroke();
+
+ cr->set_source_rgb(br_shadow.r, br_shadow.g, br_shadow.b);
+ create_path(cr, x, y, 4, 6);
+ cr->stroke();
+ create_path(cr, x, y, 7, 8);
+ cr->stroke();
+}
+
+void
+PianoRollHeader::get_path(PianoRollHeader::ItemType note_type, int note, double x[], double y[]) {
+ double y_pos = floor(_view.note_to_y(note)) - 0.5f;
+ double note_height;
+ double other_y1 = floor(_view.note_to_y(note+1)) + floor(_note_height / 2.0f) + 0.5f;
+ double other_y2 = floor(_view.note_to_y(note-1)) + floor(_note_height / 2.0f) - 1.0f;
+ double width = get_width();
+
+ if(note == 0) {
+ note_height = floor(_view.contents_height()) - y_pos;
+ }
+ else {
+ note_height = floor(_view.note_to_y(note - 1)) - y_pos;
+ }
+
+ switch(note_type) {
+ case BLACK_SEPARATOR:
+ x[0] = 1.5f;
+ y[0] = y_pos;
+ x[1] = _black_note_width;
+ y[1] = y_pos;
+ break;
+ case BLACK_MIDDLE_SEPARATOR:
+ x[0] = _black_note_width;
+ y[0] = y_pos + floor(_note_height / 2.0f);
+ x[1] = width - 1.0f;
+ y[1] = y[0];
+ break;
+ case BLACK:
+ x[0] = 1.5f;
+ y[0] = y_pos + note_height - 0.5f;
+ x[1] = 1.5f;
+ y[1] = y_pos + 1.0f;
+ x[2] = _black_note_width;
+ y[2] = y_pos + 1.0f;
+ x[3] = _black_note_width;
+ y[3] = y_pos + note_height - 0.5f;
+ x[4] = 1.5f;
+ y[4] = y_pos + note_height - 0.5f;
+ return;
+ case WHITE_SEPARATOR:
+ x[0] = 1.5f;
+ y[0] = y_pos;
+ x[1] = width - 1.5f;
+ y[1] = y_pos;
+ break;
+ case WHITE_RECT:
+ x[0] = 1.5f;
+ y[0] = y_pos + note_height - 0.5f;
+ x[1] = 1.5f;
+ y[1] = y_pos + 1.0f;
+ x[2] = width - 1.5f;
+ y[2] = y_pos + 1.0f;
+ x[3] = width - 1.5f;
+ y[3] = y_pos + note_height - 0.5f;
+ x[4] = 1.5f;
+ y[4] = y_pos + note_height - 0.5f;
+ return;
+ case WHITE_CF:
+ x[0] = 1.5f;
+ y[0] = y_pos + note_height - 1.5f;
+ x[1] = 1.5f;
+ y[1] = y_pos + 1.0f;
+ x[2] = _black_note_width + 1.0f;
+ y[2] = y_pos + 1.0f;
+ x[3] = _black_note_width + 1.0f;
+ y[3] = other_y1;
+ x[4] = width - 1.5f;
+ y[4] = other_y1;
+ x[5] = width - 1.5f;
+ y[5] = y_pos + note_height - 1.5f;
+ x[6] = 1.5f;
+ y[6] = y_pos + note_height - 1.5f;
+ return;
+ case WHITE_EB:
+ x[0] = 1.5f;
+ y[0] = y_pos + note_height - 1.5f;
+ x[1] = 1.5f;
+ y[1] = y_pos + 1.0f;
+ x[2] = width - 1.5f;
+ y[2] = y_pos + 1.0f;
+ x[3] = width - 1.5f;
+ y[3] = other_y2;
+ x[4] = _black_note_width + 1.0f;
+ y[4] = other_y2;
+ x[5] = _black_note_width + 1.0f;
+ y[5] = y_pos + note_height - 1.5f;
+ x[6] = 1.5f;
+ y[6] = y_pos + note_height - 1.5f;
+ return;
+ case WHITE_DGA:
+ x[0] = 1.5f;
+ y[0] = y_pos + note_height - 1.5f;
+ x[1] = 1.5f;
+ y[1] = y_pos + 1.0f;
+ x[2] = _black_note_width + 1.0f;
+ y[2] = y_pos + 1.0f;
+ x[3] = _black_note_width + 1.0f;
+ y[3] = other_y1;
+ x[4] = width - 1.5f;
+ y[4] = other_y1;
+ x[5] = width - 1.5f;
+ y[5] = other_y2;
+ x[6] = _black_note_width + 1.0f;
+ y[6] = other_y2;
+ x[7] = _black_note_width + 1.0f;
+ y[7] = y_pos + note_height - 1.5f;
+ x[8] = 1.5f;
+ y[8] = y_pos + note_height - 1.5f;
+ return;
+ default:
+ return;
+ }
+}
+
+bool
+PianoRollHeader::on_expose_event (GdkEventExpose* ev) {
+ GdkRectangle& rect = ev->area;
+ double font_size;
+ int lowest, highest;
+ Cairo::RefPtr<Cairo::Context> cr = get_window()->create_cairo_context();
+ Cairo::RefPtr<Cairo::LinearGradient> pat = Cairo::LinearGradient::create(0, 0, _black_note_width, 0);
+ double x[9];
+ double y[9];
+ Color bg, tl_shadow, br_shadow;
+ int oct_rel;
+ int y1 = max(rect.y, 0);
+ int y2 = min(rect.y + rect.height, (int) floor(_view.contents_height() - 1.0f));
+
+ //Cairo::TextExtents te;
+ lowest = max(_view.lowest_note(), _view.y_to_note(y2));
+ highest = min(_view.highest_note(), _view.y_to_note(y1));
+
+ if(lowest > 127) {
+ lowest = 0;
+ }
+
+ cr->select_font_face ("Georgia", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
+ font_size = min(10.0, _note_height);
+ cr->set_font_size(font_size);
+
+ /* fill the entire rect with the color for non-highlighted white notes.
+ * then we won't have to draw the background for those notes,
+ * and would only have to draw the background for the one highlighted white note*/
+ //cr->rectangle(rect.x, rect.y, rect.width, rect.height);
+ //cr->set_source_rgb(white.r, white.g, white.b);
+ //cr->fill();
+
+ cr->set_line_width(1.0f);
+
+ /* draw vertical lines with shade at both ends of the widget */
+ cr->set_source_rgb(0.0f, 0.0f, 0.0f);
+ cr->move_to(0.5f, rect.y);
+ cr->line_to(0.5f, rect.y + rect.height);
+ cr->stroke();
+ cr->move_to(get_width() - 0.5f, rect.y);
+ cr->line_to(get_width() - 0.5f, rect.y + rect.height);
+ cr->stroke();
+
+ //pat->add_color_stop_rgb(0.0, 0.33, 0.33, 0.33);
+ //pat->add_color_stop_rgb(0.2, 0.39, 0.39, 0.39);
+ //pat->add_color_stop_rgb(1.0, 0.22, 0.22, 0.22);
+ //cr->set_source(pat);
+
+ for(int i = lowest; i <= highest; ++i) {
+ oct_rel = i % 12;
+
+ switch(oct_rel) {
+ case 1:
+ case 3:
+ case 6:
+ case 8:
+ case 10:
+ /* black note */
+ if(i == _highlighted_note) {
+ bg.set(black_highlight);
+ }
+ else {
+ bg.set(black);
+ }
+
+ if(_active_notes[i]) {
+ tl_shadow.set(black_shade_dark);
+ br_shadow.set(black_shade_light);
+ }
+ else {
+ tl_shadow.set(black_shade_light);
+ br_shadow.set(black_shade_dark);
+ }
+
+ /* draw black separators */
+ cr->set_source_rgb(0.0f, 0.0f, 0.0f);
+ get_path(BLACK_SEPARATOR, i, x, y);
+ create_path(cr, x, y, 0, 1);
+ cr->stroke();
+
+ get_path(BLACK_MIDDLE_SEPARATOR, i, x, y);
+ create_path(cr, x, y, 0, 1);
+ cr->stroke();
+
+ get_path(BLACK, i, x, y);
+ render_rect(cr, i, x, y, bg, tl_shadow, br_shadow);
+ break;
+
+ default:
+ /* white note */
+ if(i == _highlighted_note) {
+ bg.set(white_highlight);
+ }
+ else {
+ bg.set(white);
+ }
+
+ if(_active_notes[i]) {
+ tl_shadow.set(white_shade_dark);
+ br_shadow.set(white_shade_light);
+ }
+ else {
+ tl_shadow.set(white_shade_light);
+ br_shadow.set(white_shade_dark);
+ }
+
+ switch(oct_rel) {
+ case 0:
+ case 5:
+ if(i == _view.highest_note()) {
+ get_path(WHITE_RECT, i, x, y);
+ render_rect(cr, i, x, y, bg, tl_shadow, br_shadow);
+ }
+ else {
+ get_path(WHITE_CF, i, x, y);
+ render_cf(cr, i, x, y, bg, tl_shadow, br_shadow);
+ }
+ break;
+
+ case 2:
+ case 7:
+ case 9:
+ if(i == _view.highest_note()) {
+ get_path(WHITE_EB, i, x, y);
+ render_eb(cr, i, x, y, bg, tl_shadow, br_shadow);
+ }
+ else if(i == _view.lowest_note()) {
+ get_path(WHITE_CF, i, x, y);
+ render_cf(cr, i, x, y, bg, tl_shadow, br_shadow);
+ }
+ else {
+ get_path(WHITE_DGA, i, x, y);
+ render_dga(cr, i, x, y, bg, tl_shadow, br_shadow);
+ }
+ break;
+
+ case 4:
+ case 11:
+ cr->set_source_rgb(0.0f, 0.0f, 0.0f);
+ get_path(WHITE_SEPARATOR, i, x, y);
+ create_path(cr, x, y, 0, 1);
+ cr->stroke();
+
+ if(i == _view.lowest_note()) {
+ get_path(WHITE_RECT, i, x, y);
+ render_rect(cr, i, x, y, bg, tl_shadow, br_shadow);
+ }
+ else {
+ get_path(WHITE_EB, i, x, y);
+ render_eb(cr, i, x, y, bg, tl_shadow, br_shadow);
+ }
+ break;
+
+ default:
+ break;
+
+ }
+ break;
+
+ }
+
+ /* render the name of which C this is */
+ if(oct_rel == 0) {
+ std::stringstream s;
+ double y = floor(_view.note_to_y(i)) - 0.5f;
+ double note_height = floor(_view.note_to_y(i - 1)) - y;
+
+ int cn = i / 12;
+ s << "C" << cn;
+
+ //cr->get_text_extents(s.str(), te);
+ cr->set_source_rgb(0.30f, 0.30f, 0.30f);
+ cr->move_to(0, y + font_size + (note_height - font_size) / 2.0f);
+ cr->show_text(s.str());
+ }
+ }
+
+ return true;
+}
+
+bool
+PianoRollHeader::on_motion_notify_event (GdkEventMotion* ev) {
+ int note = _view.y_to_note(ev->y);
+
+ if(_highlighted_note != no_note) {
+ if(note > _highlighted_note) {
+ invalidate_note_range(_highlighted_note, note);
+ }
+ else {
+ invalidate_note_range(note, _highlighted_note);
+ }
+
+ _highlighted_note = note;
+ }
+
+ /* redraw already taken care of above */
+ if(_clicked_note != no_note && _clicked_note != note) {
+ _active_notes[_clicked_note] = false;
+ send_note_off(_clicked_note);
+
+ _clicked_note = note;
+
+ if(!_active_notes[note]) {
+ _active_notes[note] = true;
+ send_note_on(note);
+ }
+ }
+
+ //win->process_updates(false);
+
+ return true;
+}
+
+bool
+PianoRollHeader::on_button_press_event (GdkEventButton* ev) {
+ int note = _view.y_to_note(ev->y);
+
+ if(ev->type == GDK_BUTTON_PRESS && note >= 0 && note < 128) {
+ if(!_active_notes[note]) {
+ _active_notes[note] = true;
+ _clicked_note = note;
+ send_note_on(note);
+
+ invalidate_note_range(note, note);
+ }
+ else {
+ _clicked_note = no_note;
+ }
+ }
+
+ return true;
+}
+
+bool
+PianoRollHeader::on_button_release_event (GdkEventButton* ev) {
+ int note = _view.y_to_note(ev->y);
+
+ if(note == _clicked_note) {
+ _active_notes[note] = false;
+ _clicked_note = no_note;
+ send_note_off(note);
+
+ invalidate_note_range(note, note);
+ }
+
+ return true;
+}
+
+bool
+PianoRollHeader::on_enter_notify_event (GdkEventCrossing* ev) {
+ _highlighted_note = _view.y_to_note(ev->y);
+ invalidate_note_range(_highlighted_note, _highlighted_note);
+ return true;
+}
+
+bool
+PianoRollHeader::on_leave_notify_event (GdkEventCrossing*) {
+ invalidate_note_range(_highlighted_note, _highlighted_note);
+
+ if(_clicked_note != no_note) {
+ _active_notes[_clicked_note] = false;
+ send_note_off(_clicked_note);
+
+ if(_clicked_note != _highlighted_note) {
+ invalidate_note_range(_clicked_note, _clicked_note);
+ }
+
+ _clicked_note = no_note;
+ }
+
+ _highlighted_note = no_note;
+ return true;
+}
+
+bool
+PianoRollHeader::on_scroll_event (GdkEventScroll* ev) {
+ return true;
+}
+
+void
+PianoRollHeader::note_range_changed() {
+ _note_height = floor(_view.note_height()) + 0.5f;
+
+ queue_draw();
+
+ Glib::RefPtr<Gdk::Window> win = get_window();
+
+ if(win) {
+ win->process_updates(false);
+ }
+}
+
+void
+PianoRollHeader::invalidate_note_range(int lowest, int highest) {
+ Glib::RefPtr<Gdk::Window> win = get_window();
+ Gdk::Rectangle rect;
+
+ // the non-rectangular geometry of some of the notes requires more
+ // redraws than the notes that actually changed.
+ switch(lowest % 12) {
+ case 0:
+ case 5:
+ lowest = max((int) _view.lowest_note(), lowest);
+ break;
+ default:
+ lowest = max((int) _view.lowest_note(), lowest - 1);
+ break;
+ }
+
+ switch(highest % 12) {
+ case 4:
+ case 11:
+ highest = min((int) _view.highest_note(), highest);
+ break;
+ case 1:
+ case 3:
+ case 6:
+ case 8:
+ case 10:
+ highest = min((int) _view.highest_note(), highest + 1);
+ break;
+ default:
+ highest = min((int) _view.highest_note(), highest + 2);
+ break;
+ }
+
+ double y = _view.note_to_y(highest);
+ double height = _view.note_to_y(lowest - 1) - y;
+
+ rect.set_x(0);
+ rect.set_width(get_width());
+ rect.set_y((int) floor(y));
+ rect.set_height((int) floor(height));
+
+ if(win) {
+ win->invalidate_rect(rect, false);
+ }
+}
+
+void
+PianoRollHeader::on_size_request(Gtk::Requisition* r) {
+ r->width = 20;
+}
+
+void
+PianoRollHeader::on_size_allocate(Gtk::Allocation& a) {
+ DrawingArea::on_size_allocate(a);
+
+ _black_note_width = floor(0.7 * get_width()) + 0.5f;
+}
+
+void
+PianoRollHeader::send_note_on(uint8_t note) {
+ boost::shared_ptr<ARDOUR::MidiTrack> track = _view.trackview().midi_track();
+
+ cerr << "note on: " << (int) note << endl;
+
+ if(track) {
+ _event[0] = MIDI_CMD_NOTE_ON;
+ _event[1] = note;
+ _event[2] = 100;
+
+ track->write_immediate_event(3, _event);
+ }
+}
+
+void
+PianoRollHeader::send_note_off(uint8_t note) {
+ boost::shared_ptr<ARDOUR::MidiTrack> track = _view.trackview().midi_track();
+
+ if(track) {
+ _event[0] = MIDI_CMD_NOTE_OFF;
+ _event[1] = note;
+ _event[2] = 100;
+
+ track->write_immediate_event(3, _event);
+ }
+}
diff --git a/gtk2_ardour/piano_roll_header.h b/gtk2_ardour/piano_roll_header.h
new file mode 100644
index 0000000000..141e7663f5
--- /dev/null
+++ b/gtk2_ardour/piano_roll_header.h
@@ -0,0 +1,104 @@
+/*
+ Copyright (C) 2008 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.
+*/
+
+#ifndef __ardour_piano_roll_header_h__
+#define __ardour_piano_roll_header_h__
+
+#include <ardour/types.h>
+
+#include <gtkmm/drawingarea.h>
+
+namespace ARDOUR {
+ class MidiTrack;
+}
+
+class MidiTimeAxisView;
+class MidiStreamView;
+
+class PianoRollHeader : public Gtk::DrawingArea {
+public:
+ PianoRollHeader(MidiStreamView&);
+
+ bool on_expose_event (GdkEventExpose*);
+ bool on_motion_notify_event (GdkEventMotion*);
+ bool on_button_press_event (GdkEventButton*);
+ bool on_button_release_event (GdkEventButton*);
+ bool on_scroll_event (GdkEventScroll*);
+ bool on_enter_notify_event (GdkEventCrossing*);
+ bool on_leave_notify_event (GdkEventCrossing*);
+
+ void on_size_request(Gtk::Requisition*);
+ void on_size_allocate(Gtk::Allocation& a);
+
+ void note_range_changed();
+
+ struct Color {
+ Color();
+ Color(double _r, double _g, double _b);
+ inline void set(const Color& c);
+
+ double r;
+ double g;
+ double b;
+ };
+
+private:
+ static Color white;
+ static Color white_highlight;
+ static Color white_shade_light;
+ static Color white_shade_dark;
+ static Color black;
+ static Color black_highlight;
+ static Color black_shade_light;
+ static Color black_shade_dark;
+
+ PianoRollHeader(const PianoRollHeader&);
+
+ enum ItemType {
+ BLACK_SEPARATOR,
+ BLACK_MIDDLE_SEPARATOR,
+ BLACK,
+ WHITE_SEPARATOR,
+ WHITE_RECT,
+ WHITE_CF,
+ WHITE_EB,
+ WHITE_DGA
+ };
+
+ void invalidate_note_range(int lowest, int highest);
+
+ void get_path(ItemType, int note, double x[], double y[]);
+
+ void send_note_on(uint8_t note);
+ void send_note_off(uint8_t note);
+
+ MidiStreamView& _view;
+
+ ARDOUR::Byte _event[3];
+
+ Cairo::RefPtr<Cairo::Context> cc;
+ bool _active_notes[128];
+ uint8_t _highlighted_note;
+ uint8_t _clicked_note;
+ double _grab_y;
+
+ double _note_height;
+ double _black_note_width;
+};
+
+#endif /* __ardour_piano_roll_header_h__ */
diff --git a/gtk2_ardour/time_axis_view.cc b/gtk2_ardour/time_axis_view.cc
index 5788daf19d..a707e36993 100644
--- a/gtk2_ardour/time_axis_view.cc
+++ b/gtk2_ardour/time_axis_view.cc
@@ -148,9 +148,9 @@ TimeAxisView::TimeAxisView (ARDOUR::Session& sess, PublicEditor& ed, TimeAxisVie
controls_hbox.pack_start (controls_ebox,true,true);
controls_hbox.show ();
- controls_frame.add (controls_hbox);
- controls_frame.set_name ("TimeAxisViewControlsBaseUnselected");
- controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
+ //controls_frame.add (controls_hbox);
+ //controls_frame.set_name ("TimeAxisViewControlsBaseUnselected");
+ //controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
ColorsChanged.connect (mem_fun (*this, &TimeAxisView::color_handler));
}
@@ -193,13 +193,13 @@ TimeAxisView::show_at (double y, int& nth, VBox *parent)
effective_height = 0;
if (control_parent) {
- control_parent->reorder_child (controls_frame, nth);
+ control_parent->reorder_child (controls_hbox, nth);
} else {
control_parent = parent;
- parent->pack_start (controls_frame, false, false);
- parent->reorder_child (controls_frame, nth);
+ parent->pack_start (controls_hbox, false, false);
+ parent->reorder_child (controls_hbox, nth);
}
- controls_frame.show ();
+ controls_hbox.show ();
controls_ebox.show ();
/* the coordinates used here are in the system of the
@@ -299,10 +299,10 @@ TimeAxisView::hide ()
}
canvas_display->hide();
- controls_frame.hide ();
+ controls_hbox.hide ();
if (control_parent) {
- control_parent->remove (controls_frame);
+ control_parent->remove (controls_hbox);
control_parent = 0;
}
@@ -363,7 +363,7 @@ void
TimeAxisView::set_height_pixels (uint32_t h)
{
height = h;
- controls_frame.set_size_request (-1, height + ((order == 0) ? 1 : 0));
+ controls_hbox.set_size_request (-1, height + ((order == 0) ? 1 : 0));
//cerr << "TimeAxisView::set_height_pixels() called h = " << h << endl;//DEBUG
if (canvas_item_visible (selection_group)) {
/* resize the selection rect */
@@ -551,7 +551,7 @@ TimeAxisView::set_selected (bool yn)
if (_selected) {
controls_ebox.set_name (controls_base_selected_name);
- controls_frame.set_name (controls_base_selected_name);
+ controls_hbox.set_name (controls_base_selected_name);
/* propagate any existing selection, if the mode is right */
@@ -561,7 +561,7 @@ TimeAxisView::set_selected (bool yn)
} else {
controls_ebox.set_name (controls_base_unselected_name);
- controls_frame.set_name (controls_base_unselected_name);
+ controls_hbox.set_name (controls_base_unselected_name);
hide_selection ();
diff --git a/gtk2_ardour/time_axis_view.h b/gtk2_ardour/time_axis_view.h
index baf8327e67..24c91e7ba4 100644
--- a/gtk2_ardour/time_axis_view.h
+++ b/gtk2_ardour/time_axis_view.h
@@ -309,7 +309,6 @@ class TimeAxisView : public virtual AxisView
void set_height_pixels (uint32_t h);
void color_handler ();
-
}; /* class TimeAxisView */
#endif /* __ardour_gtk_time_axis_h__ */
diff --git a/libs/gtkmm2ext/SConscript b/libs/gtkmm2ext/SConscript
index 3660106b73..829182c71e 100644
--- a/libs/gtkmm2ext/SConscript
+++ b/libs/gtkmm2ext/SConscript
@@ -50,6 +50,7 @@ pixfader.cc
pixscroller.cc
popup.cc
prompter.cc
+scroomer.cc
selector.cc
slider_controller.cc
stateful_button.cc
diff --git a/libs/gtkmm2ext/gtkmm2ext/scroomer.h b/libs/gtkmm2ext/gtkmm2ext/scroomer.h
new file mode 100644
index 0000000000..d4f2ce6aa3
--- /dev/null
+++ b/libs/gtkmm2ext/gtkmm2ext/scroomer.h
@@ -0,0 +1,88 @@
+/*
+ Copyright (C) 2008 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.
+
+*/
+
+#ifndef __gtkmm2ext_scroomer_h__
+#define __gtkmm2ext_scroomer_h__
+
+#include <gtkmm/drawingarea.h>
+#include <gtkmm/adjustment.h>
+#include <gdkmm.h>
+
+namespace Gtkmm2ext {
+
+class Scroomer : public Gtk::DrawingArea
+{
+public:
+ enum Component {
+ TopBase = 0,
+ Handle1 = 1,
+ Slider = 2,
+ Handle2 = 3,
+ BottomBase = 4,
+ Total = 5,
+ None = 6
+ };
+
+ Scroomer(Gtk::Adjustment& adjustment);
+ ~Scroomer();
+
+ bool on_motion_notify_event (GdkEventMotion*);
+ bool on_button_press_event (GdkEventButton*);
+ bool on_button_release_event (GdkEventButton*);
+ bool on_scroll_event (GdkEventScroll*);
+ virtual void on_size_allocate (Gtk::Allocation&);
+
+ void set_comp_rect(GdkRectangle&, Component) const;
+
+ Component point_in(double point) const;
+
+ void set_min_page_size(double page_size);
+ int get_handle_size() { return handle_size; }
+
+ inline int position_of(Component comp) { return position[comp]; }
+
+ // debug
+ std::string get_comp_name(Component);
+
+protected:
+ Gtk::Adjustment& adj;
+
+private:
+ struct UpdateRect {
+ GdkRectangle rect;
+ Component first_comp;
+ };
+
+ void update();
+ void adjustment_changed ();
+
+ int position[6];
+ int old_pos[6];
+ int handle_size;
+ double min_page_size;
+ GdkWindow* grab_window;
+ Component grab_comp;
+ double grab_y;
+ double unzoomed_val;
+ double unzoomed_page;
+};
+
+} // namespace
+
+#endif /* __gtkmm2ext_scroomer_h__ */
diff --git a/libs/gtkmm2ext/scroomer.cc b/libs/gtkmm2ext/scroomer.cc
new file mode 100644
index 0000000000..50c17a46c6
--- /dev/null
+++ b/libs/gtkmm2ext/scroomer.cc
@@ -0,0 +1,393 @@
+/*
+ Copyright (C) 2008 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 <iostream>
+#include <gtkmm2ext/scroomer.h>
+
+using namespace Gtkmm2ext;
+using namespace Gtk;
+using namespace Gdk;
+using namespace std;
+
+Scroomer::Scroomer(Gtk::Adjustment& adjustment)
+ : adj(adjustment)
+ , handle_size(0)
+ , grab_comp(None) {
+
+ position[TopBase] = 0;
+ position[Handle1] = 0;
+ position[Slider] = 0;
+ position[Handle2] = 0;
+ position[BottomBase] = 0;
+ position[Total] = 0;
+
+ add_events (Gdk::BUTTON_PRESS_MASK |
+ Gdk::BUTTON_RELEASE_MASK |
+ Gdk::POINTER_MOTION_MASK |
+ Gdk::SCROLL_MASK);
+
+ adjustment.signal_value_changed().connect (mem_fun (*this, &Scroomer::adjustment_changed));
+ //adjustment.signal_changed().connect (mem_fun (*this, &Scroomer::adjustment_changed));
+}
+
+Scroomer::~Scroomer() {
+}
+
+bool
+Scroomer::on_motion_notify_event (GdkEventMotion* ev) {
+ double range = adj.get_upper() - adj.get_lower();
+ double pixel2val = range / get_height();
+ double val_at_pointer = ((get_height() - ev->y) * pixel2val) + adj.get_lower();
+ double delta_y = ev->y - grab_y;
+ double half_min_page = min_page_size / 2;
+ double fract = delta_y / position[Total];
+ double scale, temp, zoom;
+ double val, page;
+
+ if(grab_comp == None || grab_comp == Total) {
+ return true;
+ }
+
+ if (ev->window != grab_window) {
+ grab_y = ev->y;
+ grab_window = ev->window;
+ return true;
+ }
+
+ grab_y = ev->y;
+
+ if (ev->state & GDK_CONTROL_MASK) {
+ if (ev->state & GDK_MOD1_MASK) {
+ scale = 0.05;
+ } else {
+ scale = 0.1;
+ }
+ } else {
+ scale = 1.0;
+ }
+
+ fract = min (1.0, fract);
+ fract = max (-1.0, fract);
+ fract = -fract;
+
+ switch(grab_comp) {
+ case TopBase:
+ case BottomBase:
+ unzoomed_val += scale * fract * range;
+ unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page);
+ unzoomed_val = max(unzoomed_val, adj.get_lower());
+ break;
+ case Slider:
+ unzoomed_val += scale * fract * range;
+ unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page);
+ unzoomed_val = max(unzoomed_val, adj.get_lower());
+ break;
+ case Handle1:
+ unzoomed_page += scale * fract * range;
+ unzoomed_page = min(unzoomed_page, adj.get_upper() - unzoomed_val);
+ unzoomed_page = max(unzoomed_page, min_page_size);
+ break;
+ case Handle2:
+ temp = unzoomed_val + unzoomed_page;
+ unzoomed_val += scale * fract * range;
+ unzoomed_val = min(unzoomed_val, temp - min_page_size);
+ unzoomed_val = max(unzoomed_val, adj.get_lower());
+
+ unzoomed_page = temp - unzoomed_val;
+ unzoomed_page = max(unzoomed_page, min_page_size);
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Then we handle zoom, which is dragging horizontally. We zoom around the area that is
+ * the current y pointer value, not from the area that was the start of the drag.
+ * the point of zoom must have the same
+ */
+
+ if(ev->x > get_width()) {
+ zoom = ev->x - get_width();
+
+ double higher = unzoomed_val + unzoomed_page - half_min_page - val_at_pointer;
+ double lower = val_at_pointer - (unzoomed_val + half_min_page);
+
+ higher *= zoom / 128;
+ lower *= zoom / 128;
+
+ val = unzoomed_val + lower;
+ page = unzoomed_page - higher - lower;
+
+ page = max(page, min_page_size);
+
+ if(lower < 0) {
+ val = max(val, val_at_pointer - half_min_page);
+ }
+ else if(lower > 0) {
+ val = min(val, val_at_pointer - half_min_page);
+ }
+
+ val = min(val, adj.get_upper() - min_page_size);
+ page = min(page, adj.get_upper() - val);
+ }
+ else if (ev->x < 0) {
+ /* on zoom out increase the page size as well as moving the range towards the mouse pos*/
+ zoom = abs(ev->x);
+
+ /*double higher = unzoomed_val + unzoomed_page - half_min_page - val_at_pointer;
+ double lower = val_at_pointer - (unzoomed_val + half_min_page);
+
+ higher *= zoom / 128;
+ lower *= zoom / 128;
+
+ val = unzoomed_val + lower;
+ page = unzoomed_page - higher - lower;
+
+ page = max(page, min_page_size);
+
+ if(lower < 0) {
+ val = max(val, val_at_pointer - half_min_page);
+ }
+ else if(lower > 0) {
+ val = min(val, val_at_pointer - half_min_page);
+ }
+
+ val = min(val, adj.get_upper() - min_page_size);
+ page = min(page, adj.get_upper() - val);*/
+
+ val = unzoomed_val;
+ page = unzoomed_page;
+ }
+ else {
+ val = unzoomed_val;
+ page = unzoomed_page;
+ }
+
+ adj.set_page_size(page);
+
+ if(val == adj.get_value()) {
+ adj.value_changed();
+ }
+ if(val < adj.get_lower()) {
+ adj.value_changed();
+ }
+ else if(val > adj.get_upper()) {
+ adj.value_changed();
+ }
+ else {
+ adj.set_value(val);
+ }
+
+ return true;
+}
+
+bool
+Scroomer::on_button_press_event (GdkEventButton* ev) {
+ if(ev->button == 1) {
+ Component comp = point_in(ev->y);
+
+ cerr << get_comp_name(comp) << " pressed" << endl;
+
+ if(comp == Total || comp == None) {
+ return false;
+ }
+
+ add_modal_grab();
+ grab_comp = comp;
+ grab_y = ev->y;
+ unzoomed_val = adj.get_value();
+ unzoomed_page = adj.get_page_size();
+ grab_window = ev->window;
+ }
+ return false;
+}
+
+bool
+Scroomer::on_button_release_event (GdkEventButton* ev) {
+ if(grab_comp == None || grab_comp == Total) {
+ return true;
+ }
+
+ if (ev->window != grab_window) {
+ grab_y = ev->y;
+ grab_window = ev->window;
+ return true;
+ }
+
+ if (ev->button != 1) {
+ return true;
+ }
+
+ switch(grab_comp) {
+ case TopBase:
+ break;
+ case Handle1:
+ break;
+ case Slider:
+ break;
+ case Handle2:
+ break;
+ case BottomBase:
+ break;
+ default:
+ break;
+ }
+
+ grab_comp = None;
+
+ remove_modal_grab();
+ return true;
+}
+
+bool
+Scroomer::on_scroll_event (GdkEventScroll*) {
+ return true;
+}
+
+void
+Scroomer::on_size_allocate (Allocation& a) {
+ Gtk::DrawingArea::on_size_allocate(a);
+
+ cerr << "allocate" << endl;
+
+ position[Total] = a.get_height();
+ set_min_page_size(min_page_size);
+ update();
+}
+
+/*
+ * assumes that x and width are correct, and they will not be altered
+ */
+void
+Scroomer::set_comp_rect(GdkRectangle& r, Component c) const {
+ int index = (int) c;
+
+ switch(c) {
+ case None:
+ return;
+ case Total:
+ r.y = 0;
+ r.height = position[Total];
+ break;
+ default:
+ r.y = position[index];
+ r.height = position[index+1] - position[index];
+ break;
+ }
+}
+
+Scroomer::Component
+Scroomer::point_in(double point) const {
+ for(int i = 0; i < Total; ++i) {
+ if(position[i+1] >= point) {
+ return (Component) i;
+ }
+ }
+
+ return None;
+}
+
+void
+Scroomer::set_min_page_size(double ps) {
+ double coeff = ((double)position[Total]) / (adj.get_upper() - adj.get_lower());
+
+ min_page_size = ps;
+ handle_size = (int) floor((ps * coeff) / 2);
+}
+
+void
+Scroomer::update() {
+ double range = adj.get_upper() - adj.get_lower();
+ double value = adj.get_value() - adj.get_lower();
+ int height = position[Total];
+ double coeff = ((double) height) / range;
+
+ /* save the old positions to calculate update regions later*/
+ for(int i = Handle1; i < Total; ++i) {
+ old_pos[i] = position[i];
+ }
+
+ position[BottomBase] = (int) floor(height - (adj.get_value() * coeff));
+ position[Handle2] = position[BottomBase] - handle_size;
+
+ position[Handle1] = (int) floor(height - ((adj.get_value() + adj.get_page_size()) * coeff));
+ position[Slider] = position[Handle1] + handle_size;
+}
+
+void
+Scroomer::adjustment_changed() {
+ //cerr << floor(adj.get_value()) << " " << floor(adj.get_value() + adj.get_page_size()) << endl;
+ Gdk::Rectangle rect;
+ Glib::RefPtr<Gdk::Window> win = get_window();
+
+ update();
+
+ if(!win) {
+ return;
+ }
+
+ rect.set_x(0);
+ rect.set_width(get_width());
+
+ if(position[Handle1] < old_pos[Handle1]) {
+ rect.set_y(position[Handle1]);
+ rect.set_height(old_pos[Slider] - position[Handle1]);
+ win->invalidate_rect(rect, false);
+ }
+ else if(position[Handle1] > old_pos[Handle1]) {
+ rect.set_y(old_pos[Handle1]);
+ rect.set_height(position[Slider] - old_pos[Handle1]);
+ win->invalidate_rect(rect, false);
+ }
+
+ if(position[Handle2] < old_pos[Handle2]) {
+ rect.set_y(position[Handle2]);
+ rect.set_height(old_pos[BottomBase] - position[Handle2]);
+ win->invalidate_rect(rect, false);
+ }
+ else if(position[Handle2] > old_pos[Handle2]) {
+ rect.set_y(old_pos[Handle2]);
+ rect.set_height(position[BottomBase] - old_pos[Handle2]);
+ win->invalidate_rect(rect, false);
+ }
+
+ win->process_updates(false);
+}
+
+std::string
+Scroomer::get_comp_name(Component c) {
+ switch(c) {
+ case TopBase:
+ return "TopBase";
+ case Handle1:
+ return "Handle1";
+ case Slider:
+ return "Slider";
+ case Handle2:
+ return "Handle2";
+ case BottomBase:
+ return "BottomBase";
+ case Total:
+ return "Total";
+ case None:
+ return "None";
+ default:
+ return "ERROR";
+ }
+}