Lifetime management of Triggers is unlike anything(?) we've dealt with in Ardour so far. The parent object (Triggerbox) has a normal lifetime pattern, but Triggers can come and go in a way that few other objects do (although Processors and particularly PluginInsert are somewhat similar). We do not want the GUI to hold references to the actual Triggers, because the end of life of a Trigger is not really a signal for the GUI element to go away (the Trigger will be replaced in the slot). Consequently, we do not want TriggerPtr used as a member variable anywhere in the UI. Instead we use a TriggerReference which can "lookup" a Trigger on-demand (by box and slot number). The (G)UI now uses these exclusively. Work still needed to pick up trigger swap signals from the boxen.
1194 lines
38 KiB
C++
1194 lines
38 KiB
C++
/*
|
|
* Copyright (C) 2021 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.
|
|
*/
|
|
|
|
#include <vector>
|
|
|
|
#include <gtkmm/filechooserdialog.h>
|
|
#include <gtkmm/menu.h>
|
|
#include <gtkmm/menuitem.h>
|
|
#include <gtkmm/stock.h>
|
|
|
|
#include "pbd/compose.h"
|
|
#include "pbd/convert.h"
|
|
#include "pbd/unwind.h"
|
|
|
|
#include "ardour/region.h"
|
|
#include "ardour/triggerbox.h"
|
|
|
|
#include "canvas/polygon.h"
|
|
#include "canvas/text.h"
|
|
|
|
#include "gtkmm2ext/actions.h"
|
|
#include "gtkmm2ext/colors.h"
|
|
#include "gtkmm2ext/utils.h"
|
|
|
|
#include "ardour_ui.h"
|
|
#include "gui_thread.h"
|
|
#include "public_editor.h"
|
|
#include "region_view.h"
|
|
#include "selection.h"
|
|
#include "timers.h"
|
|
#include "trigger_ui.h"
|
|
#include "triggerbox_ui.h"
|
|
#include "ui_config.h"
|
|
#include "utils.h"
|
|
|
|
#include "pbd/i18n.h"
|
|
|
|
using namespace ARDOUR;
|
|
using namespace ArdourCanvas;
|
|
using namespace Gtkmm2ext;
|
|
using namespace PBD;
|
|
|
|
TriggerEntry::TriggerEntry (Item* item, TriggerReference tr)
|
|
: ArdourCanvas::Rectangle (item)
|
|
, tref (tr)
|
|
{
|
|
set_layout_sensitive (true); // why???
|
|
|
|
name = string_compose ("trigger %1", tref.slot);
|
|
|
|
set_outline (false);
|
|
|
|
play_button = new ArdourCanvas::Rectangle (this);
|
|
play_button->set_outline (false);
|
|
play_button->set_fill (true);
|
|
play_button->name = string_compose ("playbutton %1", tref.slot);
|
|
play_button->show ();
|
|
|
|
follow_button = new ArdourCanvas::Rectangle (this);
|
|
follow_button->set_outline (false);
|
|
follow_button->set_fill (true);
|
|
follow_button->name = ("slot_selector_button");
|
|
follow_button->show ();
|
|
|
|
name_button = new ArdourCanvas::Rectangle (this);
|
|
name_button->set_outline (true);
|
|
name_button->set_fill (true);
|
|
name_button->name = ("slot_selector_button");
|
|
name_button->show ();
|
|
|
|
name_text = new Text (name_button);
|
|
name_text->set_ignore_events (false);
|
|
name_text->show ();
|
|
|
|
/* watch for change in theme */
|
|
UIConfiguration::instance ().ParameterChanged.connect (sigc::mem_fun (*this, &TriggerEntry::ui_parameter_changed));
|
|
set_default_colors ();
|
|
|
|
trigger()->PropertyChanged.connect (trigger_prop_connection, MISSING_INVALIDATOR, boost::bind (&TriggerEntry::prop_change, this, _1), gui_context ());
|
|
tref.box->TriggerSwapped.connect (trigger_swap_connection, MISSING_INVALIDATOR, boost::bind (&TriggerEntry::trigger_swap, this, _1), gui_context ());
|
|
dynamic_cast<Stripable*> (tref.box->owner ())->presentation_info ().Change.connect (owner_prop_connection, MISSING_INVALIDATOR, boost::bind (&TriggerEntry::owner_prop_change, this, _1), gui_context ());
|
|
|
|
PropertyChange changed;
|
|
changed.add (ARDOUR::Properties::name);
|
|
changed.add (ARDOUR::Properties::running);
|
|
prop_change (changed);
|
|
|
|
selection_change ();
|
|
}
|
|
|
|
TriggerEntry::~TriggerEntry ()
|
|
{
|
|
}
|
|
|
|
void
|
|
TriggerEntry::trigger_swap (uint32_t n)
|
|
{
|
|
if (n != tref.slot) {
|
|
/* some other slot in the same box got swapped. we don't care */
|
|
return;
|
|
}
|
|
trigger_prop_connection.disconnect ();
|
|
trigger()->PropertyChanged.connect (trigger_prop_connection, MISSING_INVALIDATOR, boost::bind (&TriggerEntry::prop_change, this, _1), gui_context ());
|
|
prop_change (Properties::name);
|
|
}
|
|
|
|
void
|
|
TriggerEntry::owner_prop_change (PropertyChange const& pc)
|
|
{
|
|
if (pc.contains (Properties::color)) {
|
|
owner_color_changed ();
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerEntry::owner_color_changed ()
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void
|
|
TriggerEntry::selection_change ()
|
|
{
|
|
if (PublicEditor::instance ().get_selection ().selected (this)) {
|
|
name_button->set_outline_color (UIConfiguration::instance ().color ("alert:red"));
|
|
} else {
|
|
set_default_colors ();
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerEntry::maybe_update ()
|
|
{
|
|
// what here?
|
|
}
|
|
|
|
void
|
|
TriggerEntry::_size_allocate (ArdourCanvas::Rect const& alloc)
|
|
{
|
|
Rectangle::_size_allocate (alloc);
|
|
|
|
const Distance width = _rect.width ();
|
|
const Distance height = _rect.height ();
|
|
|
|
play_button->set (ArdourCanvas::Rect (0, 0, height, height));
|
|
name_button->set (ArdourCanvas::Rect (height, 0, width-height, height));
|
|
follow_button->set (ArdourCanvas::Rect (width-height, 0, width, height));
|
|
|
|
const double scale = UIConfiguration::instance ().get_ui_scale ();
|
|
_poly_margin = 2. * scale;
|
|
_poly_size = height - 2 * _poly_margin;
|
|
|
|
float tleft = height; // make room for the play button
|
|
|
|
name_text->size_allocate (ArdourCanvas::Rect (0, 0, width, height));
|
|
name_text->set_position (Duple (tleft + _poly_margin, _poly_margin - 0.5));
|
|
name_text->clamp_width (width - height - height);
|
|
|
|
/* font scale may have changed. uiconfig 'embeds' the ui-scale in the font */
|
|
name_text->set_font_description (UIConfiguration::instance ().get_NormalFont ());
|
|
}
|
|
|
|
void
|
|
TriggerEntry::draw_follow_icon (Cairo::RefPtr<Cairo::Context> context, Trigger::FollowAction icon, float size, float scale) const
|
|
{
|
|
context->set_line_width (1 * scale);
|
|
|
|
switch (icon) {
|
|
case Trigger::Stop:
|
|
context->rectangle (6 * scale, 6 * scale, size - 12 * scale, size - 12 * scale);
|
|
context->stroke ();
|
|
break;
|
|
case Trigger::Again:
|
|
context->arc (size / 2, size / 2, size * 0.20, 60. * (M_PI / 180.0), 2 * M_PI);
|
|
context->stroke ();
|
|
context->arc (size / 2 + size * 0.2, size / 2, 1.5 * scale, 0, 2 * M_PI); // arrow head
|
|
context->fill ();
|
|
break;
|
|
case Trigger::NextTrigger:
|
|
context->move_to (size / 2, 3 * scale);
|
|
context->line_to (size / 2, size - 5 * scale);
|
|
context->stroke ();
|
|
context->arc (size / 2, size - 5 * scale, 1.5 * scale, 0, 2 * M_PI); // arrow head
|
|
context->fill ();
|
|
break;
|
|
case Trigger::PrevTrigger:
|
|
context->move_to (size / 2, 5 * scale);
|
|
context->line_to (size / 2, size - 3 * scale);
|
|
context->stroke ();
|
|
context->arc (size / 2, 5 * scale, 1.5 * scale, 0, 2 * M_PI); // arrow head
|
|
context->fill ();
|
|
break;
|
|
case Trigger::QueuedTrigger: {
|
|
Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
|
|
layout->set_font_description (UIConfiguration::instance ().get_SmallMonospaceFont ());
|
|
layout->set_text (icon == Trigger::AnyTrigger ? "&" : "@");
|
|
int tw, th;
|
|
layout->get_pixel_size (tw, th);
|
|
context->move_to (size / 2, size / 2);
|
|
context->rel_move_to (-tw / 2, -th / 2);
|
|
layout->show_in_cairo_context (context);
|
|
} break;
|
|
case Trigger::AnyTrigger: {
|
|
context->move_to (size / 2, 3 * scale);
|
|
context->line_to (size / 2, size - 3 * scale);
|
|
context->move_to (size / 2, size / 2);
|
|
context->line_to (size / 2 - 3 * scale, size / 2);
|
|
context->stroke ();
|
|
context->arc (size / 2, 4 * scale, 1.5 * scale, 0, 2 * M_PI); // arrow head
|
|
context->fill ();
|
|
context->arc (size / 2, size - 3 * scale, 1.5 * scale, 0, 2 * M_PI); // arrow head
|
|
context->fill ();
|
|
context->arc (size / 2 - 3 * scale, size / 2, 1.5 * scale, 0, 2 * M_PI); // arrow head
|
|
context->fill ();
|
|
} break;
|
|
case Trigger::OtherTrigger: {
|
|
context->move_to (size / 2, 3 * scale);
|
|
context->line_to (size / 2, 7 * scale);
|
|
context->move_to (size / 2, size - 7 * scale);
|
|
context->line_to (size / 2, size - 3 * scale);
|
|
context->stroke ();
|
|
context->arc (size / 2, 3 * scale, 1.5 * scale, 0, 2 * M_PI); // arrow head
|
|
context->fill ();
|
|
context->arc (size / 2, size - 3 * scale, 1.5 * scale, 0, 2 * M_PI); // arrow head
|
|
context->fill ();
|
|
} break;
|
|
case Trigger::None:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
TriggerEntry::draw_launch_icon (Cairo::RefPtr<Cairo::Context> context, float sz, float scale) const
|
|
{
|
|
context->set_line_width (1 * scale);
|
|
|
|
float margin = 4*scale;
|
|
float size = sz - 2*margin;
|
|
|
|
if (trigger()->active()) {
|
|
if (trigger()->launch_style()==Trigger::Toggle) {
|
|
//clicking again will Stop this clip
|
|
set_source_rgba (context, UIConfiguration::instance ().color ("neutral:foreground"));
|
|
context->move_to (margin, margin);
|
|
context->rel_line_to (size, 0);
|
|
context->rel_line_to (0, size);
|
|
context->rel_line_to (-size, 0);
|
|
context->rel_line_to (0, -size);
|
|
context->fill ();
|
|
return; //done
|
|
} else {
|
|
//actively playing; draw a filled play triangle
|
|
set_source_rgba (context, UIConfiguration::instance ().color ("neutral:foreground"));
|
|
context->move_to (margin, margin);
|
|
context->rel_line_to (0, size);
|
|
context->rel_line_to (size, -size/2);
|
|
context->fill ();
|
|
return; //done
|
|
}
|
|
}
|
|
|
|
set_source_rgba (context, UIConfiguration::instance ().color ("neutral:midground"));
|
|
|
|
if (!trigger()->region ()) {
|
|
//no content in this slot, it is only a Stop button
|
|
context->move_to (margin, margin);
|
|
context->rel_line_to (size, 0);
|
|
context->rel_line_to (0, size);
|
|
context->rel_line_to (-size, 0);
|
|
context->rel_line_to (0, -size);
|
|
context->stroke ();
|
|
return; //done
|
|
}
|
|
|
|
switch (trigger()->launch_style()) {
|
|
case Trigger::Toggle:
|
|
case Trigger::OneShot:
|
|
context->move_to (margin, margin);
|
|
context->rel_line_to (0, size);
|
|
context->rel_line_to (size, -size/2);
|
|
context->line_to (margin, margin);
|
|
context->stroke ();
|
|
break;
|
|
case Trigger::Gate: //diamond shape
|
|
context->move_to ( margin+size/2, margin );
|
|
context->rel_line_to ( size/2, size/2);
|
|
context->rel_line_to ( -size/2, size/2);
|
|
context->rel_line_to ( -size/2, -size/2);
|
|
context->rel_line_to ( size/2, -size/2);
|
|
context->stroke ();
|
|
break;
|
|
case Trigger::Repeat: //'stutter' shape
|
|
set_source_rgba (context, HSV (UIConfiguration::instance ().color ("neutral:midground")).lighter (0.25).color ()); //stutter shape needs to be brighter to maintain balance
|
|
context->set_line_width (1 * scale);
|
|
context->move_to ( margin, margin );
|
|
context->rel_line_to ( 0, size);
|
|
|
|
context->move_to ( margin + scale*3, margin + scale*2 );
|
|
context->rel_line_to ( 0, size - scale*4);
|
|
|
|
context->move_to ( margin + scale*6, margin + scale*3 );
|
|
context->rel_line_to ( 0, size - scale*6);
|
|
|
|
context->stroke ();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
context->set_line_width (1);
|
|
}
|
|
|
|
void
|
|
TriggerEntry::render (ArdourCanvas::Rect const& area, Cairo::RefPtr<Cairo::Context> context) const
|
|
{
|
|
Rectangle::render (area, context);
|
|
|
|
/* Note that item_to_window() already takes _position into account (as
|
|
part of item_to_canvas()
|
|
*/
|
|
ArdourCanvas::Rect self (item_to_window (_rect));
|
|
const ArdourCanvas::Rect draw = self.intersection (area);
|
|
|
|
if (!draw) {
|
|
return;
|
|
}
|
|
|
|
float width = _rect.width ();
|
|
float height = _rect.height ();
|
|
|
|
const double scale = UIConfiguration::instance ().get_ui_scale ();
|
|
|
|
if (_fill && !_transparent) {
|
|
setup_fill_context (context);
|
|
context->rectangle (draw.x0, draw.y0, draw.width (), draw.height ());
|
|
context->fill ();
|
|
}
|
|
|
|
render_children (area, context);
|
|
|
|
if (trigger()->scene_isolated ()) {
|
|
/* left shadow */
|
|
context->set_identity_matrix ();
|
|
context->translate (self.x0, self.y0 - 0.5);
|
|
Cairo::RefPtr<Cairo::LinearGradient> l_shadow = Cairo::LinearGradient::create (0, 0, scale * 12, 0);
|
|
l_shadow->add_color_stop_rgba (0.0, 0.0, 0.0, 0.0, 0.8);
|
|
l_shadow->add_color_stop_rgba (1.0, 0.0, 0.0, 0.0, 0.0);
|
|
context->set_source (l_shadow);
|
|
context->rectangle (0, 0, scale * 12, height);
|
|
context->fill ();
|
|
context->set_identity_matrix ();
|
|
}
|
|
|
|
if (tref.slot == 1) {
|
|
/* drop-shadow at top */
|
|
Cairo::RefPtr<Cairo::LinearGradient> drop_shadow_pattern = Cairo::LinearGradient::create (0.0, 0.0, 0.0, 6 * scale);
|
|
drop_shadow_pattern->add_color_stop_rgba (0, 0, 0, 0, 0.7);
|
|
drop_shadow_pattern->add_color_stop_rgba (1, 0, 0, 0, 0.0);
|
|
context->set_source (drop_shadow_pattern);
|
|
context->rectangle (0, 0, width, 6 * scale);
|
|
context->fill ();
|
|
} else if (tref.slot % 2 == 0) {
|
|
/* line at top */
|
|
context->set_identity_matrix ();
|
|
context->translate (self.x0, self.y0 - 0.5);
|
|
set_source_rgba (context, rgba_to_color (0, 0, 0, 1));
|
|
context->rectangle (0, 0, width, 1);
|
|
context->fill ();
|
|
context->set_identity_matrix ();
|
|
}
|
|
|
|
/* launch icon */
|
|
{
|
|
context->set_identity_matrix ();
|
|
context->translate (self.x0, self.y0 - 0.5);
|
|
context->translate (0, 0); // left side of the widget
|
|
draw_launch_icon (context, height, scale);
|
|
context->set_identity_matrix ();
|
|
}
|
|
|
|
/* follow-action icon */
|
|
if (trigger()->region ()) {
|
|
context->set_identity_matrix ();
|
|
context->translate (self.x0, self.y0 - 0.5);
|
|
context->translate (width - height, 0); // right side of the widget
|
|
set_source_rgba (context, UIConfiguration::instance ().color ("neutral:midground"));
|
|
draw_follow_icon (context, trigger()->follow_action (0), height, scale);
|
|
context->set_identity_matrix ();
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerEntry::prop_change (PropertyChange const& change)
|
|
{
|
|
if (change.contains (ARDOUR::Properties::name)) {
|
|
if (trigger()->region ()) {
|
|
name_text->set (short_version (trigger()->name (), 16));
|
|
} else {
|
|
name_text->set ("");
|
|
}
|
|
}
|
|
|
|
PropertyChange interesting_stuff;
|
|
interesting_stuff.add (ARDOUR::Properties::name);
|
|
interesting_stuff.add (ARDOUR::Properties::launch_style);
|
|
interesting_stuff.add (ARDOUR::Properties::follow_action0);
|
|
interesting_stuff.add (ARDOUR::Properties::isolated);
|
|
interesting_stuff.add (ARDOUR::Properties::running);
|
|
|
|
if (change.contains (interesting_stuff)) {
|
|
redraw ();
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerEntry::set_default_colors ()
|
|
{
|
|
set_fill_color (UIConfiguration::instance ().color ("theme:bg"));
|
|
play_button->set_fill_color (UIConfiguration::instance ().color ("theme:bg"));
|
|
name_button->set_fill_color (UIConfiguration::instance ().color ("theme:bg"));
|
|
name_button->set_outline_color (UIConfiguration::instance ().color ("theme:bg"));
|
|
follow_button->set_fill_color (UIConfiguration::instance ().color ("theme:bg"));
|
|
if ((tref.slot / 2) % 2 == 0) {
|
|
set_fill_color (HSV (fill_color ()).darker (0.15).color ());
|
|
play_button->set_fill_color (HSV (fill_color ()).darker (0.15).color ());
|
|
name_button->set_fill_color (HSV (fill_color ()).darker (0.15).color ());
|
|
name_button->set_outline_color (HSV (fill_color ()).darker (0.15).color ());
|
|
follow_button->set_fill_color (HSV (fill_color ()).darker (0.15).color ());
|
|
}
|
|
|
|
name_text->set_color (UIConfiguration::instance ().color ("neutral:foreground"));
|
|
name_text->set_fill_color (UIConfiguration::instance ().color ("neutral:midground"));
|
|
|
|
/*preserve selection border*/
|
|
if (PublicEditor::instance ().get_selection ().selected (this)) {
|
|
name_button->set_outline_color (UIConfiguration::instance ().color ("alert:red"));
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerEntry::ui_parameter_changed (std::string const& p)
|
|
{
|
|
if (p == "color-file") {
|
|
set_default_colors ();
|
|
}
|
|
}
|
|
|
|
Gtkmm2ext::Bindings* TriggerBoxUI::bindings = 0;
|
|
Glib::RefPtr<Gtk::ActionGroup> TriggerBoxUI::trigger_actions;
|
|
|
|
TriggerBoxUI::TriggerBoxUI (ArdourCanvas::Item* parent, TriggerBox& tb)
|
|
: Rectangle (parent)
|
|
, _triggerbox (tb)
|
|
, _file_chooser (0)
|
|
, _launch_context_menu (0)
|
|
, _follow_context_menu (0)
|
|
, _context_menu (0)
|
|
, _ignore_menu_action (false)
|
|
{
|
|
set_layout_sensitive (true); // why???
|
|
|
|
set_fill_color (UIConfiguration::instance ().color (X_("theme:bg")));
|
|
set_fill (true);
|
|
|
|
build ();
|
|
|
|
_selection_connection = PublicEditor::instance ().get_selection ().TriggersChanged.connect (sigc::mem_fun (*this, &TriggerBoxUI::selection_changed));
|
|
|
|
std::vector<Gtk::TargetEntry> target_table;
|
|
target_table.push_back (Gtk::TargetEntry ("regions"));
|
|
target_table.push_back (Gtk::TargetEntry ("text/uri-list"));
|
|
target_table.push_back (Gtk::TargetEntry ("text/plain"));
|
|
target_table.push_back (Gtk::TargetEntry ("application/x-rootwin-drop"));
|
|
|
|
GtkCanvas* gtkcanvas = static_cast<GtkCanvas*> (canvas ());
|
|
assert (gtkcanvas);
|
|
gtkcanvas->drag_dest_set (target_table);
|
|
gtkcanvas->signal_drag_motion ().connect (sigc::mem_fun (*this, &TriggerBoxUI::drag_motion));
|
|
gtkcanvas->signal_drag_leave ().connect (sigc::mem_fun (*this, &TriggerBoxUI::drag_leave));
|
|
gtkcanvas->signal_drag_data_received ().connect (sigc::mem_fun (*this, &TriggerBoxUI::drag_data_received));
|
|
}
|
|
|
|
TriggerBoxUI::~TriggerBoxUI ()
|
|
{
|
|
/* sigc connection's are not scoped (i.e. they do not disconnect the
|
|
functor from the signal when they are destroyed).
|
|
*/
|
|
_selection_connection.disconnect ();
|
|
_update_connection.disconnect ();
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::selection_changed ()
|
|
{
|
|
for (auto& slot : _slots) {
|
|
slot->selection_change ();
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::setup_actions_and_bindings ()
|
|
{
|
|
load_bindings ();
|
|
register_actions ();
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::load_bindings ()
|
|
{
|
|
bindings = Bindings::get_bindings (X_("Triggers"));
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::register_actions ()
|
|
{
|
|
trigger_actions = ActionManager::create_action_group (bindings, X_("Triggers"));
|
|
|
|
for (int32_t n = 0; n < TriggerBox::default_triggers_per_box; ++n) {
|
|
const std::string action_name = string_compose ("trigger-scene-%1", n);
|
|
const std::string display_name = string_compose (_("Scene %1"), n);
|
|
|
|
ActionManager::register_toggle_action (trigger_actions, action_name.c_str (), display_name.c_str (), sigc::bind (sigc::ptr_fun (TriggerBoxUI::trigger_scene), n));
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::trigger_scene (int32_t n)
|
|
{
|
|
TriggerBox::scene_bang (n);
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::build ()
|
|
{
|
|
TriggerPtr t;
|
|
uint64_t n = 0;
|
|
|
|
// clear_items (true);
|
|
|
|
_slots.clear ();
|
|
|
|
while (true) {
|
|
t = _triggerbox.trigger (n);
|
|
if (!t) {
|
|
break;
|
|
}
|
|
TriggerEntry* te = new TriggerEntry (this, TriggerReference (_triggerbox, n));
|
|
|
|
_slots.push_back (te);
|
|
|
|
te->play_button->Event.connect (sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::play_button_event), n)); //ToDo: just trigger stuff
|
|
te->name_button->Event.connect (sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::name_button_event), n));
|
|
te->follow_button->Event.connect (sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::follow_button_event), n)); //ToDo: just follow stuff
|
|
#if 0
|
|
te->Event.connect (sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::event), n));
|
|
#endif
|
|
|
|
++n;
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::_size_allocate (ArdourCanvas::Rect const& alloc)
|
|
{
|
|
Rectangle::_size_allocate (alloc);
|
|
|
|
const float width = alloc.width ();
|
|
const float height = alloc.height ();
|
|
|
|
const float slot_h = height / TriggerBox::default_triggers_per_box; // TODO
|
|
|
|
float ypos = 0;
|
|
for (auto& slot : _slots) {
|
|
slot->size_allocate (ArdourCanvas::Rect (0, 0, width, slot_h));
|
|
slot->set_position (Duple (0, ypos));
|
|
ypos += slot_h;
|
|
slot->show ();
|
|
}
|
|
}
|
|
|
|
bool
|
|
TriggerBoxUI::name_button_event (GdkEvent* ev, uint64_t n)
|
|
{
|
|
switch (ev->type) {
|
|
case GDK_ENTER_NOTIFY:
|
|
if (ev->crossing.detail != GDK_NOTIFY_INFERIOR) {
|
|
_slots[n]->set_default_colors ();
|
|
_slots[n]->name_text->set_color (UIConfiguration::instance ().color ("neutral:foregroundest"));
|
|
_slots[n]->name_button->set_fill_color (HSV (fill_color ()).lighter (0.15).color ());
|
|
_slots[n]->name_button->set_outline_color (HSV (fill_color ()).lighter (0.15).color ());
|
|
_slots[n]->follow_button->set_fill_color (HSV (fill_color ()).lighter (0.15).color ());
|
|
_slots[n]->play_button->set_fill_color (HSV (fill_color ()).lighter (0.15).color ());
|
|
/*preserve selection border*/
|
|
if (PublicEditor::instance ().get_selection ().selected (_slots[n])) {
|
|
_slots[n]->name_button->set_outline_color (UIConfiguration::instance ().color ("alert:red"));
|
|
}
|
|
}
|
|
break;
|
|
case GDK_LEAVE_NOTIFY:
|
|
if (ev->crossing.detail != GDK_NOTIFY_INFERIOR) {
|
|
_slots[n]->set_default_colors ();
|
|
}
|
|
break;
|
|
case GDK_BUTTON_PRESS:
|
|
PublicEditor::instance ().get_selection ().set (_slots[n]);
|
|
/* a side-effect of selection-change is that the slot's color is reset. retain the "entered-color" here: */
|
|
_slots[n]->name_text->set_color (UIConfiguration::instance ().color ("neutral:foregroundest"));
|
|
_slots[n]->name_button->set_fill_color (HSV (fill_color ()).lighter (0.15).color ());
|
|
_slots[n]->name_button->set_outline_color (UIConfiguration::instance ().color ("alert:red"));
|
|
_slots[n]->follow_button->set_fill_color (HSV (fill_color ()).lighter (0.15).color ());
|
|
break;
|
|
case GDK_2BUTTON_PRESS:
|
|
edit_trigger (n);
|
|
return true;
|
|
case GDK_BUTTON_RELEASE:
|
|
switch (ev->button.button) {
|
|
case 3:
|
|
context_menu (n);
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
TriggerBoxUI::play_button_event (GdkEvent* ev, uint64_t n)
|
|
{
|
|
if (!_triggerbox.trigger (n)->region ()) {
|
|
/* empty slot; this is just a stop button */
|
|
switch (ev->type) {
|
|
case GDK_BUTTON_PRESS:
|
|
if (ev->button.button == 1) {
|
|
_triggerbox.stop_all_immediately ();
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (ev->type) {
|
|
case GDK_BUTTON_PRESS:
|
|
switch (ev->button.button) {
|
|
case 1:
|
|
_slots[n]->trigger()->bang ();
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case GDK_BUTTON_RELEASE:
|
|
switch (ev->button.button) {
|
|
case 1:
|
|
if (_slots[n]->trigger()->launch_style () == Trigger::Gate ||
|
|
_slots[n]->trigger()->launch_style () == Trigger::Repeat) {
|
|
_slots[n]->trigger()->unbang ();
|
|
}
|
|
break;
|
|
case 3:
|
|
launch_context_menu (n);
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case GDK_ENTER_NOTIFY:
|
|
if (ev->crossing.detail != GDK_NOTIFY_INFERIOR) {
|
|
_slots[n]->set_default_colors ();
|
|
_slots[n]->play_button->set_fill_color (HSV (fill_color ()).lighter (0.15).color ());
|
|
}
|
|
break;
|
|
case GDK_LEAVE_NOTIFY:
|
|
if (ev->crossing.detail != GDK_NOTIFY_INFERIOR) {
|
|
_slots[n]->set_default_colors ();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
TriggerBoxUI::follow_button_event (GdkEvent* ev, uint64_t n)
|
|
{
|
|
switch (ev->type) {
|
|
case GDK_BUTTON_RELEASE:
|
|
switch (ev->button.button) {
|
|
case 3:
|
|
follow_context_menu (n);
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case GDK_ENTER_NOTIFY:
|
|
if (ev->crossing.detail != GDK_NOTIFY_INFERIOR) {
|
|
_slots[n]->set_default_colors ();
|
|
_slots[n]->follow_button->set_fill_color (HSV (fill_color ()).lighter (0.15).color ());
|
|
}
|
|
break;
|
|
case GDK_LEAVE_NOTIFY:
|
|
if (ev->crossing.detail != GDK_NOTIFY_INFERIOR) {
|
|
_slots[n]->set_default_colors ();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
TriggerBoxUI::context_menu (uint64_t n)
|
|
{
|
|
using namespace Gtk;
|
|
using namespace Gtk::Menu_Helpers;
|
|
using namespace Temporal;
|
|
|
|
delete _context_menu;
|
|
|
|
_context_menu = new Menu;
|
|
MenuList& items = _context_menu->items ();
|
|
_context_menu->set_name ("ArdourContextMenu");
|
|
|
|
Menu* load_menu = manage (new Menu);
|
|
MenuList& loitems (load_menu->items ());
|
|
|
|
loitems.push_back (MenuElem (_("from file"), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::choose_sample), n)));
|
|
loitems.push_back (MenuElem (_("from selection"), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_from_selection), n)));
|
|
|
|
items.push_back (MenuElem (_("Load..."), *load_menu));
|
|
#if DOUBLE_CLICK_IS_NOT_OBVIOUS_ENOUGH
|
|
items.push_back (MenuElem (_("Edit..."), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::edit_trigger), n)));
|
|
#endif
|
|
items.push_back (SeparatorElem());
|
|
items.push_back (MenuElem (_("Clear"), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::clear_trigger), n)));
|
|
|
|
_context_menu->popup (1, gtk_get_current_event_time ());
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::launch_context_menu (uint64_t n)
|
|
{
|
|
using namespace Gtk;
|
|
using namespace Gtk::Menu_Helpers;
|
|
using namespace Temporal;
|
|
|
|
delete _launch_context_menu;
|
|
|
|
_launch_context_menu = new Menu;
|
|
MenuList& items = _launch_context_menu->items ();
|
|
_launch_context_menu->set_name ("ArdourContextMenu");
|
|
|
|
RadioMenuItem::Group lagroup;
|
|
RadioMenuItem::Group qgroup;
|
|
|
|
Menu* launch_menu = manage (new Menu);
|
|
MenuList& litems = launch_menu->items ();
|
|
|
|
litems.push_back (RadioMenuElem (lagroup, TriggerUI::launch_style_to_string(Trigger::OneShot), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_launch_style), n, Trigger::OneShot)));
|
|
if (_triggerbox.trigger (n)->launch_style () == Trigger::OneShot) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&litems.back ())->set_active (true);
|
|
}
|
|
litems.push_back (RadioMenuElem (lagroup, TriggerUI::launch_style_to_string(Trigger::Gate), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_launch_style), n, Trigger::Gate)));
|
|
if (_triggerbox.trigger (n)->launch_style () == Trigger::Gate) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&litems.back ())->set_active (true);
|
|
}
|
|
litems.push_back (RadioMenuElem (lagroup, TriggerUI::launch_style_to_string(Trigger::Toggle), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_launch_style), n, Trigger::Toggle)));
|
|
if (_triggerbox.trigger (n)->launch_style () == Trigger::Toggle) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&litems.back ())->set_active (true);
|
|
}
|
|
litems.push_back (RadioMenuElem (lagroup, TriggerUI::launch_style_to_string(Trigger::Repeat), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_launch_style), n, Trigger::Repeat)));
|
|
if (_triggerbox.trigger (n)->launch_style () == Trigger::Repeat) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&litems.back ())->set_active (true);
|
|
}
|
|
|
|
Menu* quant_menu = manage (new Menu);
|
|
MenuList& qitems = quant_menu->items ();
|
|
bool success;
|
|
|
|
BBT_Offset b;
|
|
|
|
#if TRIGGER_PAGE_GLOBAL_QUANTIZATION_IS_IMPLEMENTED
|
|
Beats grid_beats (PublicEditor::instance ().get_grid_type_as_beats (success, timepos_t (0)));
|
|
if (success) {
|
|
b = BBT_Offset (0, grid_beats.get_beats (), grid_beats.get_ticks ());
|
|
qitems.push_back (RadioMenuElem (qgroup, _("Main Grid"), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_quantization), n, b)));
|
|
/* can't mark this active because the current trigger quant setting may just a specific setting below */
|
|
/* XXX HOW TO GET THIS TO FOLLOW GRID CHANGES (which are GUI only) */
|
|
}
|
|
#endif
|
|
|
|
b = BBT_Offset (1, 0, 0);
|
|
qitems.push_back (RadioMenuElem (qgroup, TriggerUI::quantize_length_to_string (b), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_quantization), n, b)));
|
|
if (_triggerbox.trigger (n)->quantization () == b) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&qitems.back ())->set_active (true);
|
|
}
|
|
b = BBT_Offset (0, 4, 0);
|
|
qitems.push_back (RadioMenuElem (qgroup, TriggerUI::quantize_length_to_string (b), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_quantization), n, b)));
|
|
if (_triggerbox.trigger (n)->quantization () == b) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&qitems.back ())->set_active (true);
|
|
}
|
|
b = BBT_Offset (0, 2, 0);
|
|
qitems.push_back (RadioMenuElem (qgroup, TriggerUI::quantize_length_to_string (b), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_quantization), n, b)));
|
|
if (_triggerbox.trigger (n)->quantization () == b) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&qitems.back ())->set_active (true);
|
|
}
|
|
b = BBT_Offset (0, 1, 0);
|
|
qitems.push_back (RadioMenuElem (qgroup, TriggerUI::quantize_length_to_string (b), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_quantization), n, b)));
|
|
if (_triggerbox.trigger (n)->quantization () == b) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&qitems.back ())->set_active (true);
|
|
}
|
|
b = BBT_Offset (0, 0, ticks_per_beat / 2);
|
|
qitems.push_back (RadioMenuElem (qgroup, TriggerUI::quantize_length_to_string (b), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_quantization), n, b)));
|
|
if (_triggerbox.trigger (n)->quantization () == b) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&qitems.back ())->set_active (true);
|
|
}
|
|
b = BBT_Offset (0, 0, ticks_per_beat / 4);
|
|
qitems.push_back (RadioMenuElem (qgroup, TriggerUI::quantize_length_to_string (b), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_quantization), n, b)));
|
|
if (_triggerbox.trigger (n)->quantization () == b) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&qitems.back ())->set_active (true);
|
|
}
|
|
b = BBT_Offset (0, 0, ticks_per_beat / 8);
|
|
qitems.push_back (RadioMenuElem (qgroup, TriggerUI::quantize_length_to_string (b), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_quantization), n, b)));
|
|
if (_triggerbox.trigger (n)->quantization () == b) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&qitems.back ())->set_active (true);
|
|
}
|
|
b = BBT_Offset (0, 0, ticks_per_beat / 16);
|
|
qitems.push_back (RadioMenuElem (qgroup, TriggerUI::quantize_length_to_string (b), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_quantization), n, b)));
|
|
if (_triggerbox.trigger (n)->quantization () == b) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&qitems.back ())->set_active (true);
|
|
}
|
|
|
|
Menu* load_menu = manage (new Menu);
|
|
MenuList& loitems (load_menu->items ());
|
|
|
|
items.push_back (MenuElem (_("Launch Style..."), *launch_menu));
|
|
items.push_back (MenuElem (_("Quantization..."), *quant_menu));
|
|
|
|
items.push_back (CheckMenuElem (_("Cue Isolate"), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::toggle_trigger_isolated), n)));
|
|
if (_triggerbox.trigger (n)->scene_isolated ()) {
|
|
PBD::Unwinder<bool> uw (_ignore_menu_action, true);
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&items.back ())->set_active (true);
|
|
}
|
|
|
|
_launch_context_menu->popup (1, gtk_get_current_event_time ());
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::follow_context_menu (uint64_t n)
|
|
{
|
|
using namespace Gtk;
|
|
using namespace Gtk::Menu_Helpers;
|
|
using namespace Temporal;
|
|
|
|
delete _follow_context_menu;
|
|
|
|
_follow_context_menu = new Menu;
|
|
MenuList& items = _follow_context_menu->items ();
|
|
_follow_context_menu->set_name ("ArdourContextMenu");
|
|
|
|
Menu* follow_menu = manage (new Menu);
|
|
MenuList& fitems = follow_menu->items ();
|
|
|
|
RadioMenuItem::Group fagroup;
|
|
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::None), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::None)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::None) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::Stop), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::Stop)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::Stop) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::Again), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::Again)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::Again) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
#if QUEUED_SLOTS_IMPLEMENTED
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::QueuedTrigger), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::QueuedTrigger)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::QueuedTrigger) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
#endif
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::PrevTrigger), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::PrevTrigger)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::PrevTrigger) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::NextTrigger), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::NextTrigger)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::NextTrigger) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
#if 0
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::FirstTrigger), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::FirstTrigger)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::FirstTrigger) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::LastTrigger), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::LastTrigger)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::LastTrigger) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
#endif
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::AnyTrigger), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::AnyTrigger)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::AnyTrigger) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(Trigger::OtherTrigger), sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::set_follow_action), n, Trigger::OtherTrigger)));
|
|
if (_triggerbox.trigger (n)->follow_action (0) == Trigger::OtherTrigger) {
|
|
dynamic_cast<Gtk::CheckMenuItem*> (&fitems.back ())->set_active (true);
|
|
}
|
|
|
|
items.push_back (MenuElem (_("Follow Action..."), *follow_menu));
|
|
|
|
_follow_context_menu->popup (1, gtk_get_current_event_time ());
|
|
}
|
|
|
|
|
|
void
|
|
TriggerBoxUI::toggle_trigger_isolated (uint64_t n)
|
|
{
|
|
if (_ignore_menu_action) {
|
|
return;
|
|
}
|
|
|
|
TriggerPtr trigger = _triggerbox.trigger (n);
|
|
trigger->set_scene_isolated (!trigger->scene_isolated ());
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::clear_trigger (uint64_t n)
|
|
{
|
|
TriggerPtr trigger = _triggerbox.trigger (n);
|
|
trigger->set_region (boost::shared_ptr<Region>());
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::edit_trigger (uint64_t n)
|
|
{
|
|
TriggerPtr trigger = _triggerbox.trigger (n);
|
|
TriggerWindow* tw = static_cast<TriggerWindow*> (trigger->ui ());
|
|
|
|
if (!tw) {
|
|
tw = new TriggerWindow (TriggerReference (_triggerbox, n));
|
|
trigger->set_ui (tw);
|
|
}
|
|
|
|
tw->present ();
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::set_follow_action (uint64_t n, Trigger::FollowAction fa)
|
|
{
|
|
_triggerbox.trigger (n)->set_follow_action (fa, 0);
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::set_launch_style (uint64_t n, Trigger::LaunchStyle ls)
|
|
{
|
|
_triggerbox.trigger (n)->set_launch_style (ls);
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::set_quantization (uint64_t n, Temporal::BBT_Offset const& q)
|
|
{
|
|
_triggerbox.trigger (n)->set_quantization (q);
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::choose_sample (uint64_t n)
|
|
{
|
|
if (!_file_chooser) {
|
|
_file_chooser = new Gtk::FileChooserDialog (_("Select sample"), Gtk::FILE_CHOOSER_ACTION_OPEN);
|
|
_file_chooser->add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
|
|
_file_chooser->add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
|
|
_file_chooser->set_select_multiple (true);
|
|
}
|
|
|
|
_file_chooser_connection.disconnect ();
|
|
_file_chooser_connection = _file_chooser->signal_response ().connect (sigc::bind (sigc::mem_fun (*this, &TriggerBoxUI::sample_chosen), n));
|
|
|
|
_file_chooser->present ();
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::sample_chosen (int response, uint64_t n)
|
|
{
|
|
_file_chooser->hide ();
|
|
|
|
switch (response) {
|
|
case Gtk::RESPONSE_OK:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
std::list<std::string> paths = _file_chooser->get_filenames ();
|
|
|
|
for (std::list<std::string>::iterator s = paths.begin (); s != paths.end (); ++s) {
|
|
/* this will do nothing if n is too large */
|
|
_triggerbox.set_from_path (n, *s);
|
|
++n;
|
|
}
|
|
}
|
|
|
|
uint64_t
|
|
TriggerBoxUI::slot_at_y (int y) const
|
|
{
|
|
uint64_t n = 0;
|
|
for (auto& slot : _slots) {
|
|
if (slot->height () < y) {
|
|
++n;
|
|
y -= slot->height ();
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
bool
|
|
TriggerBoxUI::drag_motion (Glib::RefPtr<Gdk::DragContext> const& context, int, int y, guint time)
|
|
{
|
|
bool can_drop = true;
|
|
uint64_t n = slot_at_y (y);
|
|
if (n >= _slots.size ()) {
|
|
assert (0);
|
|
can_drop = false;
|
|
}
|
|
|
|
if (can_drop) {
|
|
context->drag_status (Gdk::ACTION_COPY, time);
|
|
/* prelight */
|
|
GdkEventCrossing ev;
|
|
ev.detail = GDK_NOTIFY_ANCESTOR;
|
|
for (size_t i = 0; i < _slots.size (); ++i) {
|
|
ev.type = (i == n) ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
|
|
name_button_event ((GdkEvent*)&ev, i);
|
|
}
|
|
return true;
|
|
} else {
|
|
context->drag_status (Gdk::DragAction (0), time);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::drag_leave (Glib::RefPtr<Gdk::DragContext> const&, guint)
|
|
{
|
|
GdkEventCrossing ev;
|
|
ev.type = GDK_LEAVE_NOTIFY;
|
|
ev.detail = GDK_NOTIFY_ANCESTOR;
|
|
for (size_t i = 0; i < _slots.size (); ++i) {
|
|
name_button_event ((GdkEvent*)&ev, i);
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::drag_data_received (Glib::RefPtr<Gdk::DragContext> const& context, int /*x*/, int y, Gtk::SelectionData const& data, guint /*info*/, guint time)
|
|
{
|
|
uint64_t n = slot_at_y (y);
|
|
if (n >= _slots.size ()) {
|
|
context->drag_finish (false, false, time);
|
|
return;
|
|
}
|
|
if (data.get_target () == X_("regions")) {
|
|
boost::shared_ptr<Region> region = PublicEditor::instance ().get_dragged_region_from_sidebar ();
|
|
if (region) {
|
|
_triggerbox.set_from_selection (n, region);
|
|
context->drag_finish (true, false, time);
|
|
} else {
|
|
context->drag_finish (false, false, time);
|
|
}
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> paths;
|
|
if (ARDOUR_UI_UTILS::convert_drop_to_paths (paths, data)) {
|
|
for (std::vector<std::string>::iterator s = paths.begin (); s != paths.end (); ++s) {
|
|
/* this will do nothing if n is too large */
|
|
_triggerbox.set_from_path (n, *s);
|
|
++n;
|
|
}
|
|
}
|
|
context->drag_finish (true, false, time);
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::set_from_selection (uint64_t n)
|
|
{
|
|
Selection& selection (PublicEditor::instance ().get_selection ());
|
|
RegionSelection rselection (selection.regions);
|
|
|
|
if (rselection.empty ()) {
|
|
/* XXX possible message about no selection ? */
|
|
return;
|
|
}
|
|
|
|
for (RegionSelection::iterator r = rselection.begin (); r != rselection.end (); ++r) {
|
|
_triggerbox.set_from_selection (n, (*r)->region ());
|
|
++n;
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::start_updating ()
|
|
{
|
|
_update_connection = Timers::rapid_connect (sigc::mem_fun (*this, &TriggerBoxUI::rapid_update));
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::stop_updating ()
|
|
{
|
|
_update_connection.disconnect ();
|
|
}
|
|
|
|
void
|
|
TriggerBoxUI::rapid_update ()
|
|
{
|
|
for (auto& slot : _slots) {
|
|
slot->maybe_update ();
|
|
}
|
|
}
|
|
|
|
TriggerBoxWidget::TriggerBoxWidget (float w, float h)
|
|
: FittedCanvasWidget (w, h)
|
|
, ui (0)
|
|
{
|
|
set_background_color (UIConfiguration::instance ().color (X_("theme:bg")));
|
|
}
|
|
|
|
void
|
|
TriggerBoxWidget::set_triggerbox (TriggerBox* tb)
|
|
{
|
|
if (ui) {
|
|
root ()->remove (ui);
|
|
delete ui;
|
|
ui = 0;
|
|
}
|
|
|
|
if (!tb) {
|
|
return;
|
|
}
|
|
|
|
ui = new TriggerBoxUI (root (), *tb);
|
|
repeat_size_allocation ();
|
|
|
|
if (is_mapped ()) {
|
|
ui->start_updating ();
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerBoxWidget::on_map ()
|
|
{
|
|
FittedCanvasWidget::on_map ();
|
|
|
|
if (ui) {
|
|
ui->start_updating ();
|
|
}
|
|
}
|
|
|
|
void
|
|
TriggerBoxWidget::on_unmap ()
|
|
{
|
|
FittedCanvasWidget::on_unmap ();
|
|
if (ui) {
|
|
ui->stop_updating ();
|
|
}
|
|
}
|