Apply MIDI keyboard and "scroomer" patch.

git-svn-id: svn://localhost/ardour2/trunk@2908 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard 2008-01-13 17:45:17 +00:00
parent f8e3d652e9
commit 2db91c126e
22 changed files with 2596 additions and 48 deletions

View 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

View 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"/>
@ -100,5 +101,8 @@
<Option name="midi note fill mid" value="eeee338a"/>
<Option name="midi note fill max" value="ee33338a"/>
<Option name="midi note selected outline" value="5566ffee"/>
<Option name="piano roll white" value="aa585865"/>
<Option name="piano roll black" value="88393b6b"/>
<Option name="piano roll black outline" value="b5b5b576"/>
</Canvas>
</Ardour>

View file

@ -33,6 +33,7 @@ namespace Gnome {
class Line;
class Points;
class ImageFrame;
class Lineset;
}
}

View 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")

View 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"

782
gtk2_ardour/lineset.cc Normal file
View file

@ -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 */

208
gtk2_ardour/lineset.h Normal file
View file

@ -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__ */

View 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();
}
}
}

View 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&);

View file

@ -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);
}

View file

@ -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__ */

View 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)
{
@ -64,18 +66,19 @@ MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
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);
_note_lines = new ArdourCanvas::Lineset(*canvas_group, ArdourCanvas::Lineset::Horizontal);
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->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);
}

View file

@ -24,10 +24,10 @@
#include <ardour/location.h>
#include "enums.h"
#include "simplerect.h"
#include "streamview.h"
#include "time_axis_view_item.h"
#include "route_time_axis.h"
#include "canvas.h"
namespace Gdk {
class Color;
@ -66,8 +66,11 @@ class MidiStreamView : public StreamView
ContentsRange
};
Gtk::Adjustment note_range_adjustment;
VisibleNoteRange note_range() { return _range; }
void set_note_range(VisibleNoteRange r);
void set_note_range(uint8_t lowest, uint8_t highest);
uint8_t lowest_note() const { return (_range == FullRange) ? 0 : _lowest_note; }
uint8_t highest_note() const { return (_range == FullRange) ? 127 : _highest_note; }
@ -94,6 +97,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 ();
void rec_data_range_ready (jack_nframes_t start, jack_nframes_t dur, boost::weak_ptr<ARDOUR::Source> src);
@ -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__ */

View 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 ()
}
}
}

View file

@ -50,6 +50,8 @@ namespace ARDOUR {
class PublicEditor;
class MidiStreamView;
class MidiScroomer;
class PianoRollHeader;
class MidiTimeAxisView : public RouteTimeAxisView
{
@ -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;

View file

@ -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);
}
}

View file

@ -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__ */

View 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 ();

View 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__ */

View file

@ -50,6 +50,7 @@ pixfader.cc
pixscroller.cc
popup.cc
prompter.cc
scroomer.cc
selector.cc
slider_controller.cc
stateful_button.cc

View file

@ -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__ */

393
libs/gtkmm2ext/scroomer.cc Normal file
View file

@ -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";
}
}