/*******************************************************************************/ /* 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 "canvas.H" #include "pattern.H" #include "gui/draw.H" #include "common.h" #include "non.H" #include extern Fl_Color velocity_colors[]; const int ruler_height = 14; Canvas::Canvas ( int X, int Y, int W, int H, const char *L ) : Fl_Widget( X,Y,W,H,L ) { m.origin_x = m.origin_y = m.height = m.width = m.div_w = m.div_h = m.playhead = m.margin_top = m.margin_left = m.playhead = m.w = m.h = m.p1 = m.p2 = m.p3 = m.p4 = 0; m.margin_top = ruler_height; m.draw = false; m.ruler_drawn = false; m.mapping_drawn = false; m.grid_drawn = false; // m.current = m.previous = NULL; m.row_compact = true; m.maxh = 128; m.vp = NULL; } void Canvas::handle_event_change ( void ) { /* mark the song as dirty and pass the signal on */ song.set_dirty(); signal_draw(); } /** change grid to /g/, returns TRUE if new grid size differs from old */ void Canvas::grid ( Grid *g ) { m.grid = g; if ( ! g ) return; m.vp = &g->viewport; char *s = m.vp->dump(); DMESSAGE( "viewport: %s", s ); free( s ); m.ruler_drawn = false; resize_grid(); update_mapping(); // m.shape = m.grid->draw_shape(); /* 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() ); signal_draw(); signal_settings_change(); signal_pan(); } /** 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" ); /* 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 ); } /** update everything about mapping, leaving the viewport alone */ void Canvas::update_mapping ( void ) { _update_row_mapping(); m.mapping_drawn = false; adj_size(); int old_margin = m.margin_left; m.margin_left = 0; m.draw = false; m.grid->draw_row_names( this ); m.draw = true; if ( m.margin_left != old_margin ) { signal_resize(); signal_draw(); } else signal_draw(); } /** 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); signal_pan(); } Grid * Canvas::grid ( void ) { return m.grid; } /** recalculate node sizes based on physical dimensions */ void Canvas::adj_size ( void ) { 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; m.mapping_drawn = m.ruler_drawn = false; } /** reallocate buffers to match grid dimensions */ void Canvas::resize_grid ( void ) { // _update_row_mapping(); adj_size(); if ( m.vp ) { if ( m.vp->w != m.w || m.vp->h != m.h || m.div_w != m.old_div_w || m.div_h != m.old_div_h ) { if ( m.grid_drawn ) signal_resize(); m.old_div_w = m.div_w; m.old_div_h = m.div_h; } else return; } DMESSAGE( "resizing grid %dx%d", m.vp->w, m.vp->h ); m.grid_drawn = false; } /** 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; Fl_Widget::resize(x,y,w,h); adj_size(); } /***********/ /* Drawing */ /***********/ /** prepare current buffer for drawing (draw "background") */ void Canvas::clear ( void ) { /* uint rule = m.grid->ppqn(); */ /* uint lx = m.grid->ts_to_x( m.grid->length() ); */ /* for ( uint y = m.vp->h; y--; ) */ /* for ( uint x = m.vp->w; x--; ) */ /* { */ /* m.current[ x ][ y ].color = 0; */ /* m.current[ x ][ y ].state = EMPTY; */ /* m.current[ x ][ y ].flags = 0; */ /* } */ /* for ( int x = m.vp->w - rule; x >= 0; x -= rule ) */ /* for ( uint y = m.vp->h; y-- ; ) */ /* m.current[ x ][ y ].state = LINE; */ /* int sx = (int)(lx - m.vp->x) >= 0 ? lx - m.vp->x : 0; */ /* for ( int x = sx; x < m.vp->w; ++x ) */ /* for ( int y = m.vp->h; y-- ; ) */ /* m.current[ x ][ y ].state = PARTIAL; */ } /** is /x/ within the viewport? */ bool Canvas::viewable_x ( int x ) { return x >= m.vp->x && x < m.vp->x + m.vp->w; } /** flush delta of last and current buffers to screen, then flip them */ void Canvas::flip ( void ) { /* /\* FIXME: should this not go in clear()? *\/ */ /* if ( m.p1 != m.p2 ) */ /* { */ /* if ( viewable_x( m.p1 ) ) draw_line( m.p1 - m.vp->x, F_P1 ); */ /* if ( viewable_x( m.p2 ) ) draw_line( m.p2 - m.vp->x, F_P2 ); */ /* } */ /* if ( viewable_x( m.playhead ) ) draw_line( m.playhead - m.vp->x, F_PLAYHEAD ); */ /* const int shape = m.grid->draw_shape(); */ /* for ( uint y = m.vp->h; y--; ) */ /* for ( uint x = m.vp->w; x--; ) */ /* { */ /* cell_t *c = &m.current[ x ][ y ]; */ /* cell_t *p = &m.previous[ x ][ y ]; */ /* /\* draw selection rect *\/ */ /* if ( m.p3 != m.p4 ) */ /* if ( y + m.vp->y >= m.p3 && x + m.vp->x >= m.p1 && */ /* y + m.vp->y <= m.p4 && x + m.vp->x < m.p2 ) */ /* c->flags |= F_SELECTION; */ /* if ( *c != *p ) */ /* gui_draw_shape( m.origin_x + m.margin_left + x * m.div_w, m.origin_y + m.margin_top + y * m.div_h, m.div_w, m.div_h, */ /* shape, c->state, c->flags, c->color ); */ /* } */ /* cell_t **tmp = m.previous; */ /* m.previous = m.current; */ /* m.current = tmp; */ } 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 ); // fl_rectf( x, y, x + (div_w * w), y + h ); fl_rectf( x, y, (div_w * w), h ); fl_color( FL_RED ); 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++ ) if ( 0 == i % z ) { int nx = x + (i * div_w) + (div_w / 2); fl_color( FL_RED ); fl_line( nx, y, nx, y + h ); int k = ofs + i; sprintf( pat, "%i", 1 + (k / z )); fl_color( FL_WHITE ); 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 ); } } 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; } /** redraw the ruler at the top of the canvas */ void Canvas::redraw_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 ); m.ruler_drawn = true; } /** 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; int bw = m.margin_left; int bh = m.div_h; if ( y < 0 || y >= m.vp->h ) draw = false; if ( clear && draw ) fl_rectf( bx, by, bw, bh, FL_BACKGROUND_COLOR ); else m.margin_left = max( m.margin_left, gui_draw_string( bx, by, bw, bh, color, name, draw ) ); } /** redraw row names */ void Canvas::redraw_mapping ( void ) { m.margin_left = 0; m.draw = false; m.grid->draw_row_names( this ); adj_size(); m.draw = true; m.grid->draw_row_names( this ); m.mapping_drawn = true; } void Canvas::draw_mapping ( void ) { if ( ! m.mapping_drawn ) redraw_mapping(); } void Canvas::draw_ruler ( void ) { if ( ! m.ruler_drawn ) redraw_ruler(); } /** "draw" a shape in the backbuffer */ void Canvas::draw_shape ( int x, int y, int w, int color ) { y = ntr( y ); if ( y < 0 ) return; // adjust for viewport. x -= m.vp->x; y -= m.vp->y; if ( x < 0 || y < 0 || x >= m.vp->w || y >= m.vp->h ) return; fl_rectf( m.origin_x + m.margin_left + x * m.div_w, m.origin_y + m.margin_top + y * m.div_h + 1, m.div_w * w, m.div_h - 1, color ); } /** callback used by Grid::draw() */ void Canvas::draw_dash ( int x, int y, int l, int color, void *userdata ) { Canvas *o = (Canvas*)userdata; color = velocity_colors[ color ]; o->draw_shape( x, y, 1, fl_color_average( FL_WHITE, color, 0.5 ) ); o->draw_shape( x + 1, y, l - 1, color ); } /** draw a vertical line with flags */ void Canvas::draw_line ( int x, int flags ) { /* for ( uint y = m.vp->h; y-- ; ) */ /* m.current[ x ][ y ].flags |= flags; */ } int Canvas::playhead_moved ( void ) { int x = m.grid->ts_to_x( m.grid->index() ); return m.playhead != x; } /** draw only the playhead--without reexamining the grid */ int Canvas::draw_playhead ( void ) { /* int x = m.grid->ts_to_x( m.grid->index() ); */ /* if ( m.playhead == x ) */ /* return 0; */ /* m.playhead = x; */ /* if ( m.playhead < m.vp->x || m.playhead >= m.vp->x + m.vp->w ) */ /* { */ /* if ( config.follow_playhead ) */ /* { */ /* m.vp->x = m.playhead / m.vp->w * m.vp->w; */ /* m.ruler_drawn = false; */ /* signal_draw(); */ /* return 0; */ /* } */ /* } */ /* copy(); */ /* for ( uint x = m.vp->w; x-- ; ) */ /* for ( uint y = m.vp->h; y-- ; ) */ /* m.current[ x ][ y ].flags &= ~ (F_PLAYHEAD | F_P1 | F_P2 ); */ /* flip(); */ /* /\* actually if we're recording, we should draw the grid once per */ /* * playhead movement also *\/ */ /* if ( pattern::recording() == m.grid ) */ /* { */ /* draw(); */ /* } */ /* return 1; */ } /** draw ONLY those nodes necessary to bring the canvas up-to-date with the grid */ void Canvas::draw ( void ) { DMESSAGE( "drawing canvas" ); draw_mapping(); draw_ruler(); m.grid_drawn = true; fl_rectf( m.origin_x + m.margin_left, m.origin_y + m.margin_top, w(), h(), velocity_colors[10] ); /* draw grid */ fl_color( FL_BLACK ); for ( int gx = m.origin_x + m.margin_left; gx < m.origin_x + m.margin_left + ( m.div_w * m.vp->w ); gx += m.div_w ) fl_line( gx, m.origin_y + m.margin_top, gx, m.origin_y + m.margin_top + ( m.div_w * m.vp->w ) ); for ( int gy = m.origin_y + m.margin_top; gy < m.origin_y + m.margin_top + ( m.div_h * m.vp->h ); gy += m.div_h ) fl_line( m.origin_x + m.margin_left, gy, m.origin_x + m.margin_left + ( m.div_w * m.vp->w ), gy ); m.grid->draw_notes( draw_dash, this ); } /* /\** redraw every node on the canvas from the buffer (without */ /* * necessarily reexamining the grid) *\/ */ /* void */ /* Canvas::redraw ( void ) */ /* { */ /* DMESSAGE( "redrawing canvas" ); */ /* if ( ! m.grid_drawn ) */ /* draw(); */ /* m.ruler_drawn = false; */ /* m.mapping_drawn = false; */ /* draw_mapping(); */ /* draw_ruler(); */ /* const int shape = m.grid->draw_shape(); */ /* for ( int y = m.vp->h; y--; ) */ /* for ( int x = m.vp->w; x--; ) */ /* { */ /* cell_t c = m.previous[ x ][ y ]; */ /* if ( m.vp->x + x == m.playhead ) */ /* c.flags |= F_PLAYHEAD; */ /* gui_draw_shape( m.origin_x + m.margin_left + x * m.div_w, m.origin_y + m.margin_top + y * m.div_h, m.div_w, m.div_h, */ /* shape, c.state, c.flags, c.color ); */ /* } */ /* } */ /** convert pixel coords into grid coords. returns true if valid */ bool Canvas::grid_pos ( int *x, int *y ) const { *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; /* 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_name ( int x, int y ) { if ( x - m.origin_x >= m.margin_left ) return -1; x = m.margin_left; grid_pos( &x, &y ); return m.grid->y_to_note( y ); } 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(); signal_draw(); } 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(); signal_draw(); } void Canvas::set ( int x, int y ) { if ( y - m.origin_y < m.margin_top ) /* looks like a click on the ruler */ { if ( x - m.margin_left - m.origin_x >= 0 ) { m.p1 = m.vp->x + ((x - m.margin_left - m.origin_x) / m.div_w); m.ruler_drawn = false; m.p3 = m.p4 = 0; } _lr(); signal_draw(); return; } if ( ! grid_pos( &x, &y ) ) return; m.grid->put( x, y, 0 ); } void Canvas::unset ( int x, int y ) { if ( y - m.origin_y < m.margin_top ) /* looks like a click on the ruler */ { if ( x - m.margin_left - m.origin_x >= 0 ) { m.p2 = m.vp->x + ((x - m.margin_left - m.origin_x) / m.div_w); m.ruler_drawn = false; m.p3 = m.p4 = 0; } _lr(); signal_draw(); return; } if ( ! grid_pos( &x, &y ) ) return; m.grid->del( x, y ); } void Canvas::adj_color ( int x, int y, int n ) { if ( ! grid_pos( &x, &y ) ) return; m.grid->adj_velocity( x, y, n ); } void Canvas::adj_length ( int x, int y, int n ) { if ( ! grid_pos( &x, &y ) ) return; m.grid->adj_duration( x, y, n ); } void Canvas::select ( int x, int y ) { if ( ! grid_pos( &x, &y ) ) return; m.grid->toggle_select( x, y ); } 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(); } 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 ) ); 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(); m.mapping_drawn = false; } 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; m.mapping_drawn = false; 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; } } signal_draw(); signal_pan(); } 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 ); } /** 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(); } 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(); } /** 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(); } void Canvas::notes ( char *s ) { m.grid->notes( s ); } char * Canvas::notes ( void ) { return m.grid->notes(); }