diff --git a/TODO b/TODO index 061dee5..9c74024 100644 --- a/TODO +++ b/TODO @@ -5,10 +5,6 @@ ; General -* allow deselection of notes in pattern editor. -* some way to select by row as well as column. preferably better than - standard rubberband techniques. What I'd really like is a cross - hair to select x1,y1 and x2,y2 coords of a bounding box. * add option to create new instrument defintion. * per phrase tempo setting? Perhaps a percentage of global tempo? * add channel field to event list widget (but channel bits in pattern diff --git a/canvas.C b/canvas.C index 27175f2..f0fb2eb 100644 --- a/canvas.C +++ b/canvas.C @@ -63,7 +63,7 @@ Canvas::_alloc_array ( void ) Canvas::Canvas ( ) { - 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 = 0; + 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; @@ -335,6 +335,12 @@ Canvas::flip ( void ) 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, m.border_w, c->shape, c->state, c->flags, c->color ); @@ -438,7 +444,9 @@ Canvas::draw_shape ( int x, int y, int shape, int state, int color, bool selecte m.current[ x ][ y ].shape = shape; m.current[ x ][ y ].color = color; m.current[ x ][ y ].state = (uint)m.vp->x + x > m.grid->ts_to_x( m.grid->length() ) ? PARTIAL : state; - m.current[ x ][ y ].flags = selected ? F_SELECTED : 0; + if ( selected ) + m.current[ x ][ y ].state = SELECTED; + m.current[ x ][ y ].flags = 0; } /** callback used by Grid::draw() */ @@ -585,6 +593,38 @@ Canvas::is_row_name ( int x, int 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 ) { @@ -595,6 +635,8 @@ Canvas::set ( int x, int y ) { 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(); @@ -620,6 +662,8 @@ Canvas::unset ( int x, int y ) { 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(); @@ -659,7 +703,7 @@ Canvas::select ( int x, int y ) if ( ! grid_pos( &x, &y ) ) return; - m.grid->select( x, y, true ); + m.grid->toggle_select( x, y ); } void @@ -739,7 +783,16 @@ Canvas::_lr ( void ) void Canvas::select_range ( void ) { - m.grid->select( m.p1, m.p2 ); + 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 diff --git a/canvas.H b/canvas.H index f93b68a..1f6d636 100644 --- a/canvas.H +++ b/canvas.H @@ -66,10 +66,6 @@ class Canvas : public trackable int playhead; /* where the playhead is for this canvas. only used for display. */ -/* /\* these are in logical units, not pixels *\/ */ -/* int w, h; /\* viewport *\/ */ -/* int x, y; /\* pan position *\/ */ - enum { PATTERN, SEQUENCE } mode; Grid *grid; /* grid currently connected to this canvas */ @@ -81,7 +77,7 @@ class Canvas : public trackable int rule; - bool row_compact; /* use row-compaction? */ + bool row_compact; /* use row-compaction? */ /* tables used for row-compaction */ int rtn[128]; /* row-to-note */ @@ -92,7 +88,8 @@ class Canvas : public trackable Viewport *vp; int w, h; - uint p1, p2; /* cursors */ + uint p1, p2; /* range cursors */ + uint p3, p4; /* row cursors */ } m; int rtn ( int r ) const; @@ -146,6 +143,7 @@ public: void adj_length ( int x, int y, int n ); void select ( int x, int y ); void select_range ( void ); + void invert_selection ( void ); void duplicate_range ( void ); void crop ( void ); void row_compact ( int n ); @@ -157,6 +155,10 @@ public: char * notes ( void ); void randomize_row ( int y ); + + void start_cursor ( int x, int y ); + void end_cursor ( int x, int y ); + void delete_time ( void ); void insert_time ( void ); diff --git a/event_list.C b/event_list.C index 90f01f5..4c4e05f 100644 --- a/event_list.C +++ b/event_list.C @@ -291,6 +291,24 @@ event_list::select ( tick_t start, tick_t end ) } } +/** select note evenets from /start/ to /end/ within range /hi/ through /lo/ */ +void +event_list::select ( tick_t start, tick_t end, int hi, int lo ) +{ + FOR_ALL( e ) + { + tick_t ts = e->timestamp(); + + /* don't count note offs exactly on start */ + if ( ! e->is_note_on() ) + continue; + + if ( ts >= start && ts < end && + e->note() <= hi && e->note() >= lo ) + e->select(); + } +} + /** select ALL events */ void event_list::select_all ( void ) @@ -306,6 +324,19 @@ event_list::select_none ( void ) e->deselect(); } +void +event_list::invert_selection ( void ) +{ + FOR_ALL( e ) + if ( ! e->is_note_off() ) + { + if ( e->selected() ) + e->deselect(); + else + e->select(); + } +} + /** remove all selected events */ void event_list::remove_selected ( void ) diff --git a/event_list.H b/event_list.H index 80dfd04..a4a9305 100644 --- a/event_list.H +++ b/event_list.H @@ -51,8 +51,11 @@ public: event * first ( void ) const; event * last ( void ) const; void select ( tick_t start, tick_t end ); + void select ( tick_t start, tick_t end, int hi, int lo ); + void select_all ( void ); void select_none ( void ); + void invert_selection ( void ); void remove_selected ( void ); void transpose_selected ( int n ); diff --git a/grid.C b/grid.C index 142c01b..7e8c17c 100644 --- a/grid.C +++ b/grid.C @@ -457,17 +457,17 @@ Grid::adj_duration ( int x, int y, int l ) } void -Grid::select ( int x, int y, bool b ) +Grid::toggle_select ( int x, int y ) { lock(); event *e = _event( x, y, true ); if ( e ) - if ( b ) - e->select(); - else + if ( e->selected() ) e->deselect(); + else + e->select(); unlock(); } @@ -503,6 +503,20 @@ Grid::select ( int l, int r ) unlock(); } +/** select all (note) events in rectangle */ +void +Grid::select ( int l, int r, int t, int b ) +{ + tick_t start = x_to_ts( l ); + tick_t end = x_to_ts( r ); + + lock(); + + _rw->events.select( start, end, y_to_note( t) , y_to_note( b ) ); + + unlock(); +} + /** delete events from /x/ to /l/, compressing time. */ void Grid::delete_time ( int l, int r ) @@ -527,6 +541,16 @@ Grid::select_none ( void ) unlock(); } +void +Grid::invert_selection ( void ) +{ + lock(); + + _rw->events.invert_selection(); + + unlock(); +} + void Grid::delete_selected ( void ) { diff --git a/grid.H b/grid.H index 58e00cf..061e210 100644 --- a/grid.H +++ b/grid.H @@ -214,11 +214,13 @@ public: void crop ( int l, int r ); - void select ( int x, int y, bool b ); + void toggle_select ( int x, int y ); void insert_time ( int x, int r ); void select ( int start, int end ); + void select ( int start, int end, int t, int b ); void delete_time ( int start, int end ); void select_none ( void ); + void invert_selection ( void ); void resolution ( unsigned int n ); int resolution ( void ) const; diff --git a/gui/draw.C b/gui/draw.C index 7e99453..1143c23 100644 --- a/gui/draw.C +++ b/gui/draw.C @@ -174,15 +174,15 @@ gui_draw_shape ( int x, int y, int w, int h, int bw, int shape, int state, int f if ( flags & F_PLAYHEAD ) state = state == FULL ? HIT : PLAYHEAD; - else - if ( flags & F_SELECTED ) - state = SELECTED; if ( state == FULL && color ) fl_color( velocity_colors[ color ] ); else fl_color( state_colors[ state ] ); + if ( flags & F_SELECTION ) + fl_color( fl_darker( fl_color() ) ); + switch ( shape ) { case CIRCLE: diff --git a/gui/draw.H b/gui/draw.H index 77a9295..e5f496e 100644 --- a/gui/draw.H +++ b/gui/draw.H @@ -20,11 +20,11 @@ enum { FULL, /* dot or dash head */ PARTIAL, CONTINUED, /* dash tail */ + SELECTED, /* virtual */ HIT, /* playhead hit */ LINE, /* beat line */ PLAYHEAD, - SELECTED, MAX_STATE, }; @@ -35,10 +35,10 @@ enum { /* flags */ enum { - F_PLAYHEAD = 1 << 0, /* playhead is on item */ - F_P1 = 1 << 1, - F_P2 = 1 << 2, - F_SELECTED = 1 << 3 /* item is selected */ + F_PLAYHEAD = 1 << 0, /* playhead is on item */ + F_P1 = 1 << 1, + F_P2 = 1 << 2, + F_SELECTION = 1 << 3 /* item is part of the selection box */ }; diff --git a/gui/input.C b/gui/input.C index beecbc9..1ad1054 100644 --- a/gui/input.C +++ b/gui/input.C @@ -128,6 +128,9 @@ canvas_input_callback ( O_Canvas *widget, Canvas *c, int m ) case 'q': c->grid()->select_none(); break; + case 'i': + c->invert_selection(); + break; case '1': c->h_zoom( 2.0f ); break; @@ -250,6 +253,11 @@ canvas_input_callback ( O_Canvas *widget, Canvas *c, int m ) } else { + if ( Fl::event_state() & FL_SHIFT ) + { + c->start_cursor( x, y ); + break; + } if ( IS_PATTERN && Fl::event_state() & FL_CTRL ) c->randomize_row( y ); @@ -258,6 +266,12 @@ canvas_input_callback ( O_Canvas *widget, Canvas *c, int m ) } break; case 3: + if ( Fl::event_state() & FL_SHIFT ) + { + c->end_cursor( x, y ); + break; + } + c->unset( x, y ); break; case 2: