1
Fork 0
jacker/songview.cpp

1249 lines
34 KiB
C++

#include "songview.hpp"
#include "model.hpp"
#include <cassert>
#include <cmath>
#include <algorithm>
#include <string.h>
namespace Jacker {
enum {
ColorBlack = 0,
ColorWhite,
ColorBackground,
ColorTrack,
ColorGhost,
ColorMuted,
ColorCount,
};
enum {
// how far away from the border can a resize be done
ResizeThreshold = 8,
};
//=============================================================================
SongCursor::SongCursor() {
view = NULL;
track = 0;
frame = 0;
}
SongCursor::SongCursor(SongView &view) {
this->view = &view;
track = 0;
frame = 0;
}
void SongCursor::set_view(SongView &view) {
this->view = &view;
}
SongView *SongCursor::get_view() const {
return view;
}
void SongCursor::set_track(int track) {
this->track = track;
}
int SongCursor::get_track() const {
return track;
}
void SongCursor::set_frame(int frame) {
this->frame = frame;
}
int SongCursor::get_frame() const {
return frame;
}
void SongCursor::get_pos(int &x, int &y) const {
assert(view);
view->get_event_pos(frame, track, x, y);
}
void SongCursor::set_pos(int x, int y) {
assert(view);
view->get_event_location(x, y, frame, track);
}
//=============================================================================
SongView::SongView(BaseObjectType* cobject,
const Glib::RefPtr<Gtk::Builder>& builder)
: Gtk::Widget(cobject) {
origin_x = 0;
origin_y = 0;
model = NULL;
zoomlevel = 0;
hadjustment = NULL;
vadjustment = NULL;
snap_mode = SnapBar;
interact_mode = InteractNone;
colors.resize(ColorCount);
colors[ColorBlack].set("#000000");
colors[ColorWhite].set("#FFFFFF");
colors[ColorBackground].set("#e0e0e0");
colors[ColorTrack].set("#ffffff");
colors[ColorGhost].set("#606060");
colors[ColorMuted].set("#A0A0A0");
play_position = 0;
cursor_x = 0;
cursor_y = 0;
}
void SongView::set_model(class Model &model) {
this->model = &model;
}
void SongView::on_realize() {
Gtk::Widget::on_realize();
window = get_window();
// create drawing resources
gc = Gdk::GC::create(window);
cm = gc->get_colormap();
for (std::vector<Gdk::Color>::iterator i = colors.begin();
i != colors.end(); ++i) {
cm->alloc_color(*i);
}
Pango::FontDescription font_desc("sans 7");
pango_layout = Pango::Layout::create(get_pango_context());
pango_layout->set_font_description(font_desc);
//pango_layout->set_width(-1);
pango_layout->set_ellipsize(Pango::ELLIPSIZE_MIDDLE);
// create xor gc for drawing the cursor
xor_gc = Gdk::GC::create(window);
Glib::RefPtr<Gdk::Colormap> xor_cm = xor_gc->get_colormap();
Gdk::Color xor_color;
xor_color.set("#ffffff"); xor_cm->alloc_color(xor_color);
xor_gc->set_function(Gdk::XOR);
xor_gc->set_foreground(xor_color);
xor_gc->set_background(xor_color);
update_adjustments();
}
void SongView::set_origin(int x, int y) {
origin_x = x;
origin_y = y;
}
void SongView::get_origin(int &x, int &y) const {
x = origin_x;
y = origin_y;
}
void SongView::get_event_pos(int frame, int track,
int &x, int &y) const {
x = origin_x + (frame>>zoomlevel);
y = origin_y + track*TrackHeight;
}
void SongView::get_event_size(int length, int &w, int &h) const {
w = length>>zoomlevel;
h = TrackHeight;
}
void SongView::get_event_length(int w, int h, int &length, int &track) const {
length = w<<zoomlevel;
track = h/TrackHeight;
}
void SongView::get_event_location(int x, int y, int &frame, int &track) const {
frame = (x - origin_x)<<zoomlevel;
track = (y - origin_y)/TrackHeight;
}
bool SongView::find_event(const SongCursor &cur, Song::iterator &event) {
if (cur.get_track() >= model->get_track_count())
return false;
Song::IterList events;
model->song.find_events(cur.get_frame(), events);
if (events.empty())
return false;
for (Song::IterList::reverse_iterator iter = events.rbegin();
iter != events.rend(); ++iter) {
if ((*iter)->second.track == cur.get_track()) {
event = *iter;
return true;
}
}
return false;
}
void SongView::render_event(Song::iterator event) {
bool selected = is_event_selected(event);
int x,y,w,h;
get_event_rect(event, x, y, w, h);
bool mute = event->second.mute;
Gdk::Color color;
if (mute) {
x += 1;
y += 1;
color = colors[ColorMuted];
} else if (event->second.pattern->refcount > 1) {
color = colors[ColorGhost];
} else {
color = colors[ColorBlack];
}
// main border
gc->set_foreground(color);
window->draw_rectangle(gc, false, x, y+1, w, h-3);
if (!mute) {
// bottom shadow
window->draw_rectangle(gc, true, x+1, y+h-1, w, 1);
// right shadow
window->draw_rectangle(gc, true, x+w+1, y+2, 1, h-2);
}
pango_layout->set_width((w-4)*Pango::SCALE);
pango_layout->set_text(event->second.pattern->name.c_str());
if (selected) {
// fill
window->draw_rectangle(gc, true, x+2, y+3, w-3, h-6);
// outline
gc->set_foreground(colors[ColorWhite]);
window->draw_rectangle(gc, false, x+1, y+2, w-2, h-5);
// label
// TODO: make this fast
window->draw_layout(gc, x+3, y+5, pango_layout);
} else {
// fill
gc->set_foreground(colors[ColorWhite]);
window->draw_rectangle(gc, true, x+1, y+2, w-1, h-4);
// label
gc->set_foreground(color);
// TODO: make this fast
window->draw_layout(gc, x+3, y+5, pango_layout);
}
}
void SongView::render_track(int track) {
int width = 0;
int height = 0;
window->get_size(width, height);
int x,y;
get_event_pos(0, track, x, y);
gc->set_foreground(colors[ColorTrack]);
window->draw_rectangle(gc, true, 0, y, width, TrackHeight);
}
bool SongView::on_expose_event(GdkEventExpose* event) {
int width = 0;
int height = 0;
window->get_size(width, height);
// clear screen
gc->set_foreground(colors[ColorBackground]);
window->draw_rectangle(gc, true, 0, 0, width, height);
// first pass: render tracks
for (int track = 0; track < model->get_track_count(); ++track) {
render_track(track);
}
render_loop();
// second pass: render events
for (Song::iterator iter = model->song.begin();
iter != model->song.end(); ++iter) {
render_event(iter);
}
// draw play cursor
int play_x, play_y;
get_event_pos(play_position, 0, play_x, play_y);
window->draw_rectangle(xor_gc, true, play_x, 0, 2, height);
if (selecting()) {
render_select_box();
}
return true;
}
void SongView::invalidate_play_position() {
int width = 0;
int height = 0;
window->get_size(width, height);
int play_x, play_y;
get_event_pos(play_position, 0, play_x, play_y);
Gdk::Rectangle rect(play_x,0,2,height);
window->invalidate_rect(rect, true);
}
bool SongView::is_event_selected(Song::iterator event) {
return (std::find(selection.begin(), selection.end(),
event) != selection.end());
}
void SongView::clear_selection() {
invalidate_selection();
selection.clear();
}
void SongView::select_event(Song::iterator event) {
if (is_event_selected(event))
return;
selection.push_back(event);
invalidate_selection();
}
void SongView::deselect_event(Song::iterator event) {
if (!is_event_selected(event))
return;
invalidate_selection();
selection.remove(event);
}
void SongView::add_track() {
Track track;
model->tracks.push_back(track);
invalidate();
_tracks_changed();
}
void SongView::new_pattern(const SongCursor &cur, int size) {
if (cur.get_track() >= model->get_track_count())
return;
int frame = cur.get_frame();
int track = cur.get_track();
Pattern &pattern = model->new_pattern();
pattern.set_length(size);
Song::iterator event = model->song.add_event(frame, track, pattern);
clear_selection();
select_event(event);
}
void SongView::new_pattern(int size) {
SongCursor cur(*this);
cur.set_track(0);
cur.set_frame(0);
int frame_begin, frame_end, track_begin, track_end;
if (get_selection_range(frame_begin, frame_end,
track_begin, track_end)) {
cur.set_frame(frame_end);
cur.set_track(track_begin);
} else if (model->enable_loop) {
cur.set_frame(model->loop.get_begin());
}
new_pattern(cur, size);
}
void SongView::edit_pattern(Song::iterator iter) {
_pattern_edit_request(iter);
}
bool SongView::dragging() const {
return (interact_mode == InteractDrag);
}
bool SongView::moving() const {
return (interact_mode == InteractMove);
}
bool SongView::resizing() const {
return (interact_mode == InteractResize);
}
bool SongView::selecting() const {
return (interact_mode == InteractSelect);
}
bool SongView::on_button_press_event(GdkEventButton* event) {
bool ctrl_down = event->state & Gdk::CONTROL_MASK;
bool shift_down = event->state & Gdk::SHIFT_MASK;
/*
bool alt_down = event->state & Gdk::MOD1_MASK;
bool super_down = event->state & (Gdk::SUPER_MASK|Gdk::MOD4_MASK);
*/
bool double_click = (event->type == GDK_2BUTTON_PRESS);
grab_focus();
if (event->button == 1) {
SongCursor cur(*this);
cur.set_pos(event->x, event->y);
Song::iterator evt;
if (find_event(cur, evt)) {
if (!is_event_selected(evt)) {
if (!ctrl_down)
clear_selection();
select_event(evt);
} else if (ctrl_down && !shift_down) {
deselect_event(evt);
}
if (double_click) {
interact_mode = InteractNone;
edit_pattern(evt);
} else {
interact_mode = InteractDrag;
drag.start(event->x, event->y);
}
} else if (double_click) {
interact_mode = InteractNone;
cur.set_frame(quantize_frame(cur.get_frame()));
new_pattern(cur, get_step_size());
} else {
if (!ctrl_down)
clear_selection();
interact_mode = InteractSelect;
drag.start(event->x, event->y);
}
} else if (event->button == 3) {
_signal_context_menu(this, event);
}
return true;
}
bool SongView::can_resize_event(Song::iterator event, int x) {
int ex, ey, ew, eh;
get_event_rect(event,ex,ey,ew,eh);
return std::abs(x - (ex+ew)) < ResizeThreshold;
}
void SongView::set_loop(const Loop &loop) {
invalidate_loop();
this->loop = loop;
invalidate_loop();
}
void SongView::render_loop() {
if (!model->enable_loop)
return;
int width = 0;
int height = 0;
window->get_size(width, height);
gc->set_foreground(colors[ColorBlack]);
int x, y;
get_event_pos(loop.get_begin(), 0, x, y);
window->draw_rectangle(gc, true, x, 0, 1, height);
get_event_pos(loop.get_end(), 0, x, y);
window->draw_rectangle(gc, true, x, 0, 1, height);
}
void SongView::invalidate_loop() {
if (!window)
return;
int width = 0;
int height = 0;
window->get_size(width, height);
int x, y;
get_event_pos(loop.get_begin(), 0, x, y);
window->invalidate_rect(Gdk::Rectangle(x,0,1,height), true);
get_event_pos(loop.get_end(), 0, x, y);
window->invalidate_rect(Gdk::Rectangle(x,0,1,height), true);
}
void SongView::render_select_box() {
int x,y,w,h;
drag.get_rect(x,y,w,h);
if (w <= 1)
return;
if (h <= 1)
return;
window->draw_rectangle(xor_gc, false,
x,y,w-1,h-1);
}
void SongView::invalidate_select_box() {
int x,y,w,h;
drag.get_rect(x,y,w,h);
if (w <= 1)
return;
if (h <= 1)
return;
Gdk::Rectangle rect(x,y,w,h);
window->invalidate_rect(rect, true);
}
void SongView::select_from_box(bool toggle) {
int x0,y0,w,h;
drag.get_rect(x0,y0,w,h);
if (w <= 1)
return;
if (h <= 1)
return;
int x1 = x0+w;
int y1 = y0+h;
Song::iterator iter;
for (iter = model->song.begin(); iter != model->song.end(); ++iter) {
int ex0,ey0,ew,eh;
get_event_rect(iter, ex0, ey0, ew, eh);
int ex1 = ex0+ew;
int ey1 = ey0+eh;
if (ex0 >= x1)
continue;
if (ex1 < x0)
continue;
if (ey0 >= y1)
continue;
if (ey1 < y0)
continue;
if (toggle && is_event_selected(iter)) {
deselect_event(iter);
} else
select_event(iter);
}
}
void SongView::join_selection() {
int frame_begin,frame_end,track_begin,track_end;
if (!get_selection_range(frame_begin, frame_end,
track_begin, track_end))
return;
Pattern &pattern = model->new_pattern();
pattern.set_length(frame_end - frame_begin);
// find out how many channels we are going to need
int track_channels[MaxTracks];
memset(track_channels, 0, sizeof(track_channels));
for (Song::IterList::iterator iter = selection.begin();
iter != selection.end(); ++iter) {
Song::Event &song_event = (*iter)->second;
Pattern &old_pattern = *song_event.pattern;
assert ((song_event.track >= 0) && (song_event.track < MaxTracks));
track_channels[song_event.track] = std::max(
track_channels[song_event.track],
old_pattern.get_channel_count());
}
int channel_offset = 0;
// setup offsets
for (int track = 0; track < MaxTracks; ++track) {
int count = track_channels[track];
track_channels[track] = channel_offset;
channel_offset += count;
}
// setup channels
printf("%i channels in total\n", channel_offset);
pattern.set_channel_count(channel_offset);
// now do the actual joining
for (Song::IterList::iterator iter = selection.begin();
iter != selection.end(); ++iter) {
Song::Event &song_event = (*iter)->second;
Pattern &old_pattern = *song_event.pattern;
int frame_offset = song_event.frame - frame_begin;
// merge pattern events
for (Pattern::iterator jter = old_pattern.begin();
jter != old_pattern.end(); ++jter) {
Pattern::Event pattern_event = jter->second;
pattern_event.channel += track_channels[song_event.track];
pattern_event.frame += frame_offset;
pattern.add_event(pattern_event);
}
}
pattern.update_keys();
Song::iterator new_event = model->song.add_event(frame_begin, track_begin,
pattern);
// delete old events
erase_events();
// select new event
select_event(new_event);
}
void SongView::clone_selection(bool references/*=false*/) {
invalidate_selection();
Song::IterList new_selection;
Song::IterList::iterator iter;
for (iter = selection.begin(); iter != selection.end(); ++iter) {
Song::Event event = (*iter)->second;
if (!references) {
event.pattern = &model->new_pattern(event.pattern);
}
new_selection.push_back(model->song.add_event(event));
}
selection = new_selection;
invalidate_selection();
}
bool SongView::on_motion_notify_event(GdkEventMotion *event) {
bool shift_down = event->state & Gdk::SHIFT_MASK;
bool ctrl_down = event->state & Gdk::CONTROL_MASK;
cursor_x = event->x;
cursor_y = event->y;
if (interact_mode == InteractNone) {
SongCursor cur(*this);
cur.set_pos(event->x, event->y);
Song::iterator evt;
if (find_event(cur, evt)) {
if (can_resize_event(evt, event->x))
window->set_cursor(Gdk::Cursor(Gdk::SB_H_DOUBLE_ARROW));
else
window->set_cursor(Gdk::Cursor(Gdk::HAND1));
} else
window->set_cursor(Gdk::Cursor(Gdk::ARROW));
return false;
}
if (dragging()) {
drag.update(event->x, event->y);
if (drag.threshold_reached()) {
invalidate_selection();
SongCursor cur(*this);
cur.set_pos(drag.start_x, drag.start_y);
Song::iterator evt;
if (find_event(cur, evt) && can_resize_event(evt,drag.start_x)) {
interact_mode = InteractResize;
} else {
if (shift_down) {
invalidate_selection();
clone_selection(ctrl_down);
}
interact_mode = InteractMove;
}
}
}
if (resizing()||moving()) {
invalidate_selection();
drag.update(event->x, event->y);
invalidate_selection();
}
else if (selecting()) {
invalidate_select_box();
drag.update(event->x, event->y);
invalidate_select_box();
}
return true;
}
void SongView::do_move(int ofs_frame, int ofs_track) {
invalidate_selection();
bool can_move = true;
// verify that we can move
for (Song::IterList::iterator iter = selection.begin();
iter != selection.end(); ++iter) {
Song::Event &event = (*iter)->second;
int frame = quantize_frame(event.frame + ofs_frame);
int track = event.track + ofs_track;
if ((frame < 0)||(track < 0)||(track >= model->get_track_count())) {
can_move = false;
break;
}
}
if (can_move) {
Song::IterList new_selection;
// do the actual move
for (Song::IterList::iterator iter = selection.begin();
iter != selection.end(); ++iter) {
Song::Event event = (*iter)->second;
event.frame = quantize_frame(event.frame + ofs_frame);
event.track += ofs_track;
_pattern_erased(*iter);
model->song.erase(*iter);
Song::iterator new_event = model->song.add_event(event);
new_selection.push_back(new_event);
}
selection = new_selection;
invalidate_selection();
}
show_selection();
}
void SongView::apply_move() {
invalidate_selection();
int ofs_frame,ofs_track;
get_drag_offset(ofs_frame, ofs_track);
do_move(ofs_frame, ofs_track);
}
void SongView::toggle_mute_selection() {
for (Song::IterList::iterator iter = selection.begin();
iter != selection.end(); ++iter) {
Song::Event &event = (*iter)->second;
event.mute = !event.mute;
}
invalidate();
}
void SongView::do_resize(int ofs_frame) {
// verify that we can move
for (Song::IterList::iterator iter = selection.begin();
iter != selection.end(); ++iter) {
Song::Event &event = (*iter)->second;
int length = std::max(quantize_frame(event.pattern->get_length()
+ ofs_frame), get_step_size());
event.pattern->set_length(length);
}
invalidate();
show_selection();
}
void SongView::apply_resize() {
int ofs_frame,ofs_track;
get_drag_offset(ofs_frame, ofs_track);
do_resize(ofs_frame);
}
bool SongView::on_button_release_event(GdkEventButton* event) {
bool ctrl_down = event->state & Gdk::CONTROL_MASK;
if (moving()) {
apply_move();
} else if (resizing()) {
apply_resize();
} else if (selecting()) {
invalidate_select_box();
if (!ctrl_down) {
invalidate_selection();
clear_selection();
}
select_from_box(ctrl_down);
}
interact_mode = InteractNone;
invalidate_selection();
return false;
}
void SongView::select_first(bool increment) {
if (model->song.empty())
return;
if (!increment)
clear_selection();
select_event(model->song.begin());
show_selection();
}
void SongView::select_last(bool increment) {
if (model->song.empty())
return;
Song::iterator iter = model->song.end();
iter--;
if (!increment)
clear_selection();
select_event(iter);
show_selection();
}
void SongView::reset() {
selection.clear();
invalidate();
}
Song::iterator SongView::get_left_event(Song::iterator start) {
Song::iterator not_found = model->song.end();
if (start == model->song.begin())
return not_found;
Song::iterator iter = start;
do {
iter--;
if (iter->first == start->first)
continue;
if (iter->second.track == start->second.track)
return iter;
} while(iter != model->song.begin());
return not_found;
}
Song::iterator SongView::get_right_event(Song::iterator start) {
Song::iterator not_found = model->song.end();
Song::iterator iter = start;
iter++;
while (iter != model->song.end()) {
if (iter->first != start->first) {
if (iter->second.track == start->second.track)
return iter;
}
iter++;
}
return not_found;
}
Song::iterator SongView::nearest_y_event(Song::iterator start, int direction) {
Song::iterator not_found = model->song.end();
int best_delta_x = 9999999;
int best_delta_y = 9999999;
Song::iterator best_iter = not_found;
for (Song::iterator iter = model->song.begin();
iter != model->song.end(); ++iter) {
int delta_x = iter->second.track - start->second.track;
int delta_y = iter->first - start->first;
delta_x *= direction;
if (delta_x <= 0)
continue;
if (delta_x > best_delta_x)
continue;
delta_y = std::abs(delta_y);
if (delta_x == best_delta_x) {
if (delta_y >= best_delta_y)
continue;
}
best_delta_x = delta_x;
best_delta_y = delta_y;
best_iter = iter;
}
return best_iter;
}
void SongView::navigate(int dir_x, int dir_y, bool increment) {
if (selection.empty()) {
select_first();
return;
}
Song::iterator iter = selection.back();
if (dir_x < 0)
iter = get_left_event(iter);
else if (dir_x > 0)
iter = get_right_event(iter);
else if (dir_y)
iter = nearest_y_event(iter, dir_y);
if (iter != model->song.end()) {
if (!increment)
clear_selection();
select_event(iter);
}
show_selection();
}
bool SongView::get_selection_range(int &frame_begin, int &frame_end,
int &track_begin, int &track_end) {
if (selection.empty())
return false;
frame_begin = selection.front()->first;
frame_end = selection.front()->second.get_end();
track_begin = selection.front()->second.track;
track_end = selection.front()->second.track;
for (Song::IterList::iterator iter = selection.begin();
iter != selection.end(); ++iter) {
frame_begin = std::min(frame_begin, (*iter)->first);
frame_end = std::max(frame_end, (*iter)->second.get_end());
track_begin = std::min(track_begin, (*iter)->second.track);
track_end = std::max(track_end, (*iter)->second.track);
}
return true;
}
int SongView::get_selection_begin() {
int begin, end, tb, te;
if (!get_selection_range(begin, end, tb, te))
return -1;
return begin;
}
int SongView::get_selection_end() {
int begin, end, tb, te;
if (!get_selection_range(begin, end, tb, te))
return -1;
return end;
}
void SongView::set_loop_begin() {
int frame = get_selection_begin();
if (frame == -1)
return;
invalidate_loop();
loop.set_begin(frame);
model->loop = loop;
_loop_changed();
invalidate_loop();
}
void SongView::set_loop_end() {
int frame = get_selection_end();
if (frame == -1)
return;
invalidate_loop();
loop.set_end(frame);
model->loop = loop;
_loop_changed();
invalidate_loop();
}
void SongView::split_at_mouse_cursor() {
SongCursor cur(*this);
cur.set_pos(cursor_x, cursor_y);
int frame = quantize_frame(cur.get_frame());
invalidate_selection();
Song::IterList new_selection;
Song::IterList::iterator iter;
for (iter = selection.begin(); iter != selection.end(); ++iter) {
Song::Event &event = (*iter)->second;
if ((frame > event.frame) && (frame < event.get_last_frame())) {
// create two new events
Song::Event left_event = event;
Song::Event right_event = event;
left_event.pattern = &model->new_pattern(event.pattern);
left_event.pattern->set_length(frame - event.frame);
right_event.pattern = &model->new_pattern(event.pattern);
right_event.pattern->move_frames(0, event.frame - frame, -1);
right_event.pattern->set_length(event.get_end() - frame);
right_event.frame = frame;
new_selection.push_back(model->song.add_event(left_event));
new_selection.push_back(model->song.add_event(right_event));
_pattern_erased(*iter);
model->song.erase(*iter);
}
}
selection = new_selection;
invalidate_selection();
// cleanup
model->delete_unused_patterns();
}
void SongView::seek_to_mouse_cursor() {
SongCursor cur(*this);
cur.set_pos(cursor_x, cursor_y);
int frame = cur.get_frame();
if (frame < 0)
return;
_seek_request(quantize_frame(frame));
}
void SongView::play_from_selection() {
int frame = get_selection_begin();
if (frame == -1)
return;
_play_request(frame);
}
bool SongView::on_key_press_event(GdkEventKey* event) {
bool ctrl_down = event->state & Gdk::CONTROL_MASK;
bool shift_down = event->state & Gdk::SHIFT_MASK;
bool alt_down = event->state & Gdk::MOD1_MASK;
if (shift_down) {
switch (event->keyval) {
case GDK_Return: {
int f0,f1,t0,t1;
if (get_selection_range(f0,f1,t0,t1)) {
new_pattern((f1-f0));
} else {
new_pattern(get_step_size());
}
return true;
} break;
case GDK_D: if (ctrl_down) {
clone_selection(true);
return true;
} break;
case GDK_Left: {
if (alt_down)
do_resize(-get_step_size());
else
do_move(-get_step_size(),0);
return true;
} break;
case GDK_Right: {
if (alt_down)
do_resize(get_step_size());
else
do_move(get_step_size(),0);
return true;
} break;
case GDK_Page_Down: {
int f0,f1,t0,t1;
if (get_selection_range(f0,f1,t0,t1)) {
if (alt_down)
do_resize((f1-f0));
else
do_move((f1-f0),0);
}
return true;
} break;
case GDK_Page_Up: {
int f0,f1,t0,t1;
if (get_selection_range(f0,f1,t0,t1)) {
if (alt_down)
do_resize(-((f1-f0)/2));
else
do_move(-(f1-f0),0);
}
return true;
} break;
case GDK_Up: do_move(0,-1); return true;
case GDK_Down: do_move(0, 1); return true;
}
} else if (ctrl_down) {
switch (event->keyval) {
case GDK_d: clone_selection(); return true;
case GDK_b: set_loop_begin(); return true;
case GDK_e: set_loop_end(); return true;
case GDK_j: join_selection(); return true;
case GDK_Left: navigate(-1,0,true); return true;
case GDK_Right: navigate(1,0,true); return true;
case GDK_Up: navigate(0,-1,true); return true;
case GDK_Down: navigate(0,1,true); return true;
case GDK_Home: select_first(true); return true;
case GDK_End: select_last(true); return true;
default: break;
}
} else {
switch (event->keyval) {
case GDK_Delete: erase_events(); return true;
case GDK_Return: {
if (selection.size() == 1)
edit_pattern(selection.front());
return true;
} break;
case GDK_s: split_at_mouse_cursor(); return true;
case GDK_p: seek_to_mouse_cursor(); return true;
case GDK_m: toggle_mute_selection(); return true;
case GDK_Left: navigate(-1,0); return true;
case GDK_Right: navigate(1,0); return true;
case GDK_Up: navigate(0,-1); return true;
case GDK_Down: navigate(0,1); return true;
case GDK_Home: select_first(); return true;
case GDK_End: select_last(); return true;
case GDK_F6: play_from_selection(); return true;
default: break;
}
}
return false;
}
bool SongView::on_key_release_event(GdkEventKey* event) {
return false;
}
void SongView::on_size_allocate(Gtk::Allocation& allocation) {
set_allocation(allocation);
if (window) {
window->move_resize(allocation.get_x(), allocation.get_y(),
allocation.get_width(), allocation.get_height());
update_adjustments();
}
}
void SongView::show_selection() {
int f0,f1,t0,t1;
if (!get_selection_range(f0,f1,t0,t1))
return;
int step = model->get_frames_per_bar()*4;
if (hadjustment) {
int value = hadjustment->get_value();
int page_size = hadjustment->get_page_size();
if (f0 < value)
hadjustment->clamp_page(f0-step,f1);
else if (f1 >= (value+page_size))
hadjustment->clamp_page(f0,f1+step);
}
}
int absmod(int x, int m) {
return (x >= 0)?(x%m):((m-x)%m);
}
void SongView::get_drag_offset(int &frame, int &track) {
int dx,dy;
drag.get_delta(dx,dy);
get_event_length(dx, dy, frame, track);
//frame = quantize_frame(frame);
}
int SongView::get_step_size() {
switch(snap_mode) {
case SnapBar: return model->get_frames_per_bar();
default: break;
}
return 1;
}
int SongView::quantize_frame(int frame) {
return frame - (frame%get_step_size());
}
void SongView::get_event_rect(Song::iterator iter, int &x, int &y, int &w, int &h) {
Song::Event &event = iter->second;
int frame = event.frame;
int track = event.track;
int length = event.pattern->get_length();
if (moving() && is_event_selected(iter)) {
int ofs_frame,ofs_track;
get_drag_offset(ofs_frame, ofs_track);
frame = quantize_frame(frame + ofs_frame);
track += ofs_track;
} else if (resizing() && is_event_selected(iter)) {
int ofs_frame,ofs_track;
get_drag_offset(ofs_frame, ofs_track);
length = std::max(quantize_frame(length + ofs_frame), get_step_size());
}
get_event_pos(frame, track, x, y);
get_event_size(length, w, h);
}
void SongView::invalidate() {
if (!window)
return;
window->invalidate(true);
}
void SongView::invalidate_selection() {
Song::IterList::iterator iter;
for (iter = selection.begin(); iter != selection.end(); ++iter) {
int x,y,w,h;
get_event_rect(*iter, x, y, w, h);
w += 2;
Gdk::Rectangle rect(x,y,w,h);
window->invalidate_rect(rect, true);
}
}
void SongView::erase_events() {
invalidate_selection();
Song::IterList::iterator iter;
for (iter = selection.begin(); iter != selection.end(); ++iter) {
_pattern_erased(*iter);
model->song.erase(*iter);
}
selection.clear();
// cleanup
model->delete_unused_patterns();
}
void SongView::set_play_position(int pos) {
if (!window)
return;
if (pos == play_position)
return;
invalidate_play_position();
play_position = pos;
invalidate_play_position();
}
void SongView::update_adjustments() {
if (!window)
return;
Gtk::Allocation allocation = get_allocation();
int width = allocation.get_width();
int height = allocation.get_height();
if (hadjustment) {
int frame, track;
get_event_location(width, height, frame, track);
hadjustment->configure(0, // value
0, // lower
frame*4, // upper
1, // step increment
1, // page increment
frame); // page size
}
}
void SongView::on_adjustment_value_changed() {
if (hadjustment) {
int w,h;
get_event_size((int)(hadjustment->get_value()+0.5),w,h);
origin_x = -w;
}
invalidate();
}
void SongView::set_scroll_adjustments(Gtk::Adjustment *hadjustment,
Gtk::Adjustment *vadjustment) {
this->hadjustment = hadjustment;
this->vadjustment = vadjustment;
if (hadjustment) {
hadjustment->signal_value_changed().connect(sigc::mem_fun(*this,
&SongView::on_adjustment_value_changed));
}
if (vadjustment) {
vadjustment->signal_value_changed().connect(sigc::mem_fun(*this,
&SongView::on_adjustment_value_changed));
}
}
SongView::type_seek_request SongView::signal_seek_request() {
return _seek_request;
}
SongView::type_pattern_edit_request SongView::signal_pattern_edit_request() {
return _pattern_edit_request;
}
SongView::type_context_menu SongView::signal_context_menu() {
return _signal_context_menu;
}
SongView::type_loop_changed SongView::signal_loop_changed() {
return _loop_changed;
}
SongView::type_play_request SongView::signal_play_request() {
return _play_request;
}
SongView::type_pattern_erased SongView::signal_pattern_erased() {
return _pattern_erased;
}
SongView::type_tracks_changed SongView::signal_tracks_changed() {
return _tracks_changed;
}
//=============================================================================
} // namespace Jacker