2023-11-18 20:45:48 -07:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2023 Paul Davis <paul@linuxaudiosystems.com>
|
|
|
|
|
*
|
|
|
|
|
* 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.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-01-20 18:05:19 -07:00
|
|
|
#include "pbd/stateful_diff_command.h"
|
2025-03-04 11:30:42 -07:00
|
|
|
#include "pbd/unwind.h"
|
2025-01-20 18:05:19 -07:00
|
|
|
|
2024-01-26 20:45:22 -07:00
|
|
|
#include "ardour/midi_region.h"
|
2024-10-10 22:18:17 -06:00
|
|
|
#include "ardour/midi_track.h"
|
2024-02-02 10:32:04 -07:00
|
|
|
#include "ardour/smf_source.h"
|
2024-01-26 20:45:22 -07:00
|
|
|
|
2024-02-13 12:22:49 -07:00
|
|
|
#include "canvas/box.h"
|
2023-11-18 20:45:48 -07:00
|
|
|
#include "canvas/canvas.h"
|
|
|
|
|
#include "canvas/container.h"
|
|
|
|
|
#include "canvas/debug.h"
|
|
|
|
|
#include "canvas/scroll_group.h"
|
|
|
|
|
#include "canvas/rectangle.h"
|
2024-02-13 12:22:49 -07:00
|
|
|
#include "canvas/widget.h"
|
2023-11-18 20:45:48 -07:00
|
|
|
|
2024-01-31 18:08:44 -07:00
|
|
|
#include "gtkmm2ext/actions.h"
|
|
|
|
|
|
2024-02-13 12:22:49 -07:00
|
|
|
#include "widgets/ardour_button.h"
|
2024-11-12 09:49:59 -07:00
|
|
|
#include "widgets/ardour_dropdown.h"
|
2025-02-07 23:09:51 -07:00
|
|
|
#include "widgets/metabutton.h"
|
2024-11-12 09:49:59 -07:00
|
|
|
#include "widgets/tooltips.h"
|
2024-02-02 10:32:04 -07:00
|
|
|
|
2024-01-31 18:08:44 -07:00
|
|
|
#include "ardour_ui.h"
|
2024-01-26 20:45:22 -07:00
|
|
|
#include "editor_cursors.h"
|
2024-01-30 12:40:30 -07:00
|
|
|
#include "editor_drag.h"
|
2024-10-10 22:18:17 -06:00
|
|
|
#include "gui_thread.h"
|
2024-01-30 12:40:30 -07:00
|
|
|
#include "keyboard.h"
|
2025-02-07 23:09:51 -07:00
|
|
|
#include "midi_util.h"
|
2025-01-07 11:40:22 -07:00
|
|
|
#include "pianoroll_background.h"
|
2025-01-07 11:30:26 -07:00
|
|
|
#include "pianoroll.h"
|
2025-01-07 11:42:37 -07:00
|
|
|
#include "pianoroll_midi_view.h"
|
2024-01-30 12:40:30 -07:00
|
|
|
#include "note_base.h"
|
2024-02-07 13:13:40 -07:00
|
|
|
#include "prh.h"
|
2024-10-17 15:06:33 -06:00
|
|
|
#include "timers.h"
|
2023-11-18 20:45:48 -07:00
|
|
|
#include "ui_config.h"
|
2023-11-25 15:25:29 -07:00
|
|
|
#include "verbose_cursor.h"
|
2023-11-18 20:45:48 -07:00
|
|
|
|
2024-01-26 20:45:22 -07:00
|
|
|
#include "pbd/i18n.h"
|
|
|
|
|
|
2025-01-20 18:05:19 -07:00
|
|
|
using namespace PBD;
|
2023-11-19 20:43:29 -07:00
|
|
|
using namespace ARDOUR;
|
2023-11-18 20:45:48 -07:00
|
|
|
using namespace ArdourCanvas;
|
2024-02-13 12:22:49 -07:00
|
|
|
using namespace ArdourWidgets;
|
2024-01-30 12:40:30 -07:00
|
|
|
using namespace Gtkmm2ext;
|
2023-11-19 20:43:29 -07:00
|
|
|
using namespace Temporal;
|
2023-11-18 20:45:48 -07:00
|
|
|
|
2025-01-07 12:58:54 -07:00
|
|
|
Pianoroll::Pianoroll (std::string const & name)
|
|
|
|
|
: CueEditor (name)
|
2024-02-13 12:22:49 -07:00
|
|
|
, timebar_height (15.)
|
2024-02-06 22:27:36 -07:00
|
|
|
, n_timebars (3)
|
2024-02-07 13:13:40 -07:00
|
|
|
, prh (nullptr)
|
2024-02-09 11:29:23 -07:00
|
|
|
, bg (nullptr)
|
|
|
|
|
, view (nullptr)
|
2024-02-01 21:24:20 -07:00
|
|
|
, bbt_metric (*this)
|
2024-12-13 21:48:18 -07:00
|
|
|
, _note_mode (Sustained)
|
2025-01-27 09:50:28 -07:00
|
|
|
, zoom_in_allocate (false)
|
2025-03-15 10:56:47 -06:00
|
|
|
, solo_button (S_("Solo|S"))
|
2025-01-27 15:15:51 -07:00
|
|
|
, bar_adjustment (4, 1, 32, 1, 4)
|
|
|
|
|
, bar_spinner (bar_adjustment)
|
2025-01-28 13:29:21 -07:00
|
|
|
, length_label (X_("Record (Bars):"))
|
2025-03-04 11:30:42 -07:00
|
|
|
, ignore_channel_changes (false)
|
2023-11-18 20:45:48 -07:00
|
|
|
{
|
2024-02-13 12:22:49 -07:00
|
|
|
mouse_mode = Editing::MouseContent;
|
2024-02-23 11:25:07 -07:00
|
|
|
autoscroll_vertical_allowed = false;
|
2024-02-13 12:22:49 -07:00
|
|
|
|
2024-02-13 16:52:08 -07:00
|
|
|
build_grid_type_menu ();
|
|
|
|
|
build_draw_midi_menus();
|
|
|
|
|
|
2024-11-12 08:12:16 -07:00
|
|
|
build_upper_toolbar ();
|
2023-11-18 20:45:48 -07:00
|
|
|
build_canvas ();
|
2024-11-12 08:12:16 -07:00
|
|
|
build_lower_toolbar ();
|
2023-11-25 15:25:29 -07:00
|
|
|
|
2025-01-15 20:20:18 -07:00
|
|
|
load_bindings ();
|
|
|
|
|
register_actions ();
|
|
|
|
|
|
2024-10-07 11:28:56 -06:00
|
|
|
set_mouse_mode (Editing::MouseContent, true);
|
2023-11-18 20:45:48 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::~Pianoroll ()
|
2023-11-18 20:45:48 -07:00
|
|
|
{
|
2025-03-10 18:49:02 -06:00
|
|
|
delete own_bindings;
|
2025-03-12 17:58:30 -06:00
|
|
|
|
2025-03-12 22:30:16 +01:00
|
|
|
drop_grid (); // unparent gridlines before deleting _canvas_viewport
|
2025-03-12 17:58:30 -06:00
|
|
|
|
2025-03-12 22:30:16 +01:00
|
|
|
delete view;
|
2025-03-14 15:56:21 -06:00
|
|
|
delete bg;
|
2025-03-12 22:30:16 +01:00
|
|
|
delete _canvas_viewport;
|
2023-11-18 20:45:48 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-15 20:20:18 -07:00
|
|
|
void
|
|
|
|
|
Pianoroll::load_bindings ()
|
|
|
|
|
{
|
|
|
|
|
load_shared_bindings ();
|
2025-03-11 13:51:32 -06:00
|
|
|
for (auto & b : bindings) {
|
|
|
|
|
b->associate ();
|
|
|
|
|
}
|
2025-03-10 18:49:02 -06:00
|
|
|
set_widget_bindings (*get_canvas(), bindings, ARDOUR_BINDING_KEY);
|
2025-01-15 20:20:18 -07:00
|
|
|
}
|
|
|
|
|
|
2024-02-14 17:10:56 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::register_actions ()
|
2024-02-14 17:10:56 -07:00
|
|
|
{
|
2025-03-10 18:49:02 -06:00
|
|
|
editor_actions = ActionManager::create_action_group (own_bindings, editor_name());
|
2025-01-15 20:20:18 -07:00
|
|
|
|
2024-02-14 17:10:56 -07:00
|
|
|
bind_mouse_mode_buttons ();
|
|
|
|
|
register_grid_actions ();
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-04 21:21:00 -07:00
|
|
|
ArdourCanvas::GtkCanvasViewport*
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::get_canvas_viewport() const
|
2024-02-04 21:21:00 -07:00
|
|
|
{
|
|
|
|
|
return _canvas_viewport;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-23 11:25:07 -07:00
|
|
|
ArdourCanvas::GtkCanvas*
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::get_canvas() const
|
2024-02-04 21:21:00 -07:00
|
|
|
{
|
|
|
|
|
return _canvas;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_pre_event (GdkEvent* ev)
|
2024-02-04 21:21:00 -07:00
|
|
|
{
|
2024-02-04 21:47:20 -07:00
|
|
|
switch (ev->type) {
|
|
|
|
|
case GDK_ENTER_NOTIFY:
|
|
|
|
|
case GDK_LEAVE_NOTIFY:
|
|
|
|
|
if (canvas_enter_leave (&ev->crossing)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-04 21:21:00 -07:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-06 12:45:28 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::rebuild_parameter_button_map()
|
2025-01-06 12:45:28 -07:00
|
|
|
{
|
|
|
|
|
parameter_button_map.clear ();
|
2025-02-07 23:09:51 -07:00
|
|
|
parameter_button_map.insert (std::make_pair (velocity_button, Evoral::Parameter (ARDOUR::MidiVelocityAutomation, _visible_channel)));
|
|
|
|
|
parameter_button_map.insert (std::make_pair (bender_button, Evoral::Parameter (ARDOUR::MidiPitchBenderAutomation, _visible_channel)));
|
|
|
|
|
parameter_button_map.insert (std::make_pair (pressure_button, Evoral::Parameter (ARDOUR::MidiChannelPressureAutomation, _visible_channel)));
|
|
|
|
|
parameter_button_map.insert (std::make_pair (expression_button, Evoral::Parameter (ARDOUR::MidiCCAutomation, _visible_channel, MIDI_CTL_MSB_EXPRESSION)));
|
|
|
|
|
parameter_button_map.insert (std::make_pair (modulation_button, Evoral::Parameter (ARDOUR::MidiCCAutomation, _visible_channel, MIDI_CTL_MSB_MODWHEEL)));
|
|
|
|
|
|
|
|
|
|
parameter_button_map.insert (std::make_pair (cc_dropdown1, Evoral::Parameter (ARDOUR::MidiCCAutomation, _visible_channel, MIDI_CTL_MSB_GENERAL_PURPOSE1)));
|
|
|
|
|
parameter_button_map.insert (std::make_pair (cc_dropdown2, Evoral::Parameter (ARDOUR::MidiCCAutomation, _visible_channel, MIDI_CTL_MSB_GENERAL_PURPOSE2)));
|
|
|
|
|
parameter_button_map.insert (std::make_pair (cc_dropdown3, Evoral::Parameter (ARDOUR::MidiCCAutomation, _visible_channel, MIDI_CTL_MSB_GENERAL_PURPOSE3)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::reset_user_cc_choice (std::string name, Evoral::Parameter param, MetaButton* metabutton)
|
|
|
|
|
{
|
|
|
|
|
ParameterButtonMap::iterator iter;
|
|
|
|
|
|
|
|
|
|
for (iter = parameter_button_map.begin(); iter != parameter_button_map.end(); ++iter) {
|
|
|
|
|
if (iter->first == metabutton) {
|
|
|
|
|
parameter_button_map.erase (iter);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parameter_button_map.insert (std::make_pair (metabutton, param));
|
|
|
|
|
|
|
|
|
|
metabutton->set_by_menutext (name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::add_single_controller_item (Gtk::Menu_Helpers::MenuList& ctl_items,
|
|
|
|
|
int ctl,
|
|
|
|
|
const std::string& name,
|
|
|
|
|
ArdourWidgets::MetaButton* mb)
|
|
|
|
|
{
|
|
|
|
|
using namespace Gtk::Menu_Helpers;
|
|
|
|
|
|
|
|
|
|
const uint16_t selected_channels = 0xffff;
|
|
|
|
|
for (uint8_t chn = 0; chn < 16; chn++) {
|
|
|
|
|
|
|
|
|
|
if (selected_channels & (0x0001 << chn)) {
|
|
|
|
|
|
|
|
|
|
Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
|
|
|
|
|
std::string menu_text (string_compose ("<b>%1</b>: %2 [%3]", ctl, name, int (chn + 1)));
|
|
|
|
|
|
|
|
|
|
mb->add_item (name, menu_text, sigc::bind (sigc::mem_fun (*this, &Pianoroll::reset_user_cc_choice), name, fully_qualified_param, mb));
|
|
|
|
|
|
|
|
|
|
/* one channel only */
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-06 12:45:28 -07:00
|
|
|
}
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
void
|
2025-02-26 11:44:23 -07:00
|
|
|
Pianoroll::add_multi_controller_item (Gtk::Menu_Helpers::MenuList&,
|
2025-02-07 23:09:51 -07:00
|
|
|
const uint16_t channels,
|
|
|
|
|
int ctl,
|
|
|
|
|
const std::string& name,
|
2025-02-26 11:44:23 -07:00
|
|
|
MetaButton* mb)
|
2025-02-07 23:09:51 -07:00
|
|
|
{
|
|
|
|
|
using namespace Gtk;
|
|
|
|
|
using namespace Gtk::Menu_Helpers;
|
|
|
|
|
|
|
|
|
|
Menu* chn_menu = manage (new Menu);
|
|
|
|
|
MenuList& chn_items (chn_menu->items());
|
|
|
|
|
std::string menu_text (string_compose ("%1: %2", ctl, name));
|
|
|
|
|
|
|
|
|
|
/* Build the channel sub-menu */
|
|
|
|
|
|
|
|
|
|
Evoral::Parameter param_without_channel (MidiCCAutomation, 0, ctl);
|
|
|
|
|
|
2025-02-26 11:44:23 -07:00
|
|
|
/* look up the parameter represented by this MetaButton */
|
|
|
|
|
ParameterButtonMap::iterator pbmi = parameter_button_map.find (mb);
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
for (uint8_t chn = 0; chn < 16; chn++) {
|
|
|
|
|
if (channels & (0x0001 << chn)) {
|
|
|
|
|
|
|
|
|
|
/* for each selected channel, add a menu item for this controller */
|
|
|
|
|
|
|
|
|
|
Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl);
|
|
|
|
|
|
|
|
|
|
chn_items.push_back (CheckMenuElem (string_compose (_("Channel %1"), chn+1),
|
|
|
|
|
sigc::bind (sigc::mem_fun (*this, &Pianoroll::reset_user_cc_choice), menu_text, fully_qualified_param, mb)));
|
|
|
|
|
|
|
|
|
|
|
2025-02-26 11:44:23 -07:00
|
|
|
if (pbmi != parameter_button_map.end()) {
|
|
|
|
|
|
|
|
|
|
/* if this parameter is the one represented by
|
|
|
|
|
the button, mark it active in the menu
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (fully_qualified_param == pbmi->second) {
|
|
|
|
|
Gtk::CheckMenuItem* cmi = static_cast<Gtk::CheckMenuItem*>(&chn_items.back());
|
2025-02-07 23:09:51 -07:00
|
|
|
cmi->set_active();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* add an item to metabutton's menu that will connect to the
|
|
|
|
|
* per-channel submenu we built above.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-02-26 11:44:23 -07:00
|
|
|
mb->add_item (name, menu_text, *chn_menu, [](){});
|
2025-02-07 23:09:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-02-13 12:22:49 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::build_lower_toolbar ()
|
2024-11-12 08:12:16 -07:00
|
|
|
{
|
2024-12-20 10:08:20 -07:00
|
|
|
ArdourButton::Element elements = ArdourButton::Element (ArdourButton::Text|ArdourButton::Indicator|ArdourButton::Edge|ArdourButton::Body);
|
|
|
|
|
|
|
|
|
|
velocity_button = new ArdourButton (_("Velocity"), elements);
|
|
|
|
|
bender_button = new ArdourButton (_("Bender"), elements);
|
|
|
|
|
pressure_button = new ArdourButton (_("Pressure"), elements);
|
|
|
|
|
expression_button = new ArdourButton (_("Expression"), elements);
|
|
|
|
|
modulation_button = new ArdourButton (_("Modulation"), elements);
|
2025-02-07 23:09:51 -07:00
|
|
|
cc_dropdown1 = new MetaButton ();
|
|
|
|
|
cc_dropdown2 = new MetaButton ();
|
|
|
|
|
cc_dropdown3 = new MetaButton ();
|
|
|
|
|
|
|
|
|
|
cc_dropdown1->add_elements (ArdourButton::Indicator);
|
|
|
|
|
cc_dropdown2->add_elements (ArdourButton::Indicator);
|
|
|
|
|
cc_dropdown3->add_elements (ArdourButton::Indicator);
|
2024-12-20 10:08:20 -07:00
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
cc_dropdown1->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_led_click), cc_dropdown1));
|
|
|
|
|
cc_dropdown2->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_led_click), cc_dropdown2));
|
|
|
|
|
cc_dropdown3->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_led_click), cc_dropdown3));
|
2024-12-20 10:08:20 -07:00
|
|
|
|
2025-01-06 12:45:28 -07:00
|
|
|
rebuild_parameter_button_map ();
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
/* Only need to do this once because i->first is the actual button,
|
2025-01-06 12:45:28 -07:00
|
|
|
* which does not change even when the parameter_button_map is rebuilt.
|
|
|
|
|
*/
|
|
|
|
|
|
2024-12-20 10:08:20 -07:00
|
|
|
for (ParameterButtonMap::iterator i = parameter_button_map.begin(); i != parameter_button_map.end(); ++i) {
|
2025-02-07 23:09:51 -07:00
|
|
|
i->first->set_active_color (0xff0000ff);
|
|
|
|
|
i->first->set_distinct_led_click (true);
|
|
|
|
|
i->first->set_led_left (true);
|
|
|
|
|
i->first->set_act_on_release (false);
|
|
|
|
|
i->first->set_fallthrough_to_parent (true);
|
2024-12-20 10:08:20 -07:00
|
|
|
}
|
2024-11-12 08:12:16 -07:00
|
|
|
|
|
|
|
|
// button_bar.set_homogeneous (true);
|
|
|
|
|
button_bar.set_spacing (6);
|
|
|
|
|
button_bar.set_border_width (6);
|
|
|
|
|
button_bar.pack_start (*velocity_button, false, false);
|
|
|
|
|
button_bar.pack_start (*bender_button, false, false);
|
|
|
|
|
button_bar.pack_start (*pressure_button, false, false);
|
|
|
|
|
button_bar.pack_start (*modulation_button, false, false);
|
2024-12-20 10:08:20 -07:00
|
|
|
button_bar.pack_start (*cc_dropdown1, false, false);
|
|
|
|
|
button_bar.pack_start (*cc_dropdown2, false, false);
|
|
|
|
|
button_bar.pack_start (*cc_dropdown3, false, false);
|
|
|
|
|
|
2025-01-07 11:30:26 -07:00
|
|
|
velocity_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiVelocityAutomation, 0));
|
|
|
|
|
pressure_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiChannelPressureAutomation, 0));
|
|
|
|
|
bender_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiPitchBenderAutomation, 0));
|
|
|
|
|
modulation_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL));
|
|
|
|
|
expression_button->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_button_event), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION));
|
2024-12-20 10:08:20 -07:00
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
cc_dropdown1->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_automation_button_event), cc_dropdown1), false);
|
|
|
|
|
cc_dropdown2->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_automation_button_event), cc_dropdown2), false);
|
|
|
|
|
cc_dropdown3->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::user_automation_button_event), cc_dropdown3), false);
|
|
|
|
|
|
2025-01-07 11:30:26 -07:00
|
|
|
velocity_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiVelocityAutomation, 0));
|
|
|
|
|
pressure_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiChannelPressureAutomation, 0));
|
|
|
|
|
bender_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiPitchBenderAutomation, 0));
|
|
|
|
|
modulation_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_MODWHEEL));
|
|
|
|
|
expression_button->signal_led_clicked.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::automation_led_click), ARDOUR::MidiCCAutomation, MIDI_CTL_MSB_EXPRESSION));
|
2024-11-12 08:12:16 -07:00
|
|
|
|
|
|
|
|
_toolbox.pack_start (button_bar, false, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::build_upper_toolbar ()
|
2024-02-13 12:22:49 -07:00
|
|
|
{
|
2024-11-12 09:49:59 -07:00
|
|
|
using namespace Gtk::Menu_Helpers;
|
|
|
|
|
|
2024-02-13 12:22:49 -07:00
|
|
|
Gtk::HBox* mode_box = manage(new Gtk::HBox);
|
|
|
|
|
mode_box->set_border_width (2);
|
|
|
|
|
mode_box->set_spacing(2);
|
|
|
|
|
|
|
|
|
|
Gtk::HBox* mouse_mode_box = manage (new Gtk::HBox);
|
|
|
|
|
Gtk::HBox* mouse_mode_hbox = manage (new Gtk::HBox);
|
|
|
|
|
Gtk::VBox* mouse_mode_vbox = manage (new Gtk::VBox);
|
|
|
|
|
Gtk::Alignment* mouse_mode_align = manage (new Gtk::Alignment);
|
|
|
|
|
|
|
|
|
|
Glib::RefPtr<Gtk::SizeGroup> mouse_mode_size_group = Gtk::SizeGroup::create (Gtk::SIZE_GROUP_VERTICAL);
|
|
|
|
|
mouse_mode_size_group->add_widget (mouse_draw_button);
|
|
|
|
|
mouse_mode_size_group->add_widget (mouse_content_button);
|
|
|
|
|
|
|
|
|
|
mouse_mode_size_group->add_widget (grid_type_selector);
|
|
|
|
|
mouse_mode_size_group->add_widget (draw_length_selector);
|
|
|
|
|
mouse_mode_size_group->add_widget (draw_velocity_selector);
|
|
|
|
|
mouse_mode_size_group->add_widget (draw_channel_selector);
|
|
|
|
|
mouse_mode_size_group->add_widget (snap_mode_button);
|
|
|
|
|
|
|
|
|
|
mouse_mode_hbox->set_spacing (2);
|
|
|
|
|
mouse_mode_hbox->pack_start (mouse_draw_button, false, false);
|
|
|
|
|
mouse_mode_hbox->pack_start (mouse_content_button, false, false);
|
|
|
|
|
|
|
|
|
|
mouse_mode_vbox->pack_start (*mouse_mode_hbox);
|
|
|
|
|
|
|
|
|
|
mouse_mode_align->add (*mouse_mode_vbox);
|
|
|
|
|
mouse_mode_align->set (0.5, 1.0, 0.0, 0.0);
|
|
|
|
|
|
|
|
|
|
mouse_mode_box->pack_start (*mouse_mode_align, false, false);
|
|
|
|
|
|
|
|
|
|
pack_snap_box ();
|
|
|
|
|
pack_draw_box ();
|
|
|
|
|
|
|
|
|
|
Gtk::HBox* _toolbar_inner = manage (new Gtk::HBox);
|
|
|
|
|
Gtk::HBox* _toolbar_outer = manage (new Gtk::HBox);
|
2025-01-07 12:58:54 -07:00
|
|
|
Gtk::HBox* _toolbar_left = manage (new Gtk::HBox);
|
2024-02-13 12:22:49 -07:00
|
|
|
|
2024-02-13 16:32:29 -07:00
|
|
|
_toolbar_inner->pack_start (*mouse_mode_box, false, false);
|
|
|
|
|
_toolbar_inner->pack_start (snap_box, false, false);
|
|
|
|
|
_toolbar_inner->pack_start (grid_box, false, false);
|
|
|
|
|
_toolbar_inner->pack_start (draw_box, false, false);
|
2024-02-13 12:22:49 -07:00
|
|
|
|
2024-11-12 09:49:59 -07:00
|
|
|
set_tooltip (full_zoom_button, _("Zoom to full clip"));
|
2024-12-29 17:30:33 -07:00
|
|
|
set_tooltip (note_mode_button, _("Toggle between drum and regular note drawing"));
|
2025-01-02 18:14:56 -07:00
|
|
|
note_mode_button.set_icon (ArdourIcon::Drum);
|
2024-11-12 09:49:59 -07:00
|
|
|
|
2025-01-03 09:14:26 -07:00
|
|
|
play_note_selection_button.set_icon (ArdourIcon::ToolAudition);
|
|
|
|
|
|
2024-12-29 14:34:53 -07:00
|
|
|
#define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale()))
|
|
|
|
|
note_mode_button.set_size_request (PX_SCALE(50), -1);
|
2025-01-02 18:14:56 -07:00
|
|
|
note_mode_button.set_active_color (UIConfiguration::instance().color ("alert:yellow"));
|
2024-12-29 14:34:53 -07:00
|
|
|
|
2025-03-15 10:56:47 -06:00
|
|
|
|
|
|
|
|
play_button.set_icon (ArdourIcon::TransportPlay);
|
|
|
|
|
loop_button.set_icon (ArdourIcon::TransportLoop);
|
|
|
|
|
|
|
|
|
|
solo_button.set_name ("solo button");
|
|
|
|
|
|
|
|
|
|
play_box.set_spacing (8);
|
|
|
|
|
play_box.pack_start (play_button, false, false);
|
|
|
|
|
play_box.pack_start (loop_button, false, false);
|
|
|
|
|
play_box.pack_start (solo_button, false, false);
|
|
|
|
|
play_button.show();
|
|
|
|
|
loop_button.show();
|
|
|
|
|
solo_button.show();
|
|
|
|
|
play_box.set_no_show_all (true);
|
|
|
|
|
play_box.show ();
|
|
|
|
|
play_button.signal_button_release_event().connect (sigc::mem_fun (*this, &Pianoroll::play_button_press), false);
|
|
|
|
|
solo_button.signal_button_release_event().connect (sigc::mem_fun (*this, &Pianoroll::solo_button_press), false);
|
|
|
|
|
loop_button.signal_button_release_event().connect (sigc::mem_fun (*this, &Pianoroll::loop_button_press), false);
|
|
|
|
|
|
2025-01-27 15:15:51 -07:00
|
|
|
rec_enable_button.set_icon (ArdourIcon::RecButton);
|
|
|
|
|
rec_enable_button.set_sensitive (false);
|
2025-01-27 20:21:23 -07:00
|
|
|
rec_enable_button.signal_button_release_event().connect (sigc::mem_fun (*this, &Pianoroll::rec_button_press), false);
|
2025-03-15 10:56:47 -06:00
|
|
|
rec_enable_button.set_name ("record enable button");
|
2025-01-27 15:15:51 -07:00
|
|
|
|
|
|
|
|
rec_box.set_spacing (12);
|
|
|
|
|
rec_box.pack_start (rec_enable_button, false, false);
|
|
|
|
|
rec_box.pack_start (length_label, false, false);
|
|
|
|
|
rec_box.pack_start (bar_spinner, false, false);
|
|
|
|
|
rec_enable_button.show();
|
|
|
|
|
length_label.show ();
|
|
|
|
|
bar_spinner.show ();
|
2025-01-27 20:21:23 -07:00
|
|
|
rec_box.set_no_show_all (true);
|
|
|
|
|
/* rec box not shown */
|
2025-01-27 15:15:51 -07:00
|
|
|
|
2024-11-12 09:49:59 -07:00
|
|
|
_toolbar_outer->set_border_width (6);
|
|
|
|
|
_toolbar_outer->set_spacing (12);
|
2025-03-15 10:56:47 -06:00
|
|
|
_toolbar_outer->pack_start (play_box, false, false);
|
2025-01-27 15:15:51 -07:00
|
|
|
_toolbar_outer->pack_start (rec_box, false, false);
|
2024-11-12 09:49:59 -07:00
|
|
|
_toolbar_outer->pack_start (visible_channel_label, false, false);
|
|
|
|
|
_toolbar_outer->pack_start (visible_channel_selector, false, false);
|
|
|
|
|
_toolbar_outer->pack_start (play_note_selection_button, false, false);
|
2024-12-13 21:48:18 -07:00
|
|
|
_toolbar_outer->pack_start (note_mode_button, false, false);
|
2024-11-12 09:49:59 -07:00
|
|
|
_toolbar_outer->pack_start (follow_playhead_button, false, false);
|
2024-02-13 16:32:29 -07:00
|
|
|
_toolbar_outer->pack_start (*_toolbar_inner, true, false);
|
2024-11-12 09:49:59 -07:00
|
|
|
|
2024-12-29 11:24:10 -07:00
|
|
|
build_zoom_focus_menu ();
|
2025-01-21 17:38:01 -07:00
|
|
|
zoom_focus_selector.set_text (zoom_focus_strings[(int)_zoom_focus]);
|
2024-12-29 11:24:10 -07:00
|
|
|
|
2025-01-07 12:58:54 -07:00
|
|
|
_toolbar_left->pack_start (zoom_in_button, false, false);
|
|
|
|
|
_toolbar_left->pack_start (zoom_out_button, false, false);
|
|
|
|
|
_toolbar_left->pack_start (full_zoom_button, false, false);
|
|
|
|
|
_toolbar_left->pack_start (zoom_focus_selector, false, false);
|
|
|
|
|
|
|
|
|
|
_toolbar_outer->pack_start (*_toolbar_left, true, false);
|
2024-02-13 12:22:49 -07:00
|
|
|
_toolbox.pack_start (*_toolbar_outer, false, false);
|
|
|
|
|
|
2025-01-22 18:19:51 -07:00
|
|
|
_contents.add (_toolbox);
|
2025-02-27 16:42:22 +01:00
|
|
|
_contents.signal_unmap().connect ([this]() {_canvas_viewport->unmap ();}, false);
|
|
|
|
|
_contents.signal_map().connect ([this]() {_canvas_viewport->map ();}, false);
|
2024-02-13 12:22:49 -07:00
|
|
|
}
|
|
|
|
|
|
2024-11-12 09:49:59 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::set_visible_channel (int n)
|
2024-11-12 09:49:59 -07:00
|
|
|
{
|
2025-03-04 11:30:42 -07:00
|
|
|
PBD::Unwinder<bool> uw (ignore_channel_changes, true);
|
|
|
|
|
|
2024-12-20 10:44:35 -07:00
|
|
|
_visible_channel = n;
|
2025-02-02 11:29:35 -07:00
|
|
|
visible_channel_selector.set_active (string_compose ("%1", _visible_channel + 1));
|
2024-12-20 11:48:40 -07:00
|
|
|
|
2025-01-06 12:45:28 -07:00
|
|
|
rebuild_parameter_button_map ();
|
|
|
|
|
|
2024-12-20 11:48:40 -07:00
|
|
|
if (view) {
|
2025-02-28 11:57:16 -07:00
|
|
|
view->set_visible_channel (n);
|
2024-12-20 11:48:40 -07:00
|
|
|
view->swap_automation_channel (n);
|
|
|
|
|
}
|
2024-11-12 09:49:59 -07:00
|
|
|
}
|
|
|
|
|
|
2023-11-18 20:45:48 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::build_canvas ()
|
2023-11-18 20:45:48 -07:00
|
|
|
{
|
|
|
|
|
_canvas_viewport = new ArdourCanvas::GtkCanvasViewport (horizontal_adjustment, vertical_adjustment);
|
|
|
|
|
|
2023-11-25 15:25:29 -07:00
|
|
|
_canvas = _canvas_viewport->canvas ();
|
2024-01-31 18:08:44 -07:00
|
|
|
_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
|
2025-01-07 11:30:26 -07:00
|
|
|
_canvas->signal_event().connect (sigc::mem_fun (*this, &Pianoroll::canvas_pre_event), false);
|
2023-11-25 15:25:29 -07:00
|
|
|
dynamic_cast<ArdourCanvas::GtkCanvas*>(_canvas)->use_nsglview (UIConfiguration::instance().get_nsgl_view_mode () == NSGLHiRes);
|
2023-11-18 20:45:48 -07:00
|
|
|
|
2024-06-07 22:54:07 -06:00
|
|
|
_canvas->PreRender.connect (sigc::mem_fun(*this, &EditingContext::pre_render));
|
|
|
|
|
|
2023-11-18 20:45:48 -07:00
|
|
|
/* scroll group for items that should not automatically scroll
|
|
|
|
|
* (e.g verbose cursor). It shares the canvas coordinate space.
|
|
|
|
|
*/
|
|
|
|
|
no_scroll_group = new ArdourCanvas::Container (_canvas->root());
|
|
|
|
|
|
2024-02-04 21:21:00 -07:00
|
|
|
h_scroll_group = new ArdourCanvas::ScrollGroup (_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally);
|
2023-11-18 20:45:48 -07:00
|
|
|
CANVAS_DEBUG_NAME (h_scroll_group, "canvas h scroll");
|
2024-02-04 21:21:00 -07:00
|
|
|
_canvas->add_scroller (*h_scroll_group);
|
2023-11-18 20:45:48 -07:00
|
|
|
|
2024-02-09 11:29:23 -07:00
|
|
|
|
|
|
|
|
v_scroll_group = new ArdourCanvas::ScrollGroup (_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsVertically);
|
|
|
|
|
CANVAS_DEBUG_NAME (v_scroll_group, "canvas v scroll");
|
|
|
|
|
_canvas->add_scroller (*v_scroll_group);
|
|
|
|
|
|
2024-02-04 21:21:00 -07:00
|
|
|
hv_scroll_group = new ArdourCanvas::ScrollGroup (_canvas->root(),
|
2024-02-06 22:27:36 -07:00
|
|
|
ArdourCanvas::ScrollGroup::ScrollSensitivity (ArdourCanvas::ScrollGroup::ScrollsVertically|
|
|
|
|
|
ArdourCanvas::ScrollGroup::ScrollsHorizontally));
|
2024-01-31 18:08:44 -07:00
|
|
|
CANVAS_DEBUG_NAME (hv_scroll_group, "cue canvas hv scroll");
|
2024-02-04 21:21:00 -07:00
|
|
|
_canvas->add_scroller (*hv_scroll_group);
|
2023-11-18 20:45:48 -07:00
|
|
|
|
2024-02-04 21:21:00 -07:00
|
|
|
cursor_scroll_group = new ArdourCanvas::ScrollGroup (_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally);
|
2024-01-31 18:08:44 -07:00
|
|
|
CANVAS_DEBUG_NAME (cursor_scroll_group, "cue canvas cursor scroll");
|
2024-02-04 21:21:00 -07:00
|
|
|
_canvas->add_scroller (*cursor_scroll_group);
|
2023-11-18 20:45:48 -07:00
|
|
|
|
|
|
|
|
/*a group to hold global rects like punch/loop indicators */
|
|
|
|
|
global_rect_group = new ArdourCanvas::Container (hv_scroll_group);
|
2024-01-31 18:08:44 -07:00
|
|
|
CANVAS_DEBUG_NAME (global_rect_group, "cue global rect group");
|
2023-11-18 20:45:48 -07:00
|
|
|
|
|
|
|
|
transport_loop_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX));
|
2024-01-31 18:08:44 -07:00
|
|
|
CANVAS_DEBUG_NAME (transport_loop_range_rect, "cue loop rect");
|
2023-11-18 20:45:48 -07:00
|
|
|
transport_loop_range_rect->hide();
|
|
|
|
|
|
|
|
|
|
/*a group to hold time (measure) lines */
|
|
|
|
|
time_line_group = new ArdourCanvas::Container (h_scroll_group);
|
2024-01-31 18:08:44 -07:00
|
|
|
CANVAS_DEBUG_NAME (time_line_group, "cue time line group");
|
2023-11-18 20:45:48 -07:00
|
|
|
|
2024-02-06 22:27:36 -07:00
|
|
|
meter_bar = new ArdourCanvas::Rectangle (time_line_group, ArdourCanvas::Rect (0., 0, ArdourCanvas::COORD_MAX, timebar_height));
|
|
|
|
|
CANVAS_DEBUG_NAME (meter_bar, "Meter Bar");
|
|
|
|
|
meter_bar->set_fill(true);
|
|
|
|
|
meter_bar->set_outline(true);
|
|
|
|
|
meter_bar->set_outline_what(ArdourCanvas::Rectangle::BOTTOM);
|
|
|
|
|
meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar"));
|
|
|
|
|
meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
|
|
|
|
|
meter_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
|
|
|
|
|
|
|
|
|
|
tempo_bar = new ArdourCanvas::Rectangle (time_line_group, ArdourCanvas::Rect (0.0, timebar_height, ArdourCanvas::COORD_MAX, timebar_height * 2));
|
2024-02-02 16:37:05 -07:00
|
|
|
CANVAS_DEBUG_NAME (tempo_bar, "Tempo Bar");
|
|
|
|
|
tempo_bar->set_fill(true);
|
2024-02-06 22:27:36 -07:00
|
|
|
tempo_bar->set_outline(true);
|
2024-02-02 16:37:05 -07:00
|
|
|
tempo_bar->set_outline_what(ArdourCanvas::Rectangle::BOTTOM);
|
2024-02-06 22:27:36 -07:00
|
|
|
tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar"));
|
|
|
|
|
tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator"));
|
|
|
|
|
meter_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM);
|
2024-02-01 21:24:20 -07:00
|
|
|
|
|
|
|
|
Pango::FontDescription font (UIConfiguration::instance().get_SmallerFont());
|
|
|
|
|
Pango::FontDescription larger_font (UIConfiguration::instance().get_SmallBoldFont());
|
2024-02-06 22:27:36 -07:00
|
|
|
bbt_ruler = new ArdourCanvas::Ruler (time_line_group, &bbt_metric, ArdourCanvas::Rect (0, timebar_height * 2, ArdourCanvas::COORD_MAX, timebar_height * 3));
|
2024-02-01 21:24:20 -07:00
|
|
|
bbt_ruler->set_font_description (font);
|
|
|
|
|
Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base");
|
|
|
|
|
Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text");
|
|
|
|
|
bbt_ruler->set_fill_color (base);
|
|
|
|
|
bbt_ruler->set_outline_color (text);
|
2024-02-02 16:37:05 -07:00
|
|
|
CANVAS_DEBUG_NAME (bbt_ruler, "cue bbt ruler");
|
2024-02-01 21:24:20 -07:00
|
|
|
|
2025-01-28 14:34:03 -07:00
|
|
|
|
2024-02-01 21:24:20 -07:00
|
|
|
data_group = new ArdourCanvas::Container (hv_scroll_group);
|
|
|
|
|
CANVAS_DEBUG_NAME (data_group, "cue data group");
|
|
|
|
|
|
|
|
|
|
bg = new CueMidiBackground (data_group);
|
2025-01-07 11:30:26 -07:00
|
|
|
_canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &Pianoroll::canvas_allocate), false);
|
2024-01-31 18:08:44 -07:00
|
|
|
|
2024-02-22 10:37:09 -07:00
|
|
|
// used as rubberband rect
|
|
|
|
|
rubberband_rect = new ArdourCanvas::Rectangle (data_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, 0.0));
|
|
|
|
|
rubberband_rect->hide();
|
|
|
|
|
rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect"));
|
|
|
|
|
rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect"));
|
|
|
|
|
CANVAS_DEBUG_NAME (rubberband_rect, X_("cue rubberband rect"));
|
|
|
|
|
|
2024-02-09 20:59:50 -07:00
|
|
|
prh = new ArdourCanvas::PianoRollHeader (v_scroll_group, *bg);
|
|
|
|
|
|
2025-01-07 11:42:37 -07:00
|
|
|
view = new PianorollMidiView (nullptr, *data_group, *no_scroll_group, *this, *bg, 0xff0000ff);
|
2025-01-07 11:30:26 -07:00
|
|
|
view->AutomationStateChange.connect (sigc::mem_fun (*this, &Pianoroll::automation_state_changed));
|
2025-03-04 11:30:42 -07:00
|
|
|
view->VisibleChannelChanged.connect (view_connections, invalidator (*this), std::bind (&Pianoroll::visible_channel_changed, this), gui_context());
|
2024-10-17 13:37:21 -06:00
|
|
|
|
|
|
|
|
bg->set_view (view);
|
|
|
|
|
prh->set_view (view);
|
|
|
|
|
|
|
|
|
|
/* This must be called after prh and bg have had their view set */
|
|
|
|
|
|
2024-02-09 20:59:50 -07:00
|
|
|
double w, h;
|
|
|
|
|
prh->size_request (w, h);
|
|
|
|
|
|
2024-06-06 16:12:09 -06:00
|
|
|
_timeline_origin = w;
|
2024-10-17 14:36:27 -06:00
|
|
|
|
|
|
|
|
prh->set_position (Duple (0., n_timebars * timebar_height));
|
|
|
|
|
data_group->set_position (ArdourCanvas::Duple (_timeline_origin, timebar_height * n_timebars));
|
2024-11-11 21:49:55 -07:00
|
|
|
no_scroll_group->set_position (ArdourCanvas::Duple (_timeline_origin, timebar_height * n_timebars));
|
2024-10-17 14:36:27 -06:00
|
|
|
cursor_scroll_group->set_position (ArdourCanvas::Duple (_timeline_origin, timebar_height * n_timebars));
|
|
|
|
|
h_scroll_group->set_position (Duple (_timeline_origin, 0.));
|
2024-02-09 20:59:50 -07:00
|
|
|
|
2024-10-17 13:37:21 -06:00
|
|
|
_verbose_cursor = new VerboseCursor (*this);
|
|
|
|
|
|
|
|
|
|
// _playhead_cursor = new EditorCursor (*this, &Editor::canvas_playhead_cursor_event, X_("playhead"));
|
|
|
|
|
_playhead_cursor = new EditorCursor (*this, X_("playhead"));
|
|
|
|
|
_playhead_cursor->set_sensitive (UIConfiguration::instance().get_sensitize_playhead());
|
2024-10-17 14:36:27 -06:00
|
|
|
_playhead_cursor->set_color (UIConfiguration::instance().color ("play head"));
|
2024-10-17 21:47:52 -06:00
|
|
|
_playhead_cursor->canvas_item().raise_to_top();
|
2024-11-11 21:49:55 -07:00
|
|
|
h_scroll_group->raise_to_top ();
|
2024-10-17 13:37:21 -06:00
|
|
|
|
2024-01-31 18:08:44 -07:00
|
|
|
_canvas->set_name ("MidiCueCanvas");
|
|
|
|
|
_canvas->add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
|
|
|
|
|
_canvas->set_can_focus ();
|
|
|
|
|
|
2024-11-12 08:12:16 -07:00
|
|
|
_toolbox.pack_start (*_canvas_viewport, true, true);
|
2024-06-29 16:47:42 -06:00
|
|
|
}
|
|
|
|
|
|
2025-03-04 11:30:42 -07:00
|
|
|
void
|
|
|
|
|
Pianoroll::visible_channel_changed ()
|
|
|
|
|
{
|
|
|
|
|
if (ignore_channel_changes) {
|
|
|
|
|
/* We're changing it */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Something else changed it */
|
|
|
|
|
|
|
|
|
|
if (!view) {
|
|
|
|
|
return; /* Ought to be impossible */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_visible_channel = view->visible_channel();
|
|
|
|
|
visible_channel_selector.set_active (string_compose ("%1", view->visible_channel() + 1));
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-29 16:47:42 -06:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::bindings_changed ()
|
2024-06-29 16:47:42 -06:00
|
|
|
{
|
2025-03-11 13:43:55 -06:00
|
|
|
bindings.clear ();
|
|
|
|
|
load_shared_bindings ();
|
2024-01-31 18:08:44 -07:00
|
|
|
}
|
|
|
|
|
|
2024-10-17 15:06:33 -06:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::maybe_update ()
|
2024-10-17 15:06:33 -06:00
|
|
|
{
|
2025-03-14 17:25:05 -06:00
|
|
|
ARDOUR::TriggerPtr trigger;
|
2024-10-17 15:06:33 -06:00
|
|
|
|
2025-03-14 17:25:05 -06:00
|
|
|
if (_track) {
|
|
|
|
|
trigger = _track->triggerbox()->currently_playing ();
|
|
|
|
|
}
|
2024-10-18 09:50:31 -06:00
|
|
|
|
2024-10-17 15:06:33 -06:00
|
|
|
if (!trigger) {
|
2025-03-14 17:25:05 -06:00
|
|
|
|
|
|
|
|
if (_drags->active() || !view) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
samplepos_t pos = _session->transport_sample();
|
|
|
|
|
samplepos_t spos = view->midi_region()->source_position().samples();
|
|
|
|
|
if (pos < spos) {
|
|
|
|
|
_playhead_cursor->set_position (0);
|
|
|
|
|
} else {
|
|
|
|
|
_playhead_cursor->set_position (pos - spos);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-17 15:06:33 -06:00
|
|
|
} else {
|
|
|
|
|
if (trigger->active ()) {
|
2024-11-25 21:37:31 -07:00
|
|
|
_playhead_cursor->set_position (trigger->current_pos().samples() + trigger->the_region()->start().samples());
|
2024-10-17 15:06:33 -06:00
|
|
|
} else {
|
|
|
|
|
_playhead_cursor->set_position (0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 18:08:44 -07:00
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_enter_leave (GdkEventCrossing* ev)
|
2024-01-31 18:08:44 -07:00
|
|
|
{
|
|
|
|
|
switch (ev->type) {
|
|
|
|
|
case GDK_ENTER_NOTIFY:
|
|
|
|
|
if (ev->detail != GDK_NOTIFY_INFERIOR) {
|
|
|
|
|
_canvas_viewport->canvas()->grab_focus ();
|
|
|
|
|
ActionManager::set_sensitive (_midi_actions, true);
|
2024-12-11 15:15:42 -07:00
|
|
|
within_track_canvas = true;
|
2024-01-31 18:08:44 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case GDK_LEAVE_NOTIFY:
|
|
|
|
|
if (ev->detail != GDK_NOTIFY_INFERIOR) {
|
|
|
|
|
ActionManager::set_sensitive (_midi_actions, false);
|
2024-12-11 15:15:42 -07:00
|
|
|
within_track_canvas = false;
|
2024-01-31 18:08:44 -07:00
|
|
|
ARDOUR_UI::instance()->reset_focus (_canvas_viewport);
|
2025-01-07 17:10:34 -07:00
|
|
|
gdk_window_set_cursor (_canvas_viewport->get_window()->gobj(), nullptr);
|
2024-01-31 18:08:44 -07:00
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2023-11-18 20:45:48 -07:00
|
|
|
}
|
|
|
|
|
|
2024-01-08 14:38:16 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_allocate (Gtk::Allocation alloc)
|
2024-01-08 14:38:16 -07:00
|
|
|
{
|
2024-02-02 16:37:05 -07:00
|
|
|
_visible_canvas_width = alloc.get_width();
|
|
|
|
|
_visible_canvas_height = alloc.get_height();
|
|
|
|
|
|
2024-10-13 09:03:44 -06:00
|
|
|
double timebars = n_timebars * timebar_height;
|
2024-10-17 21:47:52 -06:00
|
|
|
bg->set_size (alloc.get_width(), alloc.get_height() - timebars);
|
2024-10-13 09:03:44 -06:00
|
|
|
view->set_height (alloc.get_height() - timebars);
|
2024-10-17 21:47:52 -06:00
|
|
|
prh->set (ArdourCanvas::Rect (0, 0, prh->x1(), view->midi_context().height()));
|
2024-12-12 11:52:35 -07:00
|
|
|
|
|
|
|
|
_track_canvas_width = _visible_canvas_width - prh->x1();
|
2025-01-27 09:50:28 -07:00
|
|
|
|
|
|
|
|
if (zoom_in_allocate) {
|
|
|
|
|
zoom_to_show (timecnt_t (timepos_t (max_extents_scale() * max_zoom_extent ().second.samples())));
|
|
|
|
|
zoom_in_allocate = false;
|
|
|
|
|
}
|
2025-01-29 10:52:51 -07:00
|
|
|
|
|
|
|
|
update_grid ();
|
2024-01-08 14:38:16 -07:00
|
|
|
}
|
2023-11-19 20:43:29 -07:00
|
|
|
|
|
|
|
|
timepos_t
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::snap_to_grid (timepos_t const & presnap, Temporal::RoundMode direction, SnapPref gpref) const
|
2023-11-19 20:43:29 -07:00
|
|
|
{
|
|
|
|
|
/* BBT time only */
|
|
|
|
|
return snap_to_bbt (presnap, direction, gpref);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::snap_to_internal (timepos_t& start, Temporal::RoundMode direction, SnapPref pref, bool ensure_snap) const
|
2023-11-19 20:43:29 -07:00
|
|
|
{
|
|
|
|
|
UIConfiguration const& uic (UIConfiguration::instance ());
|
|
|
|
|
const timepos_t presnap = start;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
timepos_t dist = timepos_t::max (start.time_domain()); // this records the distance of the best snap result we've found so far
|
|
|
|
|
timepos_t best = timepos_t::max (start.time_domain()); // this records the best snap-result we've found so far
|
|
|
|
|
|
|
|
|
|
timepos_t pre (presnap);
|
|
|
|
|
timepos_t post (snap_to_grid (pre, direction, pref));
|
|
|
|
|
|
|
|
|
|
check_best_snap (presnap, post, dist, best);
|
|
|
|
|
|
|
|
|
|
if (timepos_t::max (start.time_domain()) == best) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* now check "magnetic" state: is the grid within reasonable on-screen distance to trigger a snap?
|
|
|
|
|
* this also helps to avoid snapping to somewhere the user can't see. (i.e.: I clicked on a region and it disappeared!!)
|
|
|
|
|
* ToDo: Perhaps this should only occur if EditPointMouse?
|
|
|
|
|
*/
|
|
|
|
|
samplecnt_t snap_threshold_s = pixel_to_sample (uic.get_snap_threshold ());
|
|
|
|
|
|
|
|
|
|
if (!ensure_snap && ::llabs (best.distance (presnap).samples()) > snap_threshold_s) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
start = best;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-26 20:45:22 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::set_samples_per_pixel (samplecnt_t spp)
|
2024-01-26 20:45:22 -07:00
|
|
|
{
|
2024-02-23 11:25:07 -07:00
|
|
|
CueEditor::set_samples_per_pixel (spp);
|
2024-01-26 20:45:22 -07:00
|
|
|
|
|
|
|
|
if (view) {
|
|
|
|
|
view->set_samples_per_pixel (spp);
|
|
|
|
|
}
|
2024-02-01 21:24:20 -07:00
|
|
|
|
|
|
|
|
bbt_ruler->set_range (0, current_page_samples());
|
2024-02-02 16:37:05 -07:00
|
|
|
compute_bbt_ruler_scale (0, current_page_samples());
|
|
|
|
|
bbt_metric.units_per_pixel = spp;
|
2024-01-26 20:45:22 -07:00
|
|
|
}
|
|
|
|
|
|
2023-11-19 20:43:29 -07:00
|
|
|
samplecnt_t
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::current_page_samples() const
|
2023-11-19 20:43:29 -07:00
|
|
|
{
|
2025-01-26 15:49:41 -07:00
|
|
|
return (samplecnt_t) _track_canvas_width * samples_per_pixel;
|
2023-11-19 20:43:29 -07:00
|
|
|
}
|
|
|
|
|
|
2024-12-27 14:00:55 -07:00
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_bg_event (GdkEvent* event, ArdourCanvas::Item* item)
|
2024-12-27 14:00:55 -07:00
|
|
|
{
|
|
|
|
|
return typed_event (item, event, RegionItem);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 19:44:25 -06:00
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_control_point_event (GdkEvent* event, ArdourCanvas::Item* item, ControlPoint* cp)
|
2024-09-20 19:44:25 -06:00
|
|
|
{
|
|
|
|
|
return typed_event (item, event, ControlPointItem);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-04 19:07:52 -07:00
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_note_event (GdkEvent* event, ArdourCanvas::Item* item)
|
2024-01-04 19:07:52 -07:00
|
|
|
{
|
2024-01-31 18:08:44 -07:00
|
|
|
return typed_event (item, event, NoteItem);
|
2024-01-04 19:07:52 -07:00
|
|
|
}
|
2024-01-08 14:35:15 -07:00
|
|
|
|
2024-09-18 12:27:43 -06:00
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_velocity_base_event (GdkEvent* event, ArdourCanvas::Item* item)
|
2024-09-18 12:27:43 -06:00
|
|
|
{
|
|
|
|
|
return typed_event (item, event, VelocityBaseItem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_velocity_event (GdkEvent* event, ArdourCanvas::Item* item)
|
2024-09-18 12:27:43 -06:00
|
|
|
{
|
|
|
|
|
return typed_event (item, event, VelocityItem);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-23 09:06:31 -07:00
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_cue_start_event (GdkEvent* event, ArdourCanvas::Item* item)
|
2024-11-23 09:06:31 -07:00
|
|
|
{
|
|
|
|
|
return typed_event (item, event, ClipStartItem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::canvas_cue_end_event (GdkEvent* event, ArdourCanvas::Item* item)
|
2024-11-23 09:06:31 -07:00
|
|
|
{
|
|
|
|
|
return typed_event (item, event, ClipEndItem);
|
|
|
|
|
}
|
2024-09-18 12:27:43 -06:00
|
|
|
|
2024-11-25 17:54:05 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::set_trigger_start (Temporal::timepos_t const & p)
|
2024-11-25 17:54:05 -07:00
|
|
|
{
|
2025-01-20 18:05:19 -07:00
|
|
|
if (ref.trigger()) {
|
|
|
|
|
ref.trigger()->the_region()->trim_front (p);
|
|
|
|
|
} else {
|
|
|
|
|
begin_reversible_command (_("trim region front"));
|
|
|
|
|
view->midi_region()->clear_changes ();
|
2025-03-10 09:42:27 -06:00
|
|
|
view->midi_region()->trim_front (view->midi_region()->source_position() + p);
|
2025-01-20 18:05:19 -07:00
|
|
|
add_command (new StatefulDiffCommand (view->midi_region()));
|
|
|
|
|
commit_reversible_command ();
|
|
|
|
|
}
|
2024-11-25 17:54:05 -07:00
|
|
|
}
|
|
|
|
|
|
2024-11-26 13:11:17 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::set_trigger_end (Temporal::timepos_t const & p)
|
2024-11-26 13:11:17 -07:00
|
|
|
{
|
2025-01-20 18:05:19 -07:00
|
|
|
if (ref.trigger()) {
|
|
|
|
|
ref.trigger()->the_region()->trim_end (p);
|
|
|
|
|
} else {
|
|
|
|
|
begin_reversible_command (_("trim region end"));
|
|
|
|
|
view->midi_region()->clear_changes ();
|
2025-03-10 09:42:27 -06:00
|
|
|
view->midi_region()->trim_end (view->midi_region()->source_position() + p);
|
2025-01-20 18:05:19 -07:00
|
|
|
add_command (new StatefulDiffCommand (view->midi_region()));
|
|
|
|
|
commit_reversible_command ();
|
|
|
|
|
}
|
2024-11-26 13:11:17 -07:00
|
|
|
}
|
|
|
|
|
|
2024-01-08 14:35:15 -07:00
|
|
|
Gtk::Widget&
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::viewport()
|
2024-01-08 14:35:15 -07:00
|
|
|
{
|
|
|
|
|
return *_canvas_viewport;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-13 12:22:49 -07:00
|
|
|
Gtk::Widget&
|
2025-01-22 18:19:51 -07:00
|
|
|
Pianoroll::contents ()
|
2024-02-13 12:22:49 -07:00
|
|
|
{
|
2025-01-22 18:19:51 -07:00
|
|
|
return _contents;
|
2024-02-13 12:22:49 -07:00
|
|
|
}
|
|
|
|
|
|
2024-10-10 22:18:17 -06:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::data_captured (samplecnt_t total_duration)
|
2024-10-14 21:43:36 -06:00
|
|
|
{
|
|
|
|
|
data_capture_duration = total_duration;
|
|
|
|
|
|
|
|
|
|
if (!idle_update_queued.exchange (1)) {
|
2025-01-07 11:30:26 -07:00
|
|
|
Glib::signal_idle().connect (sigc::mem_fun (*this, &Pianoroll::idle_data_captured));
|
2024-10-14 21:43:36 -06:00
|
|
|
}
|
2024-10-17 14:36:27 -06:00
|
|
|
|
2024-10-24 11:44:36 -06:00
|
|
|
_playhead_cursor->set_position (data_capture_duration);
|
2024-10-14 21:43:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::idle_data_captured ()
|
2024-10-10 22:18:17 -06:00
|
|
|
{
|
2024-10-24 11:44:36 -06:00
|
|
|
double where = sample_to_pixel_unrounded (data_capture_duration);
|
2024-10-17 21:47:52 -06:00
|
|
|
|
|
|
|
|
if (where > _visible_canvas_width * 0.80) {
|
|
|
|
|
set_samples_per_pixel (samples_per_pixel * 1.5);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 22:18:17 -06:00
|
|
|
if (view) {
|
2024-10-14 21:43:36 -06:00
|
|
|
view->clip_data_recorded (data_capture_duration);
|
2024-10-10 22:18:17 -06:00
|
|
|
}
|
2024-10-14 21:43:36 -06:00
|
|
|
idle_update_queued.store (0);
|
|
|
|
|
return false;
|
2024-10-10 22:18:17 -06:00
|
|
|
}
|
|
|
|
|
|
2024-10-18 09:50:31 -06:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::box_rec_enable_change (ARDOUR::TriggerBox const & b)
|
2024-10-18 09:50:31 -06:00
|
|
|
{
|
2024-10-10 22:18:17 -06:00
|
|
|
}
|
|
|
|
|
|
2024-01-08 14:35:15 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::trigger_rec_enable_change (ARDOUR::Trigger const & t)
|
2024-01-08 14:35:15 -07:00
|
|
|
{
|
2024-11-09 09:20:25 -07:00
|
|
|
if (!t.armed()) {
|
2024-10-13 09:03:44 -06:00
|
|
|
view->end_write ();
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-08 14:35:15 -07:00
|
|
|
|
2024-01-29 14:32:18 -07:00
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
|
2024-01-29 14:32:18 -07:00
|
|
|
{
|
2024-01-30 12:40:30 -07:00
|
|
|
switch (event->button.button) {
|
|
|
|
|
case 1:
|
|
|
|
|
return button_press_handler_1 (item, event, item_type);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
|
return button_press_handler_2 (item, event, item_type);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return button_press_dispatch (&event->button);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 18:08:44 -07:00
|
|
|
return false;
|
2024-01-29 14:32:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
|
2024-01-29 14:32:18 -07:00
|
|
|
{
|
2024-01-30 12:40:30 -07:00
|
|
|
NoteBase* note = nullptr;
|
|
|
|
|
|
2024-10-07 16:28:54 -06:00
|
|
|
switch (item_type) {
|
|
|
|
|
case NoteItem:
|
|
|
|
|
if (mouse_mode == Editing::MouseContent) {
|
2024-01-30 12:40:30 -07:00
|
|
|
/* Existing note: allow trimming/motion */
|
|
|
|
|
if ((note = reinterpret_cast<NoteBase*> (item->get_data ("notebase")))) {
|
|
|
|
|
if (note->big_enough_to_trim() && note->mouse_near_ends()) {
|
|
|
|
|
_drags->set (new NoteResizeDrag (*this, item), event, get_canvas_cursor());
|
|
|
|
|
} else {
|
2024-02-27 12:21:27 -07:00
|
|
|
NoteDrag* nd = new NoteDrag (*this, item);
|
|
|
|
|
nd->set_bounding_item (data_group);
|
|
|
|
|
_drags->set (nd, event);
|
2024-01-30 12:40:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-07 16:28:54 -06:00
|
|
|
}
|
|
|
|
|
return true;
|
2024-09-18 12:27:43 -06:00
|
|
|
|
2024-10-07 16:28:54 -06:00
|
|
|
case ControlPointItem:
|
|
|
|
|
if (mouse_mode == Editing::MouseContent) {
|
2024-09-20 19:44:25 -06:00
|
|
|
_drags->set (new ControlPointDrag (*this, item), event);
|
2024-10-07 16:28:54 -06:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
break;
|
2024-09-20 19:44:25 -06:00
|
|
|
|
2024-10-07 16:28:54 -06:00
|
|
|
case VelocityItem:
|
2025-02-01 13:31:26 -07:00
|
|
|
/* mouse mode independent - always allow drags */
|
|
|
|
|
_drags->set (new LollipopDrag (*this, item), event);
|
2024-10-07 16:28:54 -06:00
|
|
|
return true;
|
|
|
|
|
break;
|
2024-09-18 12:27:43 -06:00
|
|
|
|
2024-10-07 16:28:54 -06:00
|
|
|
case VelocityBaseItem:
|
2025-02-01 13:31:26 -07:00
|
|
|
switch (mouse_mode) {
|
|
|
|
|
case Editing::MouseContent:
|
|
|
|
|
/* rubberband drag to select notes */
|
|
|
|
|
_drags->set (new RubberbandSelectDrag (*this, item, [&](GdkEvent* ev, timepos_t const & pos) { return view->velocity_rb_click (ev, pos); }), event);
|
|
|
|
|
break;
|
|
|
|
|
case Editing::MouseDraw:
|
|
|
|
|
_drags->set (new VelocityLineDrag (*this, *static_cast<ArdourCanvas::Rectangle*>(item), false, Temporal::BeatTime), event);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-10-07 16:28:54 -06:00
|
|
|
return true;
|
|
|
|
|
break;
|
2024-09-18 12:27:43 -06:00
|
|
|
|
2024-10-07 16:28:54 -06:00
|
|
|
case AutomationTrackItem:
|
|
|
|
|
switch (mouse_mode) {
|
|
|
|
|
case Editing::MouseContent:
|
|
|
|
|
/* rubberband drag to select automation points */
|
2024-10-09 11:34:01 -06:00
|
|
|
_drags->set (new RubberbandSelectDrag (*this, item, [&](GdkEvent* ev, timepos_t const & pos) { return view->automation_rb_click (ev, pos); }), event);
|
2024-10-07 16:28:54 -06:00
|
|
|
break;
|
|
|
|
|
case Editing::MouseDraw:
|
|
|
|
|
_drags->set (new AutomationDrawDrag (*this, nullptr, *static_cast<ArdourCanvas::Rectangle*>(item), false, Temporal::BeatTime), event);
|
2024-09-20 19:44:25 -06:00
|
|
|
break;
|
2024-01-30 12:40:30 -07:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-10-07 16:28:54 -06:00
|
|
|
return true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case EditorAutomationLineItem: {
|
|
|
|
|
ARDOUR::SelectionOperation op = ArdourKeyboard::selection_type (event->button.state);
|
|
|
|
|
select_automation_line (&event->button, item, op);
|
2024-10-09 12:49:48 -06:00
|
|
|
switch (mouse_mode) {
|
|
|
|
|
case Editing::MouseContent:
|
|
|
|
|
_drags->set (new LineDrag (*this, item, [&](GdkEvent* ev,timepos_t const & pos, double) { view->line_drag_click (ev, pos); }), event);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-10-07 16:28:54 -06:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-23 09:06:31 -07:00
|
|
|
case ClipStartItem: {
|
|
|
|
|
ArdourCanvas::Rectangle* r = dynamic_cast<ArdourCanvas::Rectangle*> (item);
|
|
|
|
|
if (r) {
|
2024-11-25 17:54:05 -07:00
|
|
|
_drags->set (new ClipStartDrag (*this, *r, *this), event);
|
2024-11-23 09:06:31 -07:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case ClipEndItem: {
|
|
|
|
|
ArdourCanvas::Rectangle* r = dynamic_cast<ArdourCanvas::Rectangle*> (item);
|
|
|
|
|
if (r) {
|
2024-11-26 13:11:17 -07:00
|
|
|
_drags->set (new ClipEndDrag (*this, *r, *this), event);
|
2024-11-23 09:06:31 -07:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-07 16:28:54 -06:00
|
|
|
default:
|
|
|
|
|
break;
|
2024-01-30 12:40:30 -07:00
|
|
|
}
|
|
|
|
|
|
2024-01-31 18:08:44 -07:00
|
|
|
return false;
|
2024-01-29 14:32:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::button_press_handler_2 (ArdourCanvas::Item*, GdkEvent*, ItemType)
|
2024-01-29 14:32:18 -07:00
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
|
2024-01-29 14:32:18 -07:00
|
|
|
{
|
2024-02-06 22:27:36 -07:00
|
|
|
if (!Keyboard::is_context_menu_event (&event->button)) {
|
|
|
|
|
|
|
|
|
|
/* see if we're finishing a drag */
|
|
|
|
|
|
|
|
|
|
if (_drags->active ()) {
|
|
|
|
|
bool const r = _drags->end_grab (event);
|
|
|
|
|
if (r) {
|
|
|
|
|
/* grab dragged, so do nothing else */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-31 18:08:44 -07:00
|
|
|
|
2024-01-30 12:40:30 -07:00
|
|
|
if (Keyboard::is_context_menu_event (&event->button)) {
|
|
|
|
|
switch (item_type) {
|
|
|
|
|
case NoteItem:
|
|
|
|
|
if (internal_editing()) {
|
|
|
|
|
popup_note_context_menu (item, event);
|
2024-01-31 18:08:44 -07:00
|
|
|
return true;
|
2024-01-30 12:40:30 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 18:08:44 -07:00
|
|
|
return false;
|
2024-01-29 14:32:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::button_press_dispatch (GdkEventButton* ev)
|
2024-01-29 14:32:18 -07:00
|
|
|
{
|
2024-01-30 12:40:30 -07:00
|
|
|
/* this function is intended only for buttons 4 and above. */
|
|
|
|
|
|
|
|
|
|
Gtkmm2ext::MouseButton b (ev->state, ev->button);
|
|
|
|
|
return button_bindings->activate (b, Gtkmm2ext::Bindings::Press);
|
2024-01-29 14:32:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::button_release_dispatch (GdkEventButton* ev)
|
2024-01-29 14:32:18 -07:00
|
|
|
{
|
2024-01-30 12:40:30 -07:00
|
|
|
/* this function is intended only for buttons 4 and above. */
|
|
|
|
|
|
|
|
|
|
Gtkmm2ext::MouseButton b (ev->state, ev->button);
|
|
|
|
|
return button_bindings->activate (b, Gtkmm2ext::Bindings::Release);
|
2024-01-29 14:32:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::motion_handler (ArdourCanvas::Item*, GdkEvent* event, bool from_autoscroll)
|
2024-01-29 14:32:18 -07:00
|
|
|
{
|
2024-02-06 22:27:36 -07:00
|
|
|
if (_drags->active ()) {
|
|
|
|
|
//drags change the snapped_cursor location, because we are snapping the thing being dragged, not the actual mouse cursor
|
|
|
|
|
return _drags->motion_handler (event, from_autoscroll);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-29 14:32:18 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::key_press_handler (ArdourCanvas::Item*, GdkEvent* ev, ItemType)
|
2024-01-29 14:32:18 -07:00
|
|
|
{
|
2024-01-31 18:08:44 -07:00
|
|
|
|
|
|
|
|
switch (ev->key.keyval) {
|
|
|
|
|
case GDK_d:
|
|
|
|
|
set_mouse_mode (Editing::MouseDraw);
|
|
|
|
|
break;
|
|
|
|
|
case GDK_e:
|
|
|
|
|
set_mouse_mode (Editing::MouseContent);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-29 14:32:18 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::key_release_handler (ArdourCanvas::Item*, GdkEvent*, ItemType)
|
2024-01-29 14:32:18 -07:00
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-30 12:40:30 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::set_mouse_mode (Editing::MouseMode m, bool force)
|
2024-01-30 12:40:30 -07:00
|
|
|
{
|
|
|
|
|
if (m != Editing::MouseDraw && m != Editing::MouseContent) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-07 11:28:56 -06:00
|
|
|
EditingContext::set_mouse_mode (m, force);
|
2024-01-30 12:40:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::step_mouse_mode (bool next)
|
2024-01-30 12:40:30 -07:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Editing::MouseMode
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::current_mouse_mode () const
|
2024-01-30 12:40:30 -07:00
|
|
|
{
|
|
|
|
|
return mouse_mode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::internal_editing() const
|
2024-01-30 12:40:30 -07:00
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RegionSelection
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::region_selection()
|
2024-01-30 12:40:30 -07:00
|
|
|
{
|
|
|
|
|
RegionSelection rs;
|
|
|
|
|
return rs;
|
|
|
|
|
}
|
2024-02-01 21:24:20 -07:00
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
edit_last_mark_label (std::vector<ArdourCanvas::Ruler::Mark>& marks, const std::string& newlabel)
|
|
|
|
|
{
|
|
|
|
|
ArdourCanvas::Ruler::Mark copy = marks.back();
|
|
|
|
|
copy.label = newlabel;
|
|
|
|
|
marks.pop_back ();
|
|
|
|
|
marks.push_back (copy);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::metric_get_bbt (std::vector<ArdourCanvas::Ruler::Mark>& marks, samplepos_t leftmost, samplepos_t rightmost, gint /*maxchars*/)
|
2024-02-01 21:24:20 -07:00
|
|
|
{
|
2025-01-29 10:54:07 -07:00
|
|
|
if (!_session) {
|
2024-02-01 21:24:20 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-02 10:32:04 -07:00
|
|
|
bool provided = false;
|
2024-02-01 21:24:20 -07:00
|
|
|
std::shared_ptr<Temporal::TempoMap> tmap;
|
|
|
|
|
|
2024-10-17 21:47:52 -06:00
|
|
|
if (view && view->midi_region()) {
|
2024-02-02 10:32:04 -07:00
|
|
|
std::shared_ptr<SMFSource> smf (std::dynamic_pointer_cast<SMFSource> (view->midi_region()->midi_source()));
|
|
|
|
|
|
|
|
|
|
if (smf) {
|
|
|
|
|
tmap = smf->tempo_map (provided);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!provided) {
|
2024-02-01 21:24:20 -07:00
|
|
|
tmap.reset (new Temporal::TempoMap (Temporal::Tempo (120, 4), Temporal::Meter (4, 4)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditingContext::TempoMapScope tms (*this, tmap);
|
|
|
|
|
Temporal::TempoMapPoints::const_iterator i;
|
|
|
|
|
|
|
|
|
|
char buf[64];
|
|
|
|
|
Temporal::BBT_Time next_beat;
|
|
|
|
|
double bbt_position_of_helper;
|
|
|
|
|
bool helper_active = false;
|
|
|
|
|
ArdourCanvas::Ruler::Mark mark;
|
|
|
|
|
const samplecnt_t sr (_session->sample_rate());
|
|
|
|
|
|
|
|
|
|
Temporal::TempoMapPoints grid;
|
|
|
|
|
grid.reserve (4096);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* prevent negative values of leftmost from creeping into tempomap
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const Beats left = tmap->quarters_at_sample (leftmost).round_down_to_beat();
|
|
|
|
|
const Beats lower_beat = (left < Beats() ? Beats() : left);
|
|
|
|
|
|
|
|
|
|
using std::max;
|
|
|
|
|
|
|
|
|
|
switch (bbt_ruler_scale) {
|
|
|
|
|
|
|
|
|
|
case bbt_show_quarters:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 1);
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_eighths:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 2);
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_sixteenths:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 4);
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_thirtyseconds:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 8);
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_sixtyfourths:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 16);
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_onetwentyeighths:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 32);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_1:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 1);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_4:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 4);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_16:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 16);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_64:
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 64);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
/* bbt_show_many */
|
|
|
|
|
tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 128);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if 0 // DEBUG GRID
|
|
|
|
|
for (auto const& g : grid) {
|
2024-02-13 16:52:08 -07:00
|
|
|
std::cout << "Grid " << g.time() << " Beats: " << g.beats() << " BBT: " << g.bbt() << " sample: " << g.sample(_session->nominal_sample_rate ()) << "\n";
|
2024-02-01 21:24:20 -07:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (distance (grid.begin(), grid.end()) == 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* we can accent certain lines depending on the user's Grid choice */
|
|
|
|
|
/* for example, even in a 4/4 meter we can draw a grid with triplet-feel */
|
|
|
|
|
/* and in this case you will want the accents on '3s' not '2s' */
|
|
|
|
|
uint32_t bbt_divisor = 2;
|
|
|
|
|
|
|
|
|
|
using namespace Editing;
|
|
|
|
|
|
|
|
|
|
switch (_grid_type) {
|
|
|
|
|
case GridTypeBeatDiv3:
|
|
|
|
|
bbt_divisor = 3;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv5:
|
|
|
|
|
bbt_divisor = 5;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv6:
|
|
|
|
|
bbt_divisor = 3;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv7:
|
|
|
|
|
bbt_divisor = 7;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv10:
|
|
|
|
|
bbt_divisor = 5;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv12:
|
|
|
|
|
bbt_divisor = 3;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv14:
|
|
|
|
|
bbt_divisor = 7;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv16:
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv20:
|
|
|
|
|
bbt_divisor = 5;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv24:
|
|
|
|
|
bbt_divisor = 6;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv28:
|
|
|
|
|
bbt_divisor = 7;
|
|
|
|
|
break;
|
|
|
|
|
case GridTypeBeatDiv32:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
bbt_divisor = 2;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t bbt_beat_subdivision = 1;
|
|
|
|
|
switch (bbt_ruler_scale) {
|
|
|
|
|
case bbt_show_quarters:
|
|
|
|
|
bbt_beat_subdivision = 1;
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_eighths:
|
|
|
|
|
bbt_beat_subdivision = 1;
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_sixteenths:
|
|
|
|
|
bbt_beat_subdivision = 2;
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_thirtyseconds:
|
|
|
|
|
bbt_beat_subdivision = 4;
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_sixtyfourths:
|
|
|
|
|
bbt_beat_subdivision = 8;
|
|
|
|
|
break;
|
|
|
|
|
case bbt_show_onetwentyeighths:
|
|
|
|
|
bbt_beat_subdivision = 16;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
bbt_beat_subdivision = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bbt_beat_subdivision *= bbt_divisor;
|
|
|
|
|
|
|
|
|
|
switch (bbt_ruler_scale) {
|
|
|
|
|
|
|
|
|
|
case bbt_show_many:
|
|
|
|
|
snprintf (buf, sizeof(buf), "cannot handle %" PRIu32 " bars", bbt_bars);
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Major;
|
|
|
|
|
mark.label = buf;
|
|
|
|
|
mark.position = leftmost;
|
|
|
|
|
marks.push_back (mark);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_64:
|
|
|
|
|
for (i = grid.begin(); i != grid.end(); i++) {
|
|
|
|
|
BBT_Time bbt ((*i).bbt());
|
|
|
|
|
if (bbt.is_bar()) {
|
|
|
|
|
if (bbt.bars % 64 == 1) {
|
|
|
|
|
if (bbt.bars % 256 == 1) {
|
|
|
|
|
snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars);
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Major;
|
|
|
|
|
} else {
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
|
if (bbt.bars % 256 == 129) {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Minor;
|
|
|
|
|
} else {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Micro;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mark.label = buf;
|
|
|
|
|
mark.position = (*i).sample (sr);
|
|
|
|
|
marks.push_back (mark);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_16:
|
|
|
|
|
for (i = grid.begin(); i != grid.end(); i++) {
|
|
|
|
|
BBT_Time bbt ((*i).bbt());
|
|
|
|
|
if (bbt.is_bar()) {
|
|
|
|
|
if (bbt.bars % 16 == 1) {
|
|
|
|
|
if (bbt.bars % 64 == 1) {
|
|
|
|
|
snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars);
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Major;
|
|
|
|
|
} else {
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
|
if (bbt.bars % 64 == 33) {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Minor;
|
|
|
|
|
} else {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Micro;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mark.label = buf;
|
|
|
|
|
mark.position = (*i).sample(sr);
|
|
|
|
|
marks.push_back (mark);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_4:
|
|
|
|
|
for (i = grid.begin(); i != grid.end(); ++i) {
|
|
|
|
|
BBT_Time bbt ((*i).bbt());
|
|
|
|
|
if (bbt.is_bar()) {
|
|
|
|
|
if (bbt.bars % 4 == 1) {
|
|
|
|
|
if (bbt.bars % 16 == 1) {
|
|
|
|
|
snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars);
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Major;
|
|
|
|
|
} else {
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Minor;
|
|
|
|
|
}
|
|
|
|
|
mark.label = buf;
|
|
|
|
|
mark.position = (*i).sample (sr);
|
|
|
|
|
marks.push_back (mark);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_1:
|
|
|
|
|
for (i = grid.begin(); i != grid.end(); ++i) {
|
|
|
|
|
BBT_Time bbt ((*i).bbt());
|
|
|
|
|
if (bbt.is_bar()) {
|
|
|
|
|
snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars);
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Major;
|
|
|
|
|
mark.label = buf;
|
|
|
|
|
mark.position = (*i).sample (sr);
|
|
|
|
|
marks.push_back (mark);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_quarters:
|
|
|
|
|
|
|
|
|
|
mark.label = "";
|
|
|
|
|
mark.position = leftmost;
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Micro;
|
|
|
|
|
marks.push_back (mark);
|
|
|
|
|
|
|
|
|
|
for (i = grid.begin(); i != grid.end(); ++i) {
|
|
|
|
|
|
|
|
|
|
BBT_Time bbt ((*i).bbt());
|
|
|
|
|
|
|
|
|
|
if ((*i).sample (sr) < leftmost && (bbt_bar_helper_on)) {
|
|
|
|
|
snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, bbt.bars, bbt.beats);
|
|
|
|
|
edit_last_mark_label (marks, buf);
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
if (bbt.is_bar()) {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Major;
|
|
|
|
|
snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars);
|
|
|
|
|
} else if ((bbt.beats % 2) == 1) {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Minor;
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
|
} else {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Micro;
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
mark.label = buf;
|
|
|
|
|
mark.position = (*i).sample (sr);
|
|
|
|
|
marks.push_back (mark);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case bbt_show_eighths:
|
|
|
|
|
case bbt_show_sixteenths:
|
|
|
|
|
case bbt_show_thirtyseconds:
|
|
|
|
|
case bbt_show_sixtyfourths:
|
|
|
|
|
case bbt_show_onetwentyeighths:
|
|
|
|
|
|
|
|
|
|
bbt_position_of_helper = leftmost + (3 * get_current_zoom ());
|
|
|
|
|
|
|
|
|
|
mark.label = "";
|
|
|
|
|
mark.position = leftmost;
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Micro;
|
|
|
|
|
marks.push_back (mark);
|
|
|
|
|
|
|
|
|
|
for (i = grid.begin(); i != grid.end(); ++i) {
|
|
|
|
|
|
|
|
|
|
BBT_Time bbt ((*i).bbt());
|
|
|
|
|
|
|
|
|
|
if ((*i).sample (sr) < leftmost && (bbt_bar_helper_on)) {
|
|
|
|
|
snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, bbt.bars, bbt.beats);
|
|
|
|
|
edit_last_mark_label (marks, buf);
|
|
|
|
|
helper_active = true;
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
if (bbt.is_bar()) {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Major;
|
|
|
|
|
snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars);
|
|
|
|
|
} else if (bbt.ticks == 0) {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Minor;
|
|
|
|
|
snprintf (buf, sizeof(buf), "%" PRIu32, bbt.beats);
|
|
|
|
|
} else {
|
|
|
|
|
mark.style = ArdourCanvas::Ruler::Mark::Micro;
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (((*i).sample(sr) < bbt_position_of_helper) && helper_active) {
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
mark.label = buf;
|
|
|
|
|
mark.position = (*i).sample (sr);
|
|
|
|
|
marks.push_back (mark);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-13 12:22:49 -07:00
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::mouse_mode_toggled (Editing::MouseMode m)
|
2024-02-13 12:22:49 -07:00
|
|
|
{
|
|
|
|
|
Glib::RefPtr<Gtk::Action> act = get_mouse_mode_action (m);
|
|
|
|
|
Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
|
|
|
|
|
|
|
|
|
|
if (!tact->get_active()) {
|
|
|
|
|
/* this was just the notification that the old mode has been
|
|
|
|
|
* left. we'll get called again with the new mode active in a
|
|
|
|
|
* jiffy.
|
|
|
|
|
*/
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mouse_mode = m;
|
|
|
|
|
|
|
|
|
|
/* this should generate a new enter event which will
|
|
|
|
|
trigger the appropriate cursor.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (_canvas) {
|
|
|
|
|
_canvas->re_enter ();
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-13 16:32:29 -07:00
|
|
|
|
|
|
|
|
int
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::set_state (XMLNode const & node, int version)
|
2024-02-13 16:32:29 -07:00
|
|
|
{
|
|
|
|
|
set_common_editing_state (node);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XMLNode&
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::get_state () const
|
2024-02-13 16:32:29 -07:00
|
|
|
{
|
2025-01-07 12:58:54 -07:00
|
|
|
XMLNode* node (new XMLNode (editor_name()));
|
2024-02-13 16:32:29 -07:00
|
|
|
get_common_editing_state (*node);
|
|
|
|
|
return *node;
|
|
|
|
|
}
|
2024-02-23 11:25:07 -07:00
|
|
|
|
|
|
|
|
/** @param allow_horiz true to allow horizontal autoscroll, otherwise false.
|
|
|
|
|
*
|
|
|
|
|
* @param allow_vert true to allow vertical autoscroll, otherwise false.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers)
|
2024-02-23 11:25:07 -07:00
|
|
|
{
|
|
|
|
|
if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* define a rectangular boundary for scrolling. If the mouse moves
|
|
|
|
|
* outside of this area and/or continue to be outside of this area,
|
|
|
|
|
* then we will continuously auto-scroll the canvas in the appropriate
|
|
|
|
|
* direction(s)
|
|
|
|
|
*
|
2024-02-27 12:21:27 -07:00
|
|
|
* the boundary is defined in coordinates relative to canvas' own
|
2024-02-23 11:25:07 -07:00
|
|
|
* window since that is what we're going to call ::get_pointer() on
|
|
|
|
|
* during autoscrolling to determine if we're still outside the
|
|
|
|
|
* boundary or not.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
ArdourCanvas::Rect scrolling_boundary;
|
|
|
|
|
Gtk::Allocation alloc;
|
|
|
|
|
|
|
|
|
|
alloc = get_canvas()->get_allocation ();
|
|
|
|
|
|
2024-02-27 12:21:27 -07:00
|
|
|
alloc.set_x (0);
|
|
|
|
|
alloc.set_y (0);
|
|
|
|
|
|
2024-02-23 11:25:07 -07:00
|
|
|
if (allow_vert) {
|
|
|
|
|
/* reduce height by the height of the timebars, which happens
|
2024-02-27 12:21:27 -07:00
|
|
|
to correspond to the position of the data_group.
|
2024-02-23 11:25:07 -07:00
|
|
|
*/
|
|
|
|
|
|
2024-02-27 12:21:27 -07:00
|
|
|
alloc.set_height (alloc.get_height() - data_group->position().y);
|
|
|
|
|
alloc.set_y (alloc.get_y() + data_group->position().y);
|
2024-02-23 11:25:07 -07:00
|
|
|
|
|
|
|
|
/* now reduce it again so that we start autoscrolling before we
|
|
|
|
|
* move off the top or bottom of the canvas
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
alloc.set_height (alloc.get_height() - 20);
|
|
|
|
|
alloc.set_y (alloc.get_y() + 10);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-27 12:21:27 -07:00
|
|
|
if (allow_horiz && (alloc.get_width() > 20)) {
|
2024-02-23 11:25:07 -07:00
|
|
|
|
2024-02-27 12:21:27 -07:00
|
|
|
if (prh) {
|
|
|
|
|
double w, h;
|
|
|
|
|
prh->size_request (w, h);
|
|
|
|
|
|
|
|
|
|
alloc.set_width (alloc.get_width() - w);
|
|
|
|
|
alloc.set_x (alloc.get_x() + w);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* the effective width of the autoscroll boundary so
|
|
|
|
|
that we start scrolling before we hit the edge.
|
|
|
|
|
|
|
|
|
|
this helps when the window is slammed up against the
|
|
|
|
|
right edge of the screen, making it hard to scroll
|
|
|
|
|
effectively.
|
|
|
|
|
*/
|
2024-02-23 11:25:07 -07:00
|
|
|
|
|
|
|
|
alloc.set_width (alloc.get_width() - 20);
|
|
|
|
|
alloc.set_x (alloc.get_x() + 10);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-27 12:21:27 -07:00
|
|
|
scrolling_boundary = ArdourCanvas::Rect (alloc.get_x(), alloc.get_y(), alloc.get_x() + alloc.get_width(), alloc.get_y() + alloc.get_height());
|
2024-02-23 11:25:07 -07:00
|
|
|
|
|
|
|
|
int x, y;
|
|
|
|
|
Gdk::ModifierType mask;
|
|
|
|
|
|
|
|
|
|
get_canvas()->get_window()->get_pointer (x, y, mask);
|
|
|
|
|
|
|
|
|
|
if ((allow_horiz && ((x < scrolling_boundary.x0 && _leftmost_sample > 0) || x >= scrolling_boundary.x1)) ||
|
|
|
|
|
(allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) {
|
|
|
|
|
start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::autoscroll_active () const
|
2024-02-23 11:25:07 -07:00
|
|
|
{
|
|
|
|
|
return autoscroll_connection.connected ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::autoscroll_canvas ()
|
2024-02-23 11:25:07 -07:00
|
|
|
{
|
|
|
|
|
using std::max;
|
|
|
|
|
using std::min;
|
|
|
|
|
int x, y;
|
|
|
|
|
Gdk::ModifierType mask;
|
|
|
|
|
sampleoffset_t dx = 0;
|
|
|
|
|
bool no_stop = false;
|
|
|
|
|
Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(_canvas_viewport->get_toplevel());
|
|
|
|
|
|
|
|
|
|
if (!toplevel) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get_canvas()->get_window()->get_pointer (x, y, mask);
|
|
|
|
|
|
|
|
|
|
VisualChange vc;
|
|
|
|
|
bool vertical_motion = false;
|
|
|
|
|
|
|
|
|
|
if (autoscroll_horizontal_allowed) {
|
|
|
|
|
|
|
|
|
|
samplepos_t new_sample = _leftmost_sample;
|
|
|
|
|
|
|
|
|
|
/* horizontal */
|
|
|
|
|
|
|
|
|
|
if (x > autoscroll_boundary.x1) {
|
|
|
|
|
|
|
|
|
|
/* bring it back into view */
|
|
|
|
|
dx = x - autoscroll_boundary.x1;
|
|
|
|
|
dx += 10 + (2 * (autoscroll_cnt/2));
|
|
|
|
|
|
|
|
|
|
dx = pixel_to_sample (dx);
|
|
|
|
|
|
|
|
|
|
dx *= UIConfiguration::instance().get_draggable_playhead_speed();
|
|
|
|
|
|
|
|
|
|
if (_leftmost_sample < max_samplepos - dx) {
|
|
|
|
|
new_sample = _leftmost_sample + dx;
|
|
|
|
|
} else {
|
|
|
|
|
new_sample = max_samplepos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
no_stop = true;
|
|
|
|
|
|
|
|
|
|
} else if (x < autoscroll_boundary.x0) {
|
|
|
|
|
|
|
|
|
|
dx = autoscroll_boundary.x0 - x;
|
|
|
|
|
dx += 10 + (2 * (autoscroll_cnt/2));
|
|
|
|
|
|
|
|
|
|
dx = pixel_to_sample (dx);
|
|
|
|
|
|
|
|
|
|
dx *= UIConfiguration::instance().get_draggable_playhead_speed();
|
|
|
|
|
|
|
|
|
|
if (_leftmost_sample >= dx) {
|
|
|
|
|
new_sample = _leftmost_sample - dx;
|
|
|
|
|
} else {
|
|
|
|
|
new_sample = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
no_stop = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (new_sample != _leftmost_sample) {
|
|
|
|
|
vc.time_origin = new_sample;
|
|
|
|
|
vc.add (VisualChange::TimeOrigin);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (autoscroll_vertical_allowed) {
|
|
|
|
|
|
|
|
|
|
// const double vertical_pos = vertical_adjustment.get_value();
|
|
|
|
|
const int speed_factor = 10;
|
|
|
|
|
|
|
|
|
|
/* vertical */
|
|
|
|
|
|
|
|
|
|
if (y < autoscroll_boundary.y0) {
|
|
|
|
|
|
|
|
|
|
/* scroll to make higher tracks visible */
|
|
|
|
|
|
|
|
|
|
if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
|
|
|
|
|
// XXX SCROLL UP
|
|
|
|
|
vertical_motion = true;
|
|
|
|
|
}
|
|
|
|
|
no_stop = true;
|
|
|
|
|
|
|
|
|
|
} else if (y > autoscroll_boundary.y1) {
|
|
|
|
|
|
|
|
|
|
if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
|
|
|
|
|
// XXX SCROLL DOWN
|
|
|
|
|
vertical_motion = true;
|
|
|
|
|
}
|
|
|
|
|
no_stop = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (vc.pending || vertical_motion) {
|
|
|
|
|
|
|
|
|
|
/* change horizontal first */
|
|
|
|
|
|
|
|
|
|
if (vc.pending) {
|
|
|
|
|
visual_changer (vc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* now send a motion event to notify anyone who cares
|
|
|
|
|
that we have moved to a new location (because we scrolled)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
GdkEventMotion ev;
|
|
|
|
|
|
|
|
|
|
ev.type = GDK_MOTION_NOTIFY;
|
|
|
|
|
ev.state = Gdk::BUTTON1_MASK;
|
|
|
|
|
|
|
|
|
|
/* the motion handler expects events in canvas coordinate space */
|
|
|
|
|
|
|
|
|
|
/* we asked for the mouse position above (::get_pointer()) via
|
|
|
|
|
* our own top level window (we being the Editor). Convert into
|
|
|
|
|
* coordinates within the canvas window.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int cx;
|
|
|
|
|
int cy;
|
|
|
|
|
|
|
|
|
|
//toplevel->translate_coordinates (*get_canvas(), x, y, cx,
|
|
|
|
|
//cy);
|
|
|
|
|
cx = x;
|
|
|
|
|
cy = y;
|
|
|
|
|
|
|
|
|
|
/* clamp x and y to remain within the autoscroll boundary,
|
|
|
|
|
* which is defined in window coordinates
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
|
|
|
|
|
y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
|
|
|
|
|
|
|
|
|
|
/* now convert from Editor window coordinates to canvas
|
|
|
|
|
* window coordinates
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
ArdourCanvas::Duple d = get_canvas()->window_to_canvas (ArdourCanvas::Duple (cx, cy));
|
|
|
|
|
ev.x = d.x;
|
|
|
|
|
ev.y = d.y;
|
|
|
|
|
ev.state = mask;
|
|
|
|
|
|
|
|
|
|
motion_handler (0, (GdkEvent*) &ev, true);
|
|
|
|
|
|
|
|
|
|
} else if (no_stop) {
|
|
|
|
|
|
|
|
|
|
/* not changing visual state but pointer is outside the scrolling boundary
|
|
|
|
|
* so we still need to deliver a fake motion event
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
GdkEventMotion ev;
|
|
|
|
|
|
|
|
|
|
ev.type = GDK_MOTION_NOTIFY;
|
|
|
|
|
ev.state = Gdk::BUTTON1_MASK;
|
|
|
|
|
|
|
|
|
|
/* the motion handler expects events in canvas coordinate space */
|
|
|
|
|
|
|
|
|
|
/* first convert from Editor window coordinates to canvas
|
|
|
|
|
* window coordinates
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int cx;
|
|
|
|
|
int cy;
|
|
|
|
|
|
|
|
|
|
/* clamp x and y to remain within the visible area. except
|
|
|
|
|
* .. if horizontal scrolling is allowed, always allow us to
|
|
|
|
|
* move back to zero
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (autoscroll_horizontal_allowed) {
|
|
|
|
|
x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
|
|
|
|
|
} else {
|
|
|
|
|
x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
|
|
|
|
|
}
|
|
|
|
|
y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
|
|
|
|
|
|
|
|
|
|
// toplevel->translate_coordinates (*get_canvas_viewport(), x,
|
|
|
|
|
// y, cx, cy);
|
|
|
|
|
cx = x;
|
|
|
|
|
cy = y;
|
|
|
|
|
|
|
|
|
|
ArdourCanvas::Duple d = get_canvas()->window_to_canvas (ArdourCanvas::Duple (cx, cy));
|
|
|
|
|
ev.x = d.x;
|
|
|
|
|
ev.y = d.y;
|
|
|
|
|
ev.state = mask;
|
|
|
|
|
|
|
|
|
|
motion_handler (0, (GdkEvent*) &ev, true);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
stop_canvas_autoscroll ();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
autoscroll_cnt++;
|
|
|
|
|
|
|
|
|
|
return true; /* call me again */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
|
2024-02-23 11:25:07 -07:00
|
|
|
{
|
|
|
|
|
if (!_session) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stop_canvas_autoscroll ();
|
|
|
|
|
|
|
|
|
|
autoscroll_horizontal_allowed = allow_horiz;
|
|
|
|
|
autoscroll_vertical_allowed = allow_vert;
|
|
|
|
|
autoscroll_boundary = boundary;
|
|
|
|
|
|
|
|
|
|
/* do the first scroll right now
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
autoscroll_canvas ();
|
|
|
|
|
|
|
|
|
|
/* scroll again at very very roughly 30FPS */
|
|
|
|
|
|
2025-01-07 11:30:26 -07:00
|
|
|
autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Pianoroll::autoscroll_canvas), 30);
|
2024-02-23 11:25:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::stop_canvas_autoscroll ()
|
2024-02-23 11:25:07 -07:00
|
|
|
{
|
|
|
|
|
autoscroll_connection.disconnect ();
|
|
|
|
|
autoscroll_cnt = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::visual_changer (const VisualChange& vc)
|
2024-02-23 11:25:07 -07:00
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Changed first so the correct horizontal canvas position is calculated in
|
|
|
|
|
* EditingContext::set_horizontal_position
|
|
|
|
|
*/
|
|
|
|
|
if (vc.pending & VisualChange::ZoomLevel) {
|
|
|
|
|
set_samples_per_pixel (vc.samples_per_pixel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (vc.pending & VisualChange::TimeOrigin) {
|
|
|
|
|
double new_time_origin = sample_to_pixel_unrounded (vc.time_origin);
|
|
|
|
|
set_horizontal_position (new_time_origin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (vc.pending & VisualChange::YOrigin) {
|
|
|
|
|
vertical_adjustment.set_value (vc.y_origin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Now the canvas is in the final state before render the canvas items that
|
|
|
|
|
* support the Item::prepare_for_render interface can calculate the correct
|
|
|
|
|
* item to visible canvas intersection.
|
|
|
|
|
*/
|
|
|
|
|
if (vc.pending & VisualChange::ZoomLevel) {
|
|
|
|
|
on_samples_per_pixel_changed ();
|
|
|
|
|
|
|
|
|
|
// update_tempo_based_rulers ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(vc.pending & VisualChange::ZoomLevel)) {
|
|
|
|
|
/* If the canvas is not being zoomed then the canvas items will not change
|
|
|
|
|
* and cause Item::prepare_for_render to be called so do it here manually.
|
|
|
|
|
* Not ideal, but I can't think of a better solution atm.
|
|
|
|
|
*/
|
|
|
|
|
get_canvas()->prepare_for_render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we are only scrolling vertically there is no need to update these */
|
|
|
|
|
if (vc.pending != VisualChange::YOrigin) {
|
|
|
|
|
// XXX update_fixed_rulers ();
|
2025-01-29 10:52:51 -07:00
|
|
|
redisplay_grid (true);
|
2024-02-23 11:25:07 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-06 23:46:03 -06:00
|
|
|
|
2024-06-07 22:54:07 -06:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::on_samples_per_pixel_changed ()
|
2024-06-07 22:54:07 -06:00
|
|
|
{
|
|
|
|
|
if (view) {
|
|
|
|
|
view->set_samples_per_pixel (samples_per_pixel);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-06 23:46:03 -06:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::midi_action (void (MidiView::*method)())
|
2024-06-06 23:46:03 -06:00
|
|
|
{
|
|
|
|
|
if (!view) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(view->*method) ();
|
|
|
|
|
}
|
2024-06-07 10:05:25 -06:00
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::escape ()
|
2024-06-07 10:05:25 -06:00
|
|
|
{
|
|
|
|
|
if (!view) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-15 18:09:18 -07:00
|
|
|
view->clear_selection ();
|
2024-06-07 10:05:25 -06:00
|
|
|
}
|
2025-01-15 18:09:18 -07:00
|
|
|
|
2024-09-20 19:44:25 -06:00
|
|
|
Gdk::Cursor*
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::which_track_cursor () const
|
2024-09-20 19:44:25 -06:00
|
|
|
{
|
|
|
|
|
return _cursors->grabber;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gdk::Cursor*
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::which_mode_cursor () const
|
2024-09-20 19:44:25 -06:00
|
|
|
{
|
|
|
|
|
Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor ();
|
|
|
|
|
|
|
|
|
|
switch (mouse_mode) {
|
|
|
|
|
case Editing::MouseContent:
|
2025-02-01 13:31:26 -07:00
|
|
|
mode_cursor = _cursors->grabber;
|
2024-09-20 19:44:25 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Editing::MouseDraw:
|
|
|
|
|
mode_cursor = _cursors->midi_pencil;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mode_cursor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Gdk::Cursor*
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::which_trim_cursor (bool left_side) const
|
2024-09-20 19:44:25 -06:00
|
|
|
{
|
|
|
|
|
abort ();
|
|
|
|
|
/*NOTREACHED*/
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Gdk::Cursor*
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::which_canvas_cursor (ItemType type) const
|
2024-09-20 19:44:25 -06:00
|
|
|
{
|
|
|
|
|
Gdk::Cursor* cursor = which_mode_cursor ();
|
|
|
|
|
|
|
|
|
|
if (mouse_mode == Editing::MouseContent) {
|
|
|
|
|
|
|
|
|
|
/* find correct cursor to use in object/smart mode */
|
|
|
|
|
switch (type) {
|
|
|
|
|
case AutomationTrackItem:
|
|
|
|
|
cursor = which_track_cursor ();
|
|
|
|
|
break;
|
|
|
|
|
case PlayheadCursorItem:
|
|
|
|
|
cursor = _cursors->grabber;
|
|
|
|
|
break;
|
|
|
|
|
case SelectionItem:
|
|
|
|
|
cursor = _cursors->selector;
|
|
|
|
|
break;
|
|
|
|
|
case ControlPointItem:
|
|
|
|
|
cursor = _cursors->fader;
|
|
|
|
|
break;
|
|
|
|
|
case GainLineItem:
|
|
|
|
|
cursor = _cursors->cross_hair;
|
|
|
|
|
break;
|
2024-09-20 20:00:46 -06:00
|
|
|
case EditorAutomationLineItem:
|
2024-09-20 19:44:25 -06:00
|
|
|
cursor = _cursors->cross_hair;
|
|
|
|
|
break;
|
|
|
|
|
case StartSelectionTrimItem:
|
|
|
|
|
cursor = _cursors->left_side_trim;
|
|
|
|
|
break;
|
|
|
|
|
case EndSelectionTrimItem:
|
|
|
|
|
cursor = _cursors->right_side_trim;
|
|
|
|
|
break;
|
|
|
|
|
case NoteItem:
|
|
|
|
|
cursor = _cursors->grabber_note;
|
2024-12-27 14:10:57 -07:00
|
|
|
break;
|
|
|
|
|
case RegionItem:
|
2025-01-08 14:23:04 -07:00
|
|
|
cursor = nullptr; /* default cursor */
|
2024-12-27 14:10:57 -07:00
|
|
|
break;
|
2025-02-01 13:31:26 -07:00
|
|
|
case VelocityItem:
|
|
|
|
|
cursor = _cursors->up_down;
|
|
|
|
|
break;
|
2024-12-27 14:10:57 -07:00
|
|
|
|
|
|
|
|
case ClipEndItem:
|
|
|
|
|
case ClipStartItem:
|
|
|
|
|
cursor = _cursors->expand_left_right;
|
|
|
|
|
break;
|
|
|
|
|
|
2024-09-20 19:44:25 -06:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if (mouse_mode == Editing::MouseDraw) {
|
|
|
|
|
|
|
|
|
|
/* ControlPointItem is not really specific to region gain mode
|
|
|
|
|
but it is the same cursor so don't worry about this for now.
|
|
|
|
|
The result is that we'll see the fader cursor if we enter
|
|
|
|
|
non-region-gain-line control points while in MouseDraw
|
|
|
|
|
mode, even though we can't edit them in this mode.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case ControlPointItem:
|
|
|
|
|
cursor = _cursors->fader;
|
|
|
|
|
break;
|
|
|
|
|
case NoteItem:
|
|
|
|
|
cursor = _cursors->grabber_note;
|
2024-12-27 14:10:57 -07:00
|
|
|
break;
|
|
|
|
|
case ClipEndItem:
|
|
|
|
|
case ClipStartItem:
|
|
|
|
|
cursor = _cursors->expand_left_right;
|
|
|
|
|
break;
|
|
|
|
|
case RegionItem:
|
|
|
|
|
cursor = _cursors->midi_pencil;
|
|
|
|
|
break;
|
2025-02-01 13:31:26 -07:00
|
|
|
case VelocityItem:
|
|
|
|
|
cursor = _cursors->up_down;
|
|
|
|
|
break;
|
2024-09-20 19:44:25 -06:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cursor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::enter_handler (ArdourCanvas::Item* item, GdkEvent* ev, ItemType item_type)
|
2024-09-20 19:44:25 -06:00
|
|
|
{
|
|
|
|
|
choose_canvas_cursor_on_entry (item_type);
|
|
|
|
|
|
|
|
|
|
switch (item_type) {
|
|
|
|
|
case AutomationTrackItem:
|
|
|
|
|
/* item is the base rectangle */
|
2024-11-08 12:15:51 -07:00
|
|
|
if (view) {
|
|
|
|
|
view->automation_entry ();
|
|
|
|
|
}
|
2024-09-20 19:44:25 -06:00
|
|
|
break;
|
|
|
|
|
|
2024-09-20 20:00:46 -06:00
|
|
|
case EditorAutomationLineItem:
|
2024-09-20 19:44:25 -06:00
|
|
|
{
|
|
|
|
|
ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
|
|
|
|
|
|
|
|
|
|
if (line) {
|
|
|
|
|
line->set_outline_color (UIConfiguration::instance().color ("entered automation line"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::leave_handler (ArdourCanvas::Item* item, GdkEvent* ev, ItemType item_type)
|
2024-09-20 19:44:25 -06:00
|
|
|
{
|
2024-09-20 20:00:46 -06:00
|
|
|
EditorAutomationLine* al;
|
2024-09-20 19:44:25 -06:00
|
|
|
|
2025-02-01 13:31:26 -07:00
|
|
|
set_canvas_cursor (which_mode_cursor());
|
|
|
|
|
|
2024-09-20 19:44:25 -06:00
|
|
|
switch (item_type) {
|
|
|
|
|
case ControlPointItem:
|
|
|
|
|
_verbose_cursor->hide ();
|
|
|
|
|
break;
|
|
|
|
|
|
2024-09-20 20:00:46 -06:00
|
|
|
case EditorAutomationLineItem:
|
|
|
|
|
al = reinterpret_cast<EditorAutomationLine*> (item->get_data ("line"));
|
2024-09-20 19:44:25 -06:00
|
|
|
{
|
|
|
|
|
ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
|
|
|
|
|
if (line) {
|
|
|
|
|
line->set_outline_color (al->get_line_color());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (ev->crossing.detail != GDK_NOTIFY_INFERIOR) {
|
2024-11-08 12:15:51 -07:00
|
|
|
view->automation_leave ();
|
2024-09-20 19:44:25 -06:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-10-07 13:20:11 -06:00
|
|
|
|
|
|
|
|
std::list<SelectableOwner*>
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::selectable_owners()
|
2024-10-07 13:20:11 -06:00
|
|
|
{
|
|
|
|
|
if (view) {
|
|
|
|
|
return view->selectable_owners();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::list<SelectableOwner*> ();
|
|
|
|
|
}
|
2024-10-22 14:41:40 -06:00
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::trigger_prop_change (PBD::PropertyChange const & what_changed)
|
2024-10-22 14:41:40 -06:00
|
|
|
{
|
|
|
|
|
if (what_changed.contains (Properties::region)) {
|
|
|
|
|
std::shared_ptr<MidiRegion> mr = std::dynamic_pointer_cast<MidiRegion> (ref.trigger()->the_region());
|
|
|
|
|
if (mr) {
|
|
|
|
|
set_region (mr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-27 20:21:23 -07:00
|
|
|
Pianoroll::blink_rec_enable (bool onoff)
|
2024-10-22 14:41:40 -06:00
|
|
|
{
|
2025-01-27 20:21:23 -07:00
|
|
|
if (onoff) {
|
|
|
|
|
rec_enable_button.set_active_state (Gtkmm2ext::ExplicitActive);
|
|
|
|
|
} else {
|
|
|
|
|
rec_enable_button.set_active_state (Gtkmm2ext::Off);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::rec_enable_change ()
|
|
|
|
|
{
|
|
|
|
|
if (!ref.box()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rec_blink_connection.disconnect ();
|
|
|
|
|
|
|
|
|
|
switch (ref.box()->record_enabled()) {
|
|
|
|
|
case Recording:
|
|
|
|
|
rec_enable_button.set_active_state (Gtkmm2ext::ExplicitActive);
|
|
|
|
|
break;
|
|
|
|
|
case Enabled:
|
|
|
|
|
rec_enable_button.set_active_state (Gtkmm2ext::ExplicitActive);
|
|
|
|
|
if (!UIConfiguration::instance().get_no_strobe() && ref.trigger()->armed()) {
|
|
|
|
|
rec_blink_connection = Timers::blink_connect (sigc::mem_fun (*this, &Pianoroll::blink_rec_enable));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Disabled:
|
|
|
|
|
rec_enable_button.set_active_state (Gtkmm2ext::Off);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-15 10:56:47 -06:00
|
|
|
bool
|
|
|
|
|
Pianoroll::play_button_press (GdkEventButton* ev)
|
|
|
|
|
{
|
|
|
|
|
_session->request_locate (view->midi_region()->position().samples());
|
|
|
|
|
_session->request_roll ();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
Pianoroll::loop_button_press (GdkEventButton* ev)
|
|
|
|
|
{
|
|
|
|
|
if (!view) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (!view->midi_region()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_session->get_play_loop()) {
|
|
|
|
|
_session->request_play_loop (false);
|
|
|
|
|
} else {
|
|
|
|
|
set_loop_range (view->midi_region()->position(), view->midi_region()->end(), _("loop region"));
|
|
|
|
|
_session->request_play_loop (true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
Pianoroll::solo_button_press (GdkEventButton* ev)
|
|
|
|
|
{
|
|
|
|
|
if (!view) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!view->midi_track()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view->midi_track()->solo_control()->set_value (!view->midi_track()->solo_control()->get_value(), Controllable::NoGroup);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-01-27 20:21:23 -07:00
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
Pianoroll::rec_button_press (GdkEventButton* ev)
|
|
|
|
|
{
|
|
|
|
|
std::cerr << "RBP!\n";
|
|
|
|
|
|
|
|
|
|
if (ev->button != 1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ref.box()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TriggerPtr trigger (ref.trigger());
|
|
|
|
|
bool trigger_armed = trigger->armed();
|
|
|
|
|
|
|
|
|
|
if (!trigger_armed) {
|
|
|
|
|
|
|
|
|
|
Stripable* st = dynamic_cast<Stripable*> (ref.box()->owner());
|
|
|
|
|
assert (st);
|
|
|
|
|
std::shared_ptr<Track> track = std::dynamic_pointer_cast<MidiTrack> (st->shared_from_this());
|
|
|
|
|
assert (track);
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<RouteList> rl;
|
|
|
|
|
|
|
|
|
|
rl.reset (new RouteList);
|
|
|
|
|
rl->push_back (track);
|
|
|
|
|
|
|
|
|
|
_session->set_controls (route_list_to_control_list (rl, &Stripable::rec_enable_control), true, Controllable::NoGroup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Step two: the trigger */
|
2025-01-27 15:15:51 -07:00
|
|
|
|
2025-01-27 20:21:23 -07:00
|
|
|
if (trigger_armed) {
|
|
|
|
|
trigger->disarm ();
|
|
|
|
|
} else {
|
|
|
|
|
trigger->arm ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::set (TriggerReference & tref)
|
|
|
|
|
{
|
2024-10-22 14:41:40 -06:00
|
|
|
_update_connection.disconnect ();
|
|
|
|
|
object_connections.drop_connections ();
|
|
|
|
|
|
|
|
|
|
ref = tref;
|
|
|
|
|
|
2025-01-27 20:21:23 -07:00
|
|
|
rec_box.show ();
|
|
|
|
|
rec_enable_button.set_sensitive (true);
|
|
|
|
|
ref.box()->RecEnableChanged.connect (object_connections, invalidator (*this), std::bind (&Pianoroll::rec_enable_change, this), gui_context());
|
|
|
|
|
|
2024-10-22 14:41:40 -06:00
|
|
|
idle_update_queued.store (0);
|
|
|
|
|
|
2025-01-07 11:30:26 -07:00
|
|
|
ref.box()->Captured.connect (object_connections, invalidator (*this), std::bind (&Pianoroll::data_captured, this, _1), gui_context());
|
2024-10-22 14:41:40 -06:00
|
|
|
/* Don't bind a shared_ptr<TriggerBox> within the lambda */
|
|
|
|
|
TriggerBox* tb (ref.box().get());
|
|
|
|
|
tb->RecEnableChanged.connect (object_connections, invalidator (*this), [&, tb]() { box_rec_enable_change (*tb); }, gui_context());
|
|
|
|
|
|
|
|
|
|
Stripable* st = dynamic_cast<Stripable*> (ref.box()->owner());
|
|
|
|
|
assert (st);
|
|
|
|
|
_track = std::dynamic_pointer_cast<MidiTrack> (st->shared_from_this());
|
|
|
|
|
assert (_track);
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
set_track (_track);
|
2024-10-22 14:41:40 -06:00
|
|
|
|
2025-01-07 11:30:26 -07:00
|
|
|
_track->DropReferences.connect (object_connections, invalidator (*this), std::bind (&Pianoroll::unset, this), gui_context());
|
|
|
|
|
ref.trigger()->PropertyChanged.connect (object_connections, invalidator (*this), std::bind (&Pianoroll::trigger_prop_change, this, _1), gui_context());
|
2024-10-22 14:41:40 -06:00
|
|
|
|
|
|
|
|
if (ref.trigger()->the_region()) {
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<MidiRegion> mr = std::dynamic_pointer_cast<MidiRegion> (ref.trigger()->the_region());
|
|
|
|
|
|
|
|
|
|
if (mr) {
|
|
|
|
|
set_region (mr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::unset ()
|
2024-10-22 14:41:40 -06:00
|
|
|
{
|
|
|
|
|
_update_connection.disconnect();
|
|
|
|
|
object_connections.drop_connections ();
|
|
|
|
|
_track.reset ();
|
|
|
|
|
view->set_region (nullptr);
|
|
|
|
|
ref = TriggerReference ();
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-20 18:05:19 -07:00
|
|
|
void
|
|
|
|
|
Pianoroll::set_track (std::shared_ptr<ARDOUR::MidiTrack> track)
|
|
|
|
|
{
|
|
|
|
|
if (view) {
|
|
|
|
|
view->set_track (track);
|
|
|
|
|
}
|
2025-02-07 23:09:51 -07:00
|
|
|
|
|
|
|
|
cc_dropdown1->menu().items().clear ();
|
|
|
|
|
cc_dropdown2->menu().items().clear ();
|
|
|
|
|
cc_dropdown3->menu().items().clear ();
|
|
|
|
|
|
|
|
|
|
build_controller_menu (cc_dropdown1->menu(), track->instrument_info(), 0xffff,
|
|
|
|
|
sigc::bind (sigc::mem_fun (*this, &Pianoroll::add_single_controller_item), cc_dropdown1),
|
|
|
|
|
sigc::bind (sigc::mem_fun (*this, &Pianoroll::add_multi_controller_item), cc_dropdown1));
|
|
|
|
|
build_controller_menu (cc_dropdown2->menu(), track->instrument_info(), 0xffff,
|
|
|
|
|
sigc::bind (sigc::mem_fun (*this, &Pianoroll::add_single_controller_item), cc_dropdown2),
|
|
|
|
|
sigc::bind (sigc::mem_fun (*this, &Pianoroll::add_multi_controller_item), cc_dropdown2));
|
|
|
|
|
build_controller_menu (cc_dropdown3->menu(), track->instrument_info(), 0xffff,
|
|
|
|
|
sigc::bind (sigc::mem_fun (*this, &Pianoroll::add_single_controller_item), cc_dropdown3),
|
|
|
|
|
sigc::bind (sigc::mem_fun (*this, &Pianoroll::add_multi_controller_item), cc_dropdown3));
|
|
|
|
|
|
2025-03-15 10:56:47 -06:00
|
|
|
track->solo_control()->Changed.connect (object_connections, invalidator (*this), std::bind (&Pianoroll::update_solo_display, this), gui_context());
|
|
|
|
|
update_solo_display ();
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
// reset_user_cc_choice (Evoral::Parameter (ARDOUR::MidiCCAutomation, _visible_channel, MIDI_CTL_MSB_GENERAL_PURPOSE1), cc_dropdown1);
|
|
|
|
|
// reset_user_cc_choice (Evoral::Parameter (ARDOUR::MidiCCAutomation, _visible_channel, MIDI_CTL_MSB_GENERAL_PURPOSE2), cc_dropdown2);
|
|
|
|
|
// reset_user_cc_choice (Evoral::Parameter (ARDOUR::MidiCCAutomation, _visible_channel, MIDI_CTL_MSB_GENERAL_PURPOSE3), cc_dropdown3);
|
2025-01-20 18:05:19 -07:00
|
|
|
}
|
|
|
|
|
|
2025-03-15 10:56:47 -06:00
|
|
|
void
|
|
|
|
|
Pianoroll::update_solo_display ()
|
|
|
|
|
{
|
|
|
|
|
if (view->midi_track()->solo_control()->get_value()) {
|
|
|
|
|
solo_button.set_active_state (Gtkmm2ext::ExplicitActive);
|
|
|
|
|
} else {
|
|
|
|
|
solo_button.set_active_state (Gtkmm2ext::Off);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-22 14:41:40 -06:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::set_region (std::shared_ptr<ARDOUR::MidiRegion> r)
|
2024-10-22 14:41:40 -06:00
|
|
|
{
|
|
|
|
|
if (!r) {
|
2025-03-14 17:25:05 -06:00
|
|
|
unset ();
|
2024-10-22 14:41:40 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view->set_region (r);
|
2024-11-12 18:54:41 -07:00
|
|
|
view->show_start (true);
|
|
|
|
|
view->show_end (true);
|
2024-10-22 14:41:40 -06:00
|
|
|
|
2025-03-03 18:25:21 +01:00
|
|
|
r->DropReferences.connect (object_connections, invalidator (*this), std::bind (&Pianoroll::unset, this), gui_context());
|
|
|
|
|
|
2024-10-22 14:41:40 -06:00
|
|
|
bool provided = false;
|
|
|
|
|
std::shared_ptr<Temporal::TempoMap> map;
|
|
|
|
|
std::shared_ptr<SMFSource> smf (std::dynamic_pointer_cast<SMFSource> (r->midi_source()));
|
|
|
|
|
|
|
|
|
|
if (smf) {
|
|
|
|
|
map = smf->tempo_map (provided);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!provided) {
|
2025-01-20 18:05:19 -07:00
|
|
|
/* COPY MAIN SESSION TEMPO MAP? */
|
2024-10-22 14:41:40 -06:00
|
|
|
map.reset (new Temporal::TempoMap (Temporal::Tempo (120, 4), Temporal::Meter (4, 4)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
EditingContext::TempoMapScope tms (*this, map);
|
2025-01-20 18:05:19 -07:00
|
|
|
/* Compute zoom level to show entire source plus some margin if possible */
|
2025-01-26 15:50:42 -07:00
|
|
|
zoom_to_show (timecnt_t (timepos_t (max_extents_scale() * max_zoom_extent ().second.samples())));
|
2025-01-20 18:05:19 -07:00
|
|
|
|
2024-10-22 14:41:40 -06:00
|
|
|
}
|
2025-03-14 17:25:05 -06:00
|
|
|
|
|
|
|
|
_update_connection = Timers::rapid_connect (sigc::mem_fun (*this, &Pianoroll::maybe_update));
|
2024-10-22 14:41:40 -06:00
|
|
|
}
|
2024-11-12 08:12:16 -07:00
|
|
|
|
2025-01-20 18:05:19 -07:00
|
|
|
void
|
|
|
|
|
Pianoroll::zoom_to_show (Temporal::timecnt_t const & duration)
|
|
|
|
|
{
|
|
|
|
|
if (!_visible_canvas_width) {
|
2025-01-27 09:50:28 -07:00
|
|
|
zoom_in_allocate = true;
|
2025-01-20 18:05:19 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* make it 20% wider than we need */
|
2025-01-26 15:50:42 -07:00
|
|
|
samplecnt_t samples = duration.samples();
|
2025-01-26 15:49:41 -07:00
|
|
|
samplecnt_t spp = floor (samples / _track_canvas_width);
|
2025-01-26 15:50:42 -07:00
|
|
|
|
2025-01-20 18:05:19 -07:00
|
|
|
reset_zoom (spp);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
bool
|
|
|
|
|
Pianoroll::user_automation_button_event (GdkEventButton* ev, MetaButton* mb)
|
|
|
|
|
{
|
|
|
|
|
if (mb->is_menu_popup_event (ev)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-08 09:30:59 -07:00
|
|
|
if (mb->is_led_click (ev)) {
|
|
|
|
|
user_led_click (ev, mb);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
ParameterButtonMap::iterator i = parameter_button_map.find (mb);
|
|
|
|
|
if (i == parameter_button_map.end()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return automation_button_event (ev, i->second.type(), i->second.id());
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-12 08:12:16 -07:00
|
|
|
bool
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::automation_button_event (GdkEventButton* ev, Evoral::ParameterType type, int id)
|
2024-11-12 08:12:16 -07:00
|
|
|
{
|
2024-12-20 10:08:20 -07:00
|
|
|
if (ev->button != 1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-12 08:12:16 -07:00
|
|
|
SelectionOperation op = ArdourKeyboard::selection_type (ev->state);
|
|
|
|
|
|
|
|
|
|
switch (ev->type) {
|
|
|
|
|
case GDK_BUTTON_RELEASE:
|
2024-12-20 10:08:20 -07:00
|
|
|
if (view) {
|
2024-12-20 10:44:35 -07:00
|
|
|
Evoral::Parameter param (type, _visible_channel, id);
|
2024-12-20 10:08:20 -07:00
|
|
|
|
|
|
|
|
if (view->is_visible_automation (param) && (op == SelectionSet)) {
|
|
|
|
|
op = SelectionToggle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view->update_automation_display (param, op);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2024-11-12 08:12:16 -07:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
void
|
|
|
|
|
Pianoroll::user_led_click (GdkEventButton* ev, MetaButton* metabutton)
|
|
|
|
|
{
|
|
|
|
|
if (ev->button != 1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Find the parameter */
|
|
|
|
|
|
|
|
|
|
ParameterButtonMap::iterator i;
|
|
|
|
|
for (i = parameter_button_map.begin(); i != parameter_button_map.end(); ++i) {
|
|
|
|
|
if (i->first == metabutton) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i == parameter_button_map.end()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (view) {
|
|
|
|
|
view->set_active_automation (i->second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-12 08:12:16 -07:00
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::automation_led_click (GdkEventButton* ev, Evoral::ParameterType type, int id)
|
2024-11-12 08:12:16 -07:00
|
|
|
{
|
2024-12-20 10:08:20 -07:00
|
|
|
if (ev->button != 1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-12 08:12:16 -07:00
|
|
|
if (view) {
|
2024-12-20 10:44:35 -07:00
|
|
|
view->set_active_automation (Evoral::Parameter (type, _visible_channel, id));
|
2024-12-20 10:08:20 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::automation_state_changed ()
|
2024-12-20 10:08:20 -07:00
|
|
|
{
|
|
|
|
|
assert (view);
|
|
|
|
|
|
|
|
|
|
for (ParameterButtonMap::iterator i = parameter_button_map.begin(); i != parameter_button_map.end(); ++i) {
|
2025-02-07 23:09:51 -07:00
|
|
|
std::string str (ARDOUR::EventTypeMap::instance().to_symbol (i->second));
|
2024-12-20 10:08:20 -07:00
|
|
|
|
|
|
|
|
/* Indicate visible automation state with selected/not-selected visual state */
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
if (view->is_visible_automation (i->second)) {
|
|
|
|
|
i->first->set_visual_state (Gtkmm2ext::Selected);
|
2024-12-20 10:08:20 -07:00
|
|
|
} else {
|
2025-02-07 23:09:51 -07:00
|
|
|
i->first->set_visual_state (Gtkmm2ext::NoVisualState);
|
2024-12-20 10:08:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Indicate active automation state with explicit widget active state (LED) */
|
|
|
|
|
|
2025-02-07 23:09:51 -07:00
|
|
|
if (view->is_active_automation (i->second)) {
|
|
|
|
|
i->first->set_active_state (Gtkmm2ext::ExplicitActive);
|
2024-12-20 10:08:20 -07:00
|
|
|
} else {
|
2025-02-07 23:09:51 -07:00
|
|
|
i->first->set_active_state (Gtkmm2ext::Off);
|
2024-12-20 10:08:20 -07:00
|
|
|
}
|
2024-11-12 08:12:16 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-12-13 21:48:18 -07:00
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::note_mode_clicked ()
|
2024-12-13 21:48:18 -07:00
|
|
|
{
|
|
|
|
|
assert (bg);
|
|
|
|
|
|
|
|
|
|
if (bg->note_mode() == Sustained) {
|
|
|
|
|
set_note_mode (Percussive);
|
|
|
|
|
} else {
|
|
|
|
|
set_note_mode (Sustained);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::set_note_mode (NoteMode nm)
|
2024-12-13 21:48:18 -07:00
|
|
|
{
|
|
|
|
|
assert (bg);
|
|
|
|
|
|
|
|
|
|
if (nm != bg->note_mode()) {
|
|
|
|
|
bg->set_note_mode (nm);
|
|
|
|
|
if (bg->note_mode() == Percussive) {
|
|
|
|
|
note_mode_button.set_active (true);
|
|
|
|
|
} else {
|
|
|
|
|
note_mode_button.set_active (false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-12-29 11:24:10 -07:00
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::build_zoom_focus_menu ()
|
2024-12-29 11:24:10 -07:00
|
|
|
{
|
|
|
|
|
using namespace Gtk::Menu_Helpers;
|
|
|
|
|
using namespace Editing;
|
|
|
|
|
|
|
|
|
|
zoom_focus_selector.AddMenuElem (MenuElem (zoom_focus_strings[(int)ZoomFocusLeft], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusLeft)));
|
|
|
|
|
zoom_focus_selector.AddMenuElem (MenuElem (zoom_focus_strings[(int)ZoomFocusRight], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusRight)));
|
|
|
|
|
zoom_focus_selector.AddMenuElem (MenuElem (zoom_focus_strings[(int)ZoomFocusCenter], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusCenter)));
|
|
|
|
|
zoom_focus_selector.AddMenuElem (MenuElem (zoom_focus_strings[(int)ZoomFocusMouse], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusMouse)));
|
|
|
|
|
zoom_focus_selector.set_sizing_texts (zoom_focus_strings);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-30 21:48:04 -07:00
|
|
|
|
|
|
|
|
std::pair<Temporal::timepos_t,Temporal::timepos_t>
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::max_zoom_extent() const
|
2024-12-30 21:48:04 -07:00
|
|
|
{
|
|
|
|
|
if (view && view->midi_region()) {
|
2025-01-20 18:05:19 -07:00
|
|
|
/* XXX make this dependent on view _show_source setting */
|
2025-03-06 17:09:47 -07:00
|
|
|
|
|
|
|
|
Temporal::Beats slen = view->midi_region()->midi_source()->length().beats();
|
|
|
|
|
|
|
|
|
|
if (slen != Temporal::Beats()) {
|
|
|
|
|
return std::make_pair (Temporal::timepos_t (Temporal::Beats()), Temporal::timepos_t (slen));
|
|
|
|
|
}
|
2024-12-30 21:48:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return std::make_pair (Temporal::timepos_t (Temporal::Beats()), Temporal::timepos_t (Temporal::Beats (32, 0)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2025-01-07 11:30:26 -07:00
|
|
|
Pianoroll::full_zoom_clicked()
|
2024-12-30 21:48:04 -07:00
|
|
|
{
|
|
|
|
|
/* XXXX NEED LOCAL TEMPO MAP */
|
|
|
|
|
|
|
|
|
|
std::pair<Temporal::timepos_t,Temporal::timepos_t> dur (max_zoom_extent());
|
|
|
|
|
samplecnt_t s = dur.second.samples() - dur.first.samples();
|
|
|
|
|
reposition_and_zoom (0, (s / (double) _visible_canvas_width));
|
|
|
|
|
}
|
2025-01-14 15:28:51 -07:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::point_selection_changed ()
|
|
|
|
|
{
|
|
|
|
|
if (view) {
|
|
|
|
|
view->point_selection_changed ();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-14 15:31:53 -07:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::delete_ ()
|
|
|
|
|
{
|
|
|
|
|
/* Editor has a lot to do here, potentially. But we don't */
|
|
|
|
|
cut_copy (Editing::Delete);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::paste (float times, bool from_context_menu)
|
|
|
|
|
{
|
|
|
|
|
if (view) {
|
|
|
|
|
// view->paste (Editing::Cut);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::keyboard_paste ()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Cut, copy or clear selected regions, automation points or a time range.
|
|
|
|
|
* @param op Operation (Delete, Cut, Copy or Clear)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::cut_copy (Editing::CutCopyOp op)
|
|
|
|
|
{
|
|
|
|
|
using namespace Editing;
|
|
|
|
|
|
|
|
|
|
/* only cancel selection if cut/copy is successful.*/
|
|
|
|
|
|
|
|
|
|
std::string opname;
|
|
|
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
|
case Delete:
|
|
|
|
|
opname = _("delete");
|
|
|
|
|
break;
|
|
|
|
|
case Cut:
|
|
|
|
|
opname = _("cut");
|
|
|
|
|
break;
|
|
|
|
|
case Copy:
|
|
|
|
|
opname = _("copy");
|
|
|
|
|
break;
|
|
|
|
|
case Clear:
|
|
|
|
|
opname = _("clear");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* if we're deleting something, and the mouse is still pressed,
|
|
|
|
|
the thing we started a drag for will be gone when we release
|
|
|
|
|
the mouse button(s). avoid this. see part 2 at the end of
|
|
|
|
|
this function.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (op == Delete || op == Cut || op == Clear) {
|
|
|
|
|
if (_drags->active ()) {
|
|
|
|
|
_drags->abort ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (op != Delete) { //"Delete" doesn't change copy/paste buf
|
|
|
|
|
cut_buffer->clear ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (mouse_mode) {
|
|
|
|
|
case MouseDraw:
|
|
|
|
|
case MouseContent:
|
|
|
|
|
if (view) {
|
|
|
|
|
begin_reversible_command (opname + ' ' + X_("MIDI"));
|
|
|
|
|
view->cut_copy_clear (op);
|
|
|
|
|
commit_reversible_command ();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (op == Delete || op == Cut || op == Clear) {
|
|
|
|
|
_drags->abort ();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-19 15:04:51 -07:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Pianoroll::select_all_within (Temporal::timepos_t const & start, Temporal::timepos_t const & end, double y0, double y1, std::list<SelectableOwner*> const & ignored, ARDOUR::SelectionOperation op, bool preserve_if_selected)
|
|
|
|
|
{
|
|
|
|
|
std::list<Selectable*> found;
|
|
|
|
|
|
|
|
|
|
if (!view) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AutomationLine* al = view->active_automation_line();
|
|
|
|
|
|
|
|
|
|
if (!al) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double topfrac;
|
|
|
|
|
double botfrac;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* translate y0 and y1 to use the top of the automation area as the * origin */
|
|
|
|
|
|
|
|
|
|
double automation_origin = view->automation_group_position().y;
|
|
|
|
|
|
|
|
|
|
y0 -= automation_origin;
|
|
|
|
|
y1 -= automation_origin;
|
|
|
|
|
|
|
|
|
|
if (y0 < 0. && al->height() <= y1) {
|
|
|
|
|
|
|
|
|
|
/* _y_position is below top, mybot is above bot, so we're fully
|
|
|
|
|
covered vertically.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
topfrac = 1.0;
|
|
|
|
|
botfrac = 0.0;
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
/* top and bot are within _y_position .. mybot */
|
|
|
|
|
|
|
|
|
|
topfrac = 1.0 - (y0 / al->height());
|
|
|
|
|
botfrac = 1.0 - (y1 / al->height());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
al->get_selectables (start, end, botfrac, topfrac, found);
|
|
|
|
|
|
|
|
|
|
if (found.empty()) {
|
|
|
|
|
view->clear_selection ();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (preserve_if_selected && op != SelectionToggle) {
|
|
|
|
|
auto i = found.begin();
|
|
|
|
|
while (i != found.end() && (*i)->selected()) {
|
|
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i == found.end()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
|
case SelectionAdd:
|
|
|
|
|
begin_reversible_selection_op (X_("add select all within"));
|
|
|
|
|
selection->add (found);
|
|
|
|
|
break;
|
|
|
|
|
case SelectionToggle:
|
|
|
|
|
begin_reversible_selection_op (X_("toggle select all within"));
|
|
|
|
|
selection->toggle (found);
|
|
|
|
|
break;
|
|
|
|
|
case SelectionSet:
|
|
|
|
|
begin_reversible_selection_op (X_("select all within"));
|
|
|
|
|
selection->set (found);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commit_reversible_selection_op ();
|
|
|
|
|
}
|