Apply MIDI keyboard and "scroomer" patch.
authorDavid Robillard <d@drobilla.net>
Sun, 13 Jan 2008 17:45:17 +0000 (17:45 +0000)
committerDavid Robillard <d@drobilla.net>
Sun, 13 Jan 2008 17:45:17 +0000 (17:45 +0000)
git-svn-id: svn://localhost/ardour2/trunk@2908 d708f5d6-7413-0410-9779-e7cbd77b26cf

22 files changed:
gtk2_ardour/SConscript
gtk2_ardour/ardour2_ui_default.conf
gtk2_ardour/canvas.h
gtk2_ardour/canvas_vars.h
gtk2_ardour/editor.cc
gtk2_ardour/lineset.cc [new file with mode: 0644]
gtk2_ardour/lineset.h [new file with mode: 0644]
gtk2_ardour/midi_region_view.cc
gtk2_ardour/midi_region_view.h
gtk2_ardour/midi_scroomer.cc [new file with mode: 0644]
gtk2_ardour/midi_scroomer.h [new file with mode: 0644]
gtk2_ardour/midi_streamview.cc
gtk2_ardour/midi_streamview.h
gtk2_ardour/midi_time_axis.cc
gtk2_ardour/midi_time_axis.h
gtk2_ardour/piano_roll_header.cc [new file with mode: 0644]
gtk2_ardour/piano_roll_header.h [new file with mode: 0644]
gtk2_ardour/time_axis_view.cc
gtk2_ardour/time_axis_view.h
libs/gtkmm2ext/SConscript
libs/gtkmm2ext/gtkmm2ext/scroomer.h [new file with mode: 0644]
libs/gtkmm2ext/scroomer.cc [new file with mode: 0644]

index 4d34a461766628cc518fa2546ad7cef51cbd1708..f03286243bae0011197bc02213f76491de6e2f12 100644 (file)
@@ -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
index 80f53f7181bfabc9a4264b59092bccbb8f0d9213..c45e4da9c667e6d2a6d13983064f217bed426fbd 100644 (file)
@@ -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"/>
     <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>
index 75cb7157a9acf8aad2ed6cf4a6030fbe0efe6b94..9fa737258c74278de05380d55653ab9b04bf725d 100644 (file)
@@ -33,6 +33,7 @@ namespace Gnome {
                class Line;
                class Points;
                class ImageFrame;
+               class Lineset;
        }
 }
 
index 7d291b7a5d5b9f9c5061566b929f4ee11479b2a0..bc17a1d9881ac9a6c66ff403455be0e37339f1a6 100644 (file)
@@ -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")
index 41261b7079b868813b56a7aad44f64f3b627e67a..3ff52d69e7d843e5a5affee73291afb49088c3bb 100644 (file)
@@ -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 (file)
index 0000000..46c0670
--- /dev/null
@@ -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 (file)
index 0000000..a5d0516
--- /dev/null
@@ -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__ */
index ec326f1e2ea23f733c7ffff409ab7418daaddef4..177adbf38bed438b72de669a7a3529aa9805a808 100644 (file)
@@ -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();
+               }
+       }
+}
index 20c7671f5bbc0377a3836ba9d304c452d4e510ff..a709f523a3aa660f5fd5ff63039fb35a2400ca35 100644 (file)
@@ -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 (file)
index 0000000..a38911d
--- /dev/null
@@ -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 (file)
index 0000000..a0021de
--- /dev/null
@@ -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__ */
index 9e0f9f368f2156c0b0bdf3235fd26846eb19d269..172d30b7ef6e6256a4b6a60b81d6d21eccf4c87a 100644 (file)
@@ -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);
+}
index fa414cfe9cf9f38eb33b9be5aca3ff7ac9845ea6..5c0ee47c3065e982e6326f0187bd75fbeba04294 100644 (file)
 
 #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__ */
index 21848fff59de2e99d1fa8e59a6680833aeefccbf..b3892d2790236357909b27bd9443b51b902cf199 100644 (file)
@@ -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 ()
                }
        }
 }
-
-
index f7ec164edc24f5da9e3ec4b3fa0aa13f9f93780e..15e487e00e5d8e0eb7e2024f4bb8bbf79552e0c3 100644 (file)
@@ -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 (file)
index 0000000..8be0bc8
--- /dev/null
@@ -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 (file)
index 0000000..141e766
--- /dev/null
@@ -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__ */
index 5788daf19d67083f209c40a1387b2dd748514c8d..a707e3699396d5d702e11e853cf793f2818d3b6e 100644 (file)
@@ -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 ();
 
index baf8327e672d03128edd86548667aa0b698297a9..24c91e7ba4e55ac267a9b3179083bef9218e02f9 100644 (file)
@@ -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__ */
index 3660106b73e1d4970e708b66204d724d80912324..829182c71e327d2adef2e83c1cb90cb3baa9e5a6 100644 (file)
@@ -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 (file)
index 0000000..d4f2ce6
--- /dev/null
@@ -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 (file)
index 0000000..50c17a4
--- /dev/null
@@ -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";
+       }
+}