non/sequencer/src/canvas.C

1662 lines
40 KiB
C++
Raw Normal View History

2007-12-18 05:09:02 +01:00
/*******************************************************************************/
/* Copyright (C) 2007-2008 Jonathan Moore Liles */
/* */
/* 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; see the file COPYING. If not,write to the Free Software */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/*******************************************************************************/
/* This is a generic double-buffering, optimizing canvas interface to
grids (patterns and phrases). It draws only what is necessary to keep
the display up-to-date. Actual drawing functions are in draw.C */
#include <FL/Fl.H>
#include <FL/Fl_Cairo.H>
2007-12-18 05:09:02 +01:00
#include "canvas.H"
#include "pattern.H"
#include "common.h"
#include "non.H"
#include <FL/fl_draw.H>
#include <FL/Fl.H>
#include "gui/ui.H"
#include <FL/Fl_Panzoomer.H>
#include <FL/Fl_Slider.H>
using namespace MIDI;
extern UI *ui;
2007-12-18 05:09:02 +01:00
extern Fl_Color velocity_colors[];
const int ruler_height = 14;
2007-12-18 05:09:02 +01:00
class Canvas::Canvas_Panzoomer : public Fl_Panzoomer
{
Fl_Offscreen backbuffer;
public:
Canvas_Panzoomer( int X, int Y,int W, int H, const char *L = 0 )
: Fl_Panzoomer(X,Y,W,H,L)
{
backbuffer = 0;
}
Canvas *canvas;
private:
static void draw_dash ( tick_t x, int y, tick_t l, int color, int selected, void *userdata )
{
Canvas_Panzoomer *o = (Canvas_Panzoomer*)userdata;
o->draw_dash( x,y,l,color,selected );
}
void draw_dash ( tick_t x, int y, tick_t w, int color, int selected ) const
{
if ( selected )
color = FL_MAGENTA;
else
color = velocity_colors[ color ];
Canvas *c = canvas;
double VS = (double)this->h() / c->m.maxh;
double HS = (double)this->w() / ( _xmax - _xmin );
y = c->ntr( y );
if ( y < 0 )
return;
y *= VS;
fl_color( fl_color_average( color, FL_GRAY, 0.5 ) );
fl_rectf(
x * HS,
y,
(w * HS) + 0.5,
1 * VS + 0.5 );
}
protected:
void draw_background ( int X, int Y, int W, int H )
{
/* DMESSAGE( "%s%s%s%s%s%s", */
/* damage() & FL_DAMAGE_CHILD ? "CHILD " : "", */
/* damage() & FL_DAMAGE_ALL ? "ALL " : "", */
/* damage() & FL_DAMAGE_USER1 ? "USER 1 ": "", */
/* damage() & FL_DAMAGE_EXPOSE ? "EXPOSE " : "", */
/* damage() & FL_DAMAGE_SCROLL ? "SCROLL " : "", */
/* damage() & FL_DAMAGE_OVERLAY ? "OVERLAY " : ""); */
if ( ! backbuffer ||
! ( damage() & FL_DAMAGE_USER1 ) )
{
if ( !backbuffer )
backbuffer = fl_create_offscreen( W, H );
DMESSAGE( "redrawing preview" );
fl_begin_offscreen(backbuffer);
fl_rectf( 0, 0, W, H, color() );
canvas->m.grid->draw_notes( draw_dash, this );
fl_end_offscreen();
}
fl_copy_offscreen( X,Y,W,H,backbuffer,0, 0 );
}
public:
void resize ( int X, int Y, int W, int H )
{
Fl_Panzoomer::resize( X,Y,W,H );
if ( backbuffer )
fl_delete_offscreen( backbuffer );
backbuffer = 0;
redraw();
}
void draw_overlay ( void )
{
Canvas *c = canvas;
double HS = (double)w() /( _xmax - _xmin );
tick_t new_x = c->grid()->index();
fl_color( fl_color_add_alpha( FL_GREEN, 100 ) );
fl_line( x() + new_x * HS, y(), x() + new_x * HS, y() + h() );
}
};
static note_properties *ghost_note = 0;
Canvas::Canvas ( int X, int Y, int W, int H, const char *L ) : Fl_Group( X,Y,W,H,L )
2007-12-18 05:09:02 +01:00
{
{ Fl_Box *o = new Fl_Box( X, Y, W, H - 75 );
/* this is a dummy group where the canvas goes */
Fl_Group::current()->resizable( o );
}
{ Fl_Group *o = new Fl_Group( X, Y + H - 75, W, 75 );
{
Canvas_Panzoomer *o = new Canvas_Panzoomer( X, Y + H - 75, W - 14, 75 );
o->canvas = this;
o->box( FL_FLAT_BOX );
// o->color(fl_color_average( FL_BLACK, FL_WHITE, 0.90 ));
o->color( FL_BLACK );
// o->color(FL_BACKGROUND_COLOR);
// o->type( FL_HORIZONTAL );
o->callback( cb_scroll, this );
o->when( FL_WHEN_CHANGED );
panzoomer = o;
}
{
Fl_Slider *o = new Fl_Slider( X + W - 14, Y + H - panzoomer->h(), 14, panzoomer->h() );
o->range( 1, 128 );
o->step( 1 );
o->type( FL_VERTICAL );
o->tooltip( "Vertical Zoom" );
o->callback( cb_scroll, this );
vzoom = o;
}
o->end();
}
m.origin_x = m.origin_y = m.height = m.width = m.div_w = m.div_h = m.margin_top = m.margin_left = m.playhead = m.w = m.h = m.p1 = m.p2 = m.p3 = m.p4 = 0;
2007-12-18 05:09:02 +01:00
m.margin_top = ruler_height;
m.draw = false;
2007-12-18 05:09:02 +01:00
m.ruler_drawn = false;
m.grid_drawn = false;
// m.current = m.previous = NULL;
2007-12-18 05:09:02 +01:00
m.row_compact = true;
m.maxh = 128;
m.vp = NULL;
end();
}
Canvas::~Canvas ( )
{
2007-12-18 05:09:02 +01:00
}
void
Canvas::handle_event_change ( void )
{
/* mark the song as dirty and pass the signal on */
song.set_dirty();
// panzoomer->redraw();
redraw();
2007-12-18 05:09:02 +01:00
}
/** change grid to /g/, returns TRUE if new grid size differs from old */
void
2007-12-18 05:09:02 +01:00
Canvas::grid ( Grid *g )
{
m.grid = g;
if ( ! g )
return;
2007-12-18 05:09:02 +01:00
m.vp = &g->viewport;
char *s = m.vp->dump();
DMESSAGE( "viewport: %s", s );
2007-12-18 05:09:02 +01:00
free( s );
m.ruler_drawn = false;
2007-12-18 05:09:02 +01:00
resize_grid();
vzoom->range( 1, m.maxh );
vzoom->value( m.vp->h );
update_mapping();
2007-12-18 05:09:02 +01:00
/* connect signals */
/* FIXME: what happens when we do this twice? */
g->signal_events_change.connect( mem_fun( this, &Canvas::handle_event_change ) );
g->signal_settings_change.connect( signal_settings_change.make_slot() );
redraw();
2007-12-18 05:09:02 +01:00
// parent()->redraw();
// signal_settings_change();
2007-12-18 05:09:02 +01:00
}
/** keep row compaction tables up-to-date */
void
Canvas::_update_row_mapping ( void )
{
/* reset */
for ( int i = 128; i-- ; )
m.rtn[i] = m.ntr[i] = -1;
DMESSAGE( "updating row mapping" );
2007-12-18 05:09:02 +01:00
/* rebuild */
int r = 0;
for ( int n = 0; n < 128; ++n )
{
if ( m.grid->row_name( n ) )
{
m.rtn[r] = n;
m.ntr[n] = r;
++r;
}
}
if ( m.row_compact && r )
m.maxh = r;
else
m.maxh = 128;
m.vp->h = min( m.vp->h, m.maxh );
resize_grid();
2007-12-18 05:09:02 +01:00
}
/** update everything about mapping, leaving the viewport alone */
2007-12-18 05:09:02 +01:00
void
Canvas::update_mapping ( void )
2007-12-18 05:09:02 +01:00
{
_update_row_mapping();
adj_size();
2007-12-18 05:09:02 +01:00
int old_margin = m.margin_left;
m.margin_left = 0;
m.draw = false;
m.grid->draw_row_names( this );
2012-03-01 08:00:59 +01:00
m.draw = true;
/* if ( m.margin_left != old_margin ) */
/* { */
/* // signal_resize(); */
/* redraw(); */
/* } */
/* else */
damage(FL_DAMAGE_USER1);
2007-12-18 05:09:02 +01:00
}
/** change grid mapping */
void
Canvas::changed_mapping ( void )
{
update_mapping();
m.vp->h = min( m.vp->h, m.maxh );
if ( m.vp->y + m.vp->h > m.maxh )
m.vp->y = (m.maxh / 2) - (m.vp->h / 2);
}
2007-12-18 05:09:02 +01:00
Grid *
Canvas::grid ( void )
{
return m.grid;
}
/** recalculate node sizes based on physical dimensions */
void
Canvas::adj_size ( void )
2007-12-18 05:09:02 +01:00
{
if ( ! m.vp )
return;
m.div_w = (m.width - m.margin_left) / m.vp->w;
m.div_h = (m.height - m.margin_top) / m.vp->h;
}
/** reallocate buffers to match grid dimensions */
void
Canvas::resize_grid ( void )
{
adj_size();
DMESSAGE( "resizing grid %dx%d", m.vp->w, m.vp->h );
2007-12-18 05:09:02 +01:00
Grid *g = grid();
panzoomer->x_value( g->x_to_ts( m.vp->x), g->x_to_ts( m.vp->w ), 0, g->length());
panzoomer->y_value( m.vp->y, m.vp->h, 0, m.maxh );
2007-12-18 05:09:02 +01:00
panzoomer->zoom_range( 2, 16 );
2007-12-18 05:09:02 +01:00
// m.vp->w = max( 32, min( (int)(m.vp->w * n), 256 ) );
2007-12-18 05:09:02 +01:00
}
/** inform the canvas with new phsyical dimensions */
void
Canvas::resize ( int x, int y, int w, int h )
{
m.origin_x = x;
m.origin_y = y;
m.width = w;
m.height = h - 75;
Fl_Group::resize(x,y,w,h);
2007-12-18 05:09:02 +01:00
adj_size();
2007-12-18 05:09:02 +01:00
}
/***********/
/* Drawing */
/***********/
/** is /x/ within the viewport? */
bool
Canvas::viewable_x ( int x )
{
return x >= m.vp->x && x < m.vp->x + m.vp->w;
}
static
int
gui_draw_ruler ( int x, int y, int w, int div_w, int div, int ofs, int p1, int p2 )
{
/* Across the top */
fl_font( FL_TIMES, ruler_height );
int h = ruler_height;
fl_color( FL_BACKGROUND_COLOR );
w += 100; /* FIXME: hack */
// fl_rectf( x, y, x + (div_w * w), y + h );
fl_rectf( x, y, (div_w * w), h );
fl_color( FL_FOREGROUND_COLOR );
fl_line( x + div_w / 2, y, x + div_w * w, y );
char pat[40];
int z = div;
int i;
for ( i = 0; i < w; i++ )
{
int k = ofs + i;
if ( 0 == k % z )
{
int nx = x + (i * div_w) + (div_w / 2);
2007-12-18 05:09:02 +01:00
fl_color( FL_FOREGROUND_COLOR );
2007-12-18 05:09:02 +01:00
fl_line( nx, y, nx, y + h - 1 );
sprintf( pat, "%i", 1 + (k / z ));
fl_color( FL_FOREGROUND_COLOR );
fl_draw( pat, nx + div_w / 2, y + h + 1 / 2 );
}
}
/* if ( p1 != p2 ) */
/* { */
/* if ( p1 >= 0 ) */
/* { */
/* if ( p1 < p2 ) */
/* fl_color( FL_GREEN ); */
/* else */
/* fl_color( FL_RED ); */
/* fl_rectf( x + (div_w * p1), y + h / 2, div_w, h / 2 ); */
/* } */
/* if ( p2 >= 0 ) */
/* { */
/* if ( p2 < p1 ) */
/* fl_color( FL_GREEN ); */
/* else */
/* fl_color( FL_RED ); */
/* fl_rectf( x + (div_w * p2), y + h / 2, div_w, h / 2 ); */
2007-12-18 05:09:02 +01:00
/* } */
/* } */
2007-12-18 05:09:02 +01:00
return h;
}
static
int
gui_draw_string ( int x, int y, int w, int h, int color, const char *s, bool draw )
{
int rw;
if ( ! s )
return 0;
fl_font( FL_COURIER, min( h, 18 ) );
rw = fl_width( s );
if ( fl_not_clipped( x, y, rw, h ) && draw )
{
// fl_rectf( x,y,w,h, FL_BACKGROUND_COLOR );
if ( color )
fl_color( velocity_colors[ color ] );
else
fl_color( FL_DARK_CYAN );
fl_draw( s, x, y + h / 2 + fl_descent() );
}
return rw;
2007-12-18 05:09:02 +01:00
}
/** callback called by Grid::draw_row_names() to draw an individual row name */
void
Canvas::draw_row_name ( int y, const char *name, int color )
{
bool draw = m.draw;
bool clear = false;
y = ntr( y );
if ( ! m.row_compact && ! name )
clear = true;
y -= m.vp->y;
int bx = m.origin_x;
int by = m.origin_y + m.margin_top + y * m.div_h;
2012-03-01 08:00:59 +01:00
int bw = m.margin_left;
2007-12-18 05:09:02 +01:00
int bh = m.div_h;
if ( y < 0 || y >= m.vp->h )
draw = false;
if ( draw && name )
{
fl_rectf( bx, by, bw, bh, index(name, '#') ? FL_GRAY : FL_BLACK );
fl_rect( bx, by, bw, bh, FL_BLACK );
}
m.margin_left = max( m.margin_left, gui_draw_string( bx + 1, by + 2,
bw - 1, bh - 4,
color,
name,
draw ) );
2007-12-18 05:09:02 +01:00
}
void
Canvas::draw_mapping ( void )
2007-12-18 05:09:02 +01:00
{
int old_margin = m.margin_left;
2007-12-18 05:09:02 +01:00
m.margin_left = 0;
m.draw = false;
m.grid->draw_row_names( this );
adj_size();
2007-12-18 05:09:02 +01:00
m.draw = true;
m.grid->draw_row_names( this );
}
void
Canvas::draw_ruler ( void )
{
m.margin_top = gui_draw_ruler( m.origin_x + m.margin_left,
m.origin_y,
m.vp->w,
m.div_w,
m.grid->division(),
m.vp->x,
m.p1 - m.vp->x,
m.p2 - m.vp->x );
2007-12-18 05:09:02 +01:00
}
void
Canvas::damage_grid ( tick_t x, int y, tick_t w, int h = 1 )
2007-12-18 05:09:02 +01:00
{
y = ntr( y );
if ( y < 0 )
return;
2007-12-18 05:09:02 +01:00
// adjust for viewport.
x = m.grid->ts_to_x(x);
w = m.grid->ts_to_x(w);
2007-12-18 05:09:02 +01:00
x -= m.vp->x;
y -= m.vp->y;
if ( x < 0 || y < 0 || x >= m.vp->w || y >= m.vp->h )
return;
damage(FL_DAMAGE_USER1, m.origin_x + m.margin_left + x * m.div_w,
m.origin_y + m.margin_top + y * m.div_h,
m.div_w * w,
m.div_h * h );
2007-12-18 05:09:02 +01:00
}
void
Canvas::draw_dash ( tick_t x, int y, tick_t w, int color, int selected ) const
2007-12-18 05:09:02 +01:00
{
if ( m.grid->velocity_sensitive() )
color = velocity_colors[ color ];
else
color = velocity_colors[ 127 ];
y = ntr( y );
if ( y < 0 )
return;
// adjust for viewport.
x = m.grid->ts_to_x(x);
w = m.grid->ts_to_x(w);
x -= m.vp->x;
y -= m.vp->y;
x = m.origin_x + m.margin_left + x * m.div_w;
y = m.origin_y + m.margin_top + y * m.div_h;
w *= m.div_w;
/* fl_rectf( x, y + 1, w, m.div_h - 1, fl_color_add_alpha( color, 170 ) ); */
/* fl_rect( x, y + 1, w, m.div_h - 1, selected ? FL_MAGENTA : fl_lighter( FL_BACKGROUND_COLOR )); */
fl_draw_box( FL_ROUNDED_BOX, x, y + 1, w, m.div_h - 1, color );
if ( selected )
fl_draw_box( FL_ROUNDED_FRAME, x, y + 1, w, m.div_h - 1, FL_MAGENTA );
// fl_color_add_alpha( color, 170 ));
2007-12-18 05:09:02 +01:00
}
/** callback used by Grid::draw() */
2007-12-18 05:09:02 +01:00
void
Canvas::draw_dash ( tick_t x, int y, tick_t w, int color, int selected, void *userdata )
2007-12-18 05:09:02 +01:00
{
Canvas *o = (Canvas*)userdata;
o->draw_dash( x,y,w,color,selected );
2007-12-18 05:09:02 +01:00
}
int
Canvas::playhead_moved ( void )
{
int x = m.grid->ts_to_x( m.grid->index() );
return m.playhead != x;
}
2007-12-18 05:09:02 +01:00
void
Canvas::redraw_playhead ( void )
{
int old_x = m.playhead;
int new_x = m.grid->ts_to_x( m.grid->index() );
if ( old_x != new_x )
{
window()->damage( FL_DAMAGE_OVERLAY );
}
if ( m.playhead < m.vp->x || m.playhead >= m.vp->x + m.vp->w )
{
if ( config.follow_playhead )
{
new_x = m.playhead;
panzoomer->x_value( m.grid->index() );
panzoomer->do_callback();
}
}
}
void
Canvas::draw_overlay ( void )
{
if ( ! visible_r() )
return;
fl_push_no_clip();
fl_push_clip( x() + m.margin_left,
y() + m.margin_top,
w() - m.margin_left,
h() - panzoomer->h() - m.margin_top );
draw_playhead();
fl_pop_clip();
panzoomer->draw_overlay();
fl_pop_clip();
}
2007-12-18 05:09:02 +01:00
/** draw only the playhead--without reexamining the grid */
void
2007-12-18 05:09:02 +01:00
Canvas::draw_playhead ( void )
{
int x = m.grid->ts_to_x( m.grid->index() );
2007-12-18 05:09:02 +01:00
if ( m.playhead == x )
return;
2007-12-18 05:09:02 +01:00
m.playhead = x;
2007-12-18 05:09:02 +01:00
if ( m.playhead < m.vp->x || m.playhead >= m.vp->x + m.vp->w )
return;
2007-12-18 05:09:02 +01:00
int px = m.origin_x + m.margin_left + ( x - m.vp->x ) * m.div_w;
2007-12-18 05:09:02 +01:00
int X,Y,W,H;
X = px;
Y = m.origin_y + m.margin_top;
W = m.div_w;
H = m.origin_y + m.margin_top + m.vp->h * m.div_h;
2007-12-18 05:09:02 +01:00
fl_rectf( X,Y,W,H, fl_color_add_alpha( FL_RED, 127 ) );
}
2007-12-18 05:09:02 +01:00
void
Canvas::draw_clip ( void *v, int X, int Y, int W, int H )
{
((Canvas*)v)->draw_clip( X,Y,W,H );
}
2007-12-18 05:09:02 +01:00
void
Canvas::draw_clip ( int X, int Y, int W, int H )
{
box( FL_FLAT_BOX );
labeltype( FL_NO_LABEL );
2007-12-18 05:09:02 +01:00
fl_push_clip( X,Y,W,H );
2007-12-18 05:09:02 +01:00
2012-03-01 04:57:23 +01:00
fl_push_clip( m.origin_x + m.margin_left,
m.origin_y + m.margin_top,
w() - m.margin_left,
h() - m.margin_top - panzoomer->h() );
fl_rectf( m.origin_x + m.margin_left, m.origin_y + m.margin_top, w(), h(), FL_BLACK );
/* draw bar/beat lines */
for ( int gx = m.vp->x;
gx < m.vp->x + m.vp->w + 200; /* hack */
gx++ )
{
if ( gx % m.grid->division() == 0 )
fl_color( fl_color_add_alpha( FL_GRAY, 50 ));
else if ( gx % m.grid->subdivision() == 0 )
fl_color( fl_color_add_alpha( FL_GRAY, 30 ));
else
continue;
fl_rectf( m.origin_x + m.margin_left + ( ( gx - m.vp->x ) * m.div_w ),
m.origin_y + m.margin_top,
m.div_w,
y() + h() - m.margin_top );
}
m.grid->draw_notes( draw_dash, this );
if ( ghost_note )
draw_dash( ghost_note->start,
ghost_note->note,
ghost_note->duration,
ghost_note->velocity,
1 );
fl_color( fl_color_add_alpha( fl_rgb_color( 127,127,127 ), 50 ));
/* draw grid */
if ( m.div_w > 4 )
{
for ( int gx = m.origin_x + m.margin_left;
gx < x() + w();
gx += m.div_w )
fl_line( gx, m.origin_y + m.margin_top, gx, y() + h() );
}
if ( m.div_h > 2 )
{
for ( int gy = m.origin_y + m.margin_top;
gy < y() + h();
gy += m.div_h )
fl_line( m.origin_x + m.margin_left, gy, x() + w(), gy );
}
fl_pop_clip();
fl_pop_clip();
2007-12-18 05:09:02 +01:00
}
2007-12-18 05:09:02 +01:00
/** draw ONLY those nodes necessary to bring the canvas up-to-date with the grid */
void
Canvas::draw ( void )
{
box( FL_NO_BOX );
labeltype( FL_NO_LABEL );
2007-12-18 05:09:02 +01:00
/* DMESSAGE( "%s%s%s%s%s%s", */
/* damage() & FL_DAMAGE_CHILD ? "CHILD " : "", */
/* damage() & FL_DAMAGE_ALL ? "ALL " : "", */
/* damage() & FL_DAMAGE_USER1 ? "USER 1 ": "", */
/* damage() & FL_DAMAGE_EXPOSE ? "EXPOSE " : "", */
/* damage() & FL_DAMAGE_SCROLL ? "SCROLL " : "", */
/* damage() & FL_DAMAGE_OVERLAY ? "OVERLAY " : ""); */
2007-12-18 05:09:02 +01:00
if ( damage() & FL_DAMAGE_SCROLL )
{
draw_ruler();
int dx = ( _old_scroll_x - m.vp->x ) * m.div_w;
int dy = ( _old_scroll_y - m.vp->y ) * m.div_h;
fl_scroll( m.origin_x + m.margin_left,
m.origin_y + m.margin_top,
w() - m.margin_left,
h() - m.margin_top - panzoomer->h(),
dx, dy,
draw_clip,
this );
if ( dy )
draw_mapping();
_old_scroll_x = m.vp->x;
_old_scroll_y = m.vp->y;
if ( damage() & FL_DAMAGE_CHILD )
clear_damage( FL_DAMAGE_CHILD );
}
else if ( damage() & ~FL_DAMAGE_CHILD )
{
draw_mapping();
draw_ruler();
draw_clip( x(), y(), w(), h() );
}
draw_children();
2007-12-18 05:09:02 +01:00
}
void
Canvas::cb_scroll ( Fl_Widget *w, void *v )
{
((Canvas*)v)->cb_scroll( w );
}
2007-12-18 05:09:02 +01:00
void
Canvas::cb_scroll ( Fl_Widget *w )
{
if ( w == panzoomer )
{
Fl_Panzoomer *o = (Fl_Panzoomer*)w;
2007-12-18 05:09:02 +01:00
_old_scroll_x = m.vp->x;
2007-12-18 05:09:02 +01:00
m.vp->x = grid()->ts_to_x( o->x_value() );
m.vp->y = o->y_value();
2007-12-18 05:09:02 +01:00
damage( FL_DAMAGE_SCROLL );
if ( o->zoom_changed() )
{
m.vp->w = m.grid->division() * o->zoom();
resize_grid();
redraw();
}
}
else if ( w == vzoom )
{
Fl_Slider *o = (Fl_Slider*)w;
float n = o->value();
2007-12-18 05:09:02 +01:00
m.vp->h = min( (int)n, m.maxh );
resize_grid();
song.set_dirty();
redraw();
}
}
2007-12-18 05:09:02 +01:00
/** convert pixel coords into grid coords. returns true if valid */
bool
Canvas::grid_pos ( int *x, int *y ) const
2007-12-18 05:09:02 +01:00
{
/* if ( ( *x < m.origin_x + m.margin_left ) || */
/* ( *y < m.origin_y + m.margin_top ) || */
/* ( *x > m.origin_x + w() ) || */
/* (*y > m.origin_y + h() - panzoomer->h() ) ) */
/* return false; */
2007-12-18 05:09:02 +01:00
*y = (*y - m.margin_top - m.origin_y) / m.div_h;
*x = (*x - m.margin_left - m.origin_x) / m.div_w;
/* if ( *x < 0 || *y < 0 || *x >= m.vp->w || *y >= m.vp->h ) */
/* return false; */
2007-12-18 05:09:02 +01:00
/* adjust for viewport */
*x += m.vp->x;
*y += m.vp->y;
/* adjust for row-compaction */
*y = rtn( *y );
return true;
}
/******************/
/* Input handlers */
/******************/
/* These methods translate viewport pixel coords to absolute grid
coords and pass on to the grid. */
/** if coords correspond to a row name entry, return the (absolute) note number, otherwise return -1 */
int
Canvas::is_row_press ( void ) const
{
if ( Fl::event_inside( this->x(),
this->y() + this->m.margin_top,
this->m.margin_left,
( this->h() - this->m.margin_top ) - this->panzoomer->h() ) )
{
int dx,dy;
dx = Fl::event_x();
dy = Fl::event_y();
grid_pos( &dx, &dy );
return m.grid->y_to_note(dy );
}
else
return -1;
}
void
Canvas::start_cursor ( int x, int y )
{
if ( ! grid_pos( &x, &y ) )
return;
m.ruler_drawn = false;
m.p1 = x;
m.p3 = ntr( y );
_lr();
redraw();
}
void
Canvas::end_cursor ( int x, int y )
{
if ( ! grid_pos( &x, &y ) )
return;
m.ruler_drawn = false;
m.p2 = x;
m.p4 = ntr( y );
_lr();
redraw();
}
2007-12-18 05:09:02 +01:00
void
Canvas::adj_color ( int x, int y, int n )
2007-12-18 05:09:02 +01:00
{
if ( ! grid_pos( &x, &y ) )
return;
m.grid->adj_velocity( x, y, n );
2007-12-18 05:09:02 +01:00
}
void
Canvas::adj_length ( int x, int y, int n )
2007-12-18 05:09:02 +01:00
{
if ( ! grid_pos( &x, &y ) )
return;
m.grid->adj_duration( x, y, n );
2007-12-18 05:09:02 +01:00
}
void
Canvas::set_end ( int x, int y, int n )
2007-12-18 05:09:02 +01:00
{
if ( ! grid_pos( &x, &y ) )
return;
m.grid->set_end( x, y, n );
2007-12-18 05:09:02 +01:00
}
void
Canvas::select ( int x, int y )
{
if ( ! grid_pos( &x, &y ) )
return;
m.grid->toggle_select( x, y );
2007-12-18 05:09:02 +01:00
}
void
Canvas::move_selected ( int dir, int n )
{
switch ( dir )
{
case RIGHT:
m.grid->move_selected( n );
break;
case LEFT:
m.grid->move_selected( 0 - n );
break;
case UP:
case DOWN:
{
/* row-compaction makes this a little complicated */
event_list *el = m.grid->events();
/* FIXME: don't allow movement beyond the edges! */
/* int hi, lo; */
/* m.grid->selected_hi_lo_note( &hi, &lo ); */
/* hi = ntr( hi ) > 0 ? ntr( hi ) : */
/* if ( m.grid->y_to_note( ntr( hi ) ) ) */
if ( dir == UP )
for ( int y = 0; y <= m.maxh; ++y )
el->rewrite_selected( m.grid->y_to_note( rtn( y ) ), m.grid->y_to_note( rtn( y - n ) ) );
else
for ( int y = m.maxh; y >= 0; --y )
el->rewrite_selected( m.grid->y_to_note( rtn( y ) ), m.grid->y_to_note( rtn( y + n ) ) );
m.grid->events( el );
delete el;
break;
}
}
}
void
Canvas::randomize_row ( int y )
{
int x = m.margin_left;
if ( ! grid_pos( &x, &y ) )
return;
((pattern*)m.grid)->randomize_row( y, song.random.feel, song.random.probability );
}
void
Canvas::_lr ( void )
{
int l, r;
if ( m.p2 > m.p1 )
{
l = m.p1;
r = m.p2;
}
else
{
l = m.p2;
r = m.p1;
}
m.p1 = l;
m.p2 = r;
}
void
Canvas::select_range ( void )
{
if ( m.p3 == m.p4 )
m.grid->select( m.p1, m.p2 );
else
m.grid->select( m.p1, m.p2, rtn( m.p3 ), rtn( m.p4 ) );
}
void
Canvas::invert_selection ( void )
{
m.grid->invert_selection();
2007-12-18 05:09:02 +01:00
}
void
Canvas::crop ( void )
{
if ( m.p3 == m.p4 )
m.grid->crop( m.p1, m.p2 );
else
m.grid->crop( m.p1, m.p2, rtn( m.p3 ), rtn( m.p4 ) );
2007-12-18 05:09:02 +01:00
m.vp->x = 0;
m.p2 = m.p2 - m.p1;
m.p1 = 0;
m.ruler_drawn = false;
}
void
Canvas::delete_time ( void )
{
m.grid->delete_time( m.p1, m.p2 );
}
void
Canvas::insert_time ( void )
{
m.grid->insert_time( m.p1, m.p2 );
}
/** paste range as new grid */
void
Canvas::duplicate_range ( void )
{
Grid *g = m.grid->clone();
g->crop( m.p1, m.p2 );
g->viewport.x = 0;
}
void
Canvas::row_compact ( int n )
{
switch ( n )
{
case OFF:
m.row_compact = false;
m.maxh = 128;
break;
case ON:
m.row_compact = true;
m.vp->y = 0;
_update_row_mapping();
break;
case TOGGLE:
row_compact( m.row_compact ? OFF : ON );
break;
}
// _reset();
2007-12-18 05:09:02 +01:00
}
void
Canvas::pan ( int dir, int n )
{
switch ( dir )
{
case LEFT: case RIGHT: case TO_PLAYHEAD: case TO_NEXT_NOTE: case TO_PREV_NOTE:
/* handle horizontal movement specially */
n *= m.grid->division();
m.ruler_drawn = false;
break;
default:
n *= 5;
break;
}
switch ( dir )
{
case LEFT:
m.vp->x = max( m.vp->x - n, 0 );
break;
case RIGHT:
m.vp->x += n;
break;
case TO_PLAYHEAD:
m.vp->x = m.playhead - (m.playhead % m.grid->division());
break;
case UP:
m.vp->y = max( m.vp->y - n, 0 );
break;
case DOWN:
m.vp->y = min( m.vp->y + n, m.maxh - m.vp->h );
break;
case TO_NEXT_NOTE:
{
int x = m.grid->next_note_x( m.vp->x );
m.vp->x = x - (x % m.grid->division() );
break;
}
case TO_PREV_NOTE:
{
int x = m.grid->prev_note_x( m.vp->x );
m.vp->x = x - (x % m.grid->division() );
break;
}
}
damage(FL_DAMAGE_USER1);
2007-12-18 05:09:02 +01:00
}
void
Canvas::can_scroll ( int *left, int *right, int *up, int *down )
{
*left = m.vp->x;
*right = -1;
*up = m.vp->y;
*down = m.maxh - ( m.vp->y + m.vp->h );
}
2007-12-18 05:09:02 +01:00
/** adjust horizontal zoom (* n) */
void
Canvas::h_zoom ( float n )
{
m.vp->w = max( 32, min( (int)(m.vp->w * n), 256 ) );
resize_grid();
song.set_dirty();
2007-12-18 05:09:02 +01:00
}
void
Canvas::v_zoom_fit ( void )
{
if ( ! m.grid )
return;
changed_mapping();
m.vp->h = m.maxh;
m.vp->y = 0;
resize_grid();
song.set_dirty();
}
2007-12-18 05:09:02 +01:00
/** adjust vertical zoom (* n) */
void
Canvas::v_zoom ( float n )
{
m.vp->h = max( 1, min( (int)(m.vp->h * n), m.maxh ) );
resize_grid();
song.set_dirty();
2007-12-18 05:09:02 +01:00
}
void
Canvas::notes ( char *s )
{
m.grid->notes( s );
}
char *
Canvas::notes ( void )
{
return m.grid->notes();
}
int
Canvas::handle ( int m )
{
Canvas *c = this;
int ow, oh;
int x, y;
int processed = 1;
x = Fl::event_x();
y = Fl::event_y();
static int drag_x;
static int drag_y;
static bool delete_note;
static note_properties *drag_note;
static int adjusting_velocity;
// static note_properties;
ow = c->grid()->viewport.w;
oh = c->grid()->viewport.h;
switch ( m )
{
case FL_FOCUS:
case FL_UNFOCUS:
return 1;
case FL_ENTER:
case FL_LEAVE:
fl_cursor( FL_CURSOR_DEFAULT );
return 1;
case FL_MOVE:
{
if ( Fl::event_inside( this->x() + this->m.margin_left,
this->y() + this->m.margin_top,
this->w() - this->m.margin_left,
( this->h() - this->m.margin_top ) - this->panzoomer->h() ) )
fl_cursor( FL_CURSOR_HAND );
else
fl_cursor( FL_CURSOR_DEFAULT );
return 1;
break;
}
case FL_KEYBOARD:
{
/* if ( Fl::event_state() & FL_ALT || Fl::event_state() & FL_CTRL ) */
/* // this is more than a simple keypress. */
/* return 0; */
if ( Fl::event_state() & FL_CTRL )
{
switch ( Fl::event_key() )
{
case FL_Delete:
c->delete_time();
break;
case FL_Insert:
c->insert_time();
break;
case FL_Right:
c->pan( TO_NEXT_NOTE, 0 );
break;
case FL_Left:
c->pan( TO_PREV_NOTE, 0 );
break;
default:
return 0;
}
}
else
if ( Fl::event_state() & FL_ALT )
return 0;
switch ( Fl::event_key() )
{
case FL_Left:
c->pan( LEFT, 1 );
break;
case FL_Right:
c->pan( RIGHT, 1 );
break;
case FL_Up:
c->pan( UP, 1 );
break;
case FL_Down:
c->pan( DOWN, 1 );
break;
default:
/* have to do this to get shifted keys */
switch ( *Fl::event_text() )
{
case 'f':
c->pan( TO_PLAYHEAD, 0 );
break;
case 'r':
c->select_range();
break;
case 'q':
c->grid()->select_none();
break;
case 'i':
c->invert_selection();
break;
/* case '1': */
/* c->h_zoom( 2.0f ); */
/* break; */
/* case '2': */
/* c->h_zoom( 0.5f ); */
/* break; */
/* case '3': */
/* c->v_zoom( 2.0f ); */
/* break; */
/* case '4': */
/* c->v_zoom( 0.5f ); */
/* break; */
/* case ' ': */
/* transport.toggle(); */
/* break; */
#define IS_PATTERN (parent() == ui->pattern_tab)
#define IS_PHRASE (parent() == ui->phrase_tab)
#define IS_SEQUENCE (parent() == ui->sequence_tab)
case '<':
c->move_selected( LEFT, 1 );
break;
case '>':
c->move_selected( RIGHT, 1 );
break;
case ',':
c->move_selected( UP, 1 );
break;
case '.':
c->move_selected( DOWN, 1 );
break;
case 'C':
c->crop();
break;
case 'd':
{
MESSAGE( "duplicating thing" );
c->grid( c->grid()->clone() );
// number of phrases may have changed.
ui->update_sequence_widgets();
break;
}
case 'D':
c->duplicate_range();
break;
case 't':
c->grid()->trim();
break;
default:
processed = 0;
break;
}
break;
}
break;
}
case FL_PUSH:
{
switch ( Fl::event_button() )
{
case 1:
{
delete_note = true;
adjusting_velocity = 0;
if ( Fl::event_ctrl() )
{
c->select( x, y );
processed = 2;
break;
}
int dx = x;
int dy = y;
grid_pos( &dx, &dy );
int note;
if ( ( note = c->is_row_press() ) >= 0 )
{
if ( IS_PATTERN )
((pattern *)c->grid())->row_name_press( note );
processed = 2;
break;
}
if ( Fl::event_inside( this->x() + this->m.margin_left,
this->y() + this->m.margin_top,
this->w() - this->m.margin_left,
( this->h() - this->m.margin_top ) - this->panzoomer->h() ) )
{
if ( ! this->m.grid->is_set( dx,dy ))
{
ghost_note = new note_properties;
ghost_note->start = this->m.grid->x_to_ts( dx );
ghost_note->note = dy;
ghost_note->duration = this->m.grid->default_length();
ghost_note->velocity = 64;
delete_note = false;
processed = 1;
break;
}
else
{
ghost_note = new note_properties;
drag_note = new note_properties;
this->m.grid->get_note_properties( dx, dy, ghost_note );
this->m.grid->get_note_properties( dx, dy, drag_note );
this->m.grid->del( dx, dy );
delete_note = true;
}
this->m.grid->get_start( &dx, &dy );
drag_x = x;
drag_y = y;
take_focus();
}
else
processed = 0;
break;
}
case 3:
{
int note;
if ( ( note = is_row_press() ) >= 0 )
{
/* inside the note headings */
DMESSAGE( "click on row %d", note );
if ( IS_PATTERN )
{
Instrument *i = ((pattern *)c->grid())->mapping.instrument();
if ( i )
{
ui->edit_instrument_row( i, note );
c->changed_mapping();
}
}
}
else
if ( Fl::event_state() & FL_SHIFT )
{
c->end_cursor( x, y );
break;
}
break;
}
default:
processed = 0;
}
break;
}
case FL_RELEASE:
switch ( Fl::event_button() )
{
case 1:
{
int dx = x;
int dy = y;
grid_pos( &dx, &dy );
if ( IS_PATTERN && Fl::event_state() & ( FL_ALT | FL_CTRL ) )
c->randomize_row( y );
else
{
if ( delete_note )
{
// this->m.grid->del( dx, dy );
if ( ghost_note )
{
damage_grid( ghost_note->start, ghost_note->note, ghost_note->duration, 1 );
delete ghost_note;
}
ghost_note = 0;
}
else
if ( ghost_note )
{
this->m.grid->put( this->m.grid->ts_to_x( ghost_note->start ),
ghost_note->note,
ghost_note->duration,
ghost_note->velocity);
delete_note = false;
delete ghost_note;
ghost_note = 0;
}
}
if ( drag_note )
delete drag_note;
drag_note = 0;
break;
}
default:
processed = 0;
break;
}
break;
case FL_DRAG:
if ( Fl::event_button1() )
{
int dx = x;
int dy = y;
grid_pos( &dx, &dy );
if ( ghost_note )
{
damage_grid( ghost_note->start, ghost_note->note, ghost_note->duration, 1 );
if ( drag_note )
{
int ody = drag_y;
int odx = drag_x;
grid_pos( &odx, &ody );
if ( ody != dy )
{
adjusting_velocity = 1;
ghost_note->velocity =
drag_note->velocity +
( (drag_y - y) / 3.0f );
if ( ghost_note->velocity < 0 )
ghost_note->velocity = 0;
else if ( ghost_note->velocity > 127 )
ghost_note->velocity = 127;
}
}
if ( ! adjusting_velocity )
{
if ( dx > this->m.grid->ts_to_x( ghost_note->start ) )
{
ghost_note->duration = this->m.grid->x_to_ts( dx ) - ghost_note->start;
}
damage_grid( ghost_note->start, ghost_note->note, ghost_note->duration, 1 );
}
else
ghost_note->duration = drag_note->duration;
delete_note = false;
processed = 2;
}
}
break;
default:
processed = 0;
}
int nw, nh;
nw = c->grid()->viewport.w;
nh = c->grid()->viewport.h;
if ( processed )
window()->damage(FL_DAMAGE_OVERLAY);
if ( processed == 1 )
damage(FL_DAMAGE_USER1);
if ( ! processed )
return Fl_Group::handle( m );
return processed;
}