--- gigedit/trunk/src/gigedit/paramedit.cpp 2009/02/03 19:38:19 1831 +++ gigedit/trunk/src/gigedit/paramedit.cpp 2018/01/23 16:30:56 3409 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andreas Persson + * Copyright (C) 2006-2017 Andreas Persson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -17,55 +17,151 @@ * 02110-1301 USA. */ -#include - +#include "global.h" #include "paramedit.h" -#include "global.h" +#include "compat.h" +#include "Settings.h" + +#include +#include namespace { - const char* const controlChangeTexts[] = { - _("none"), _("channelaftertouch"), _("velocity"), - 0, - _("modwheel"), // "Modulation Wheel or Lever", - _("breath"), // "Breath Controller", - 0, - _("foot"), // "Foot Controller", - _("portamentotime"), // "Portamento Time", - 0, 0, 0, 0, 0, 0, - _("effect1"), // "Effect Control 1", - _("effect2"), // "Effect Control 2", - 0, 0, - _("genpurpose1"), // "General Purpose Controller 1", - _("genpurpose2"), // "General Purpose Controller 2", - _("genpurpose3"), // "General Purpose Controller 3", - _("genpurpose4"), // "General Purpose Controller 4", - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - _("sustainpedal"), // "Damper Pedal on/off (Sustain)", - _("portamento"), // "Portamento On/Off", - _("sostenuto"), // "Sustenuto On/Off", - _("softpedal"), // "Soft Pedal On/Off", - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - _("genpurpose5"), // "General Purpose Controller 5", - _("genpurpose6"), // "General Purpose Controller 6", - _("genpurpose7"), // "General Purpose Controller 7", - _("genpurpose8"), // "General Purpose Controller 8", - 0, 0, 0, 0, 0, 0, 0, - _("effect1depth"), // "Effects 1 Depth", - _("effect2depth"), // "Effects 2 Depth", - _("effect3depth"), // "Effects 3 Depth", - _("effect4depth"), // "Effects 4 Depth", - _("effect5depth"), // "Effects 5 Depth" + struct CCText { + const char* const txt; + bool isExtension; ///< True if this is a controller only supported by LinuxSampler, but not supperted by Gigasampler/GigaStudio. + }; + static const CCText controlChangeTexts[] = { + // 3 special ones (not being CCs) + { _("none") }, { _("channelaftertouch") }, { _("velocity") }, + {0}, // bank select MSB (hard coded in sampler, so discouraged to be used here, even though considerable) + { _("modwheel") }, // "Modulation Wheel or Lever", + { _("breath") }, // "Breath Controller", + { _("undefined"), true }, + { _("foot") }, // "Foot Controller", + { _("portamentotime") }, // "Portamento Time", + { _("data entry MSB"), true }, + { _("volume"), true }, + { _("balance"), true }, + { _("undefined"), true }, + { _("pan"), true }, + { _("expression"), true }, + { _("effect1") }, // "Effect Control 1", + { _("effect2") }, // "Effect Control 2", + { _("undefined"), true }, + { _("undefined"), true }, + { _("genpurpose1") }, // "General Purpose Controller 1", + { _("genpurpose2") }, // "General Purpose Controller 2", + { _("genpurpose3") }, // "General Purpose Controller 3", + { _("genpurpose4") }, // "General Purpose Controller 4", + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + + // LSB variant of the various controllers above + // (so discouraged to be used here for now) + {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, + {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, + {0}, {0}, {0}, {0}, + + { _("sustainpedal") }, // "Damper Pedal on/off (Sustain)", + { _("portamento") }, // "Portamento On/Off", + { _("sostenuto") }, // "Sustenuto On/Off", + { _("softpedal") }, // "Soft Pedal On/Off", + { _("legato"), true }, + { _("hold2"), true }, + { _("soundvariation"), true }, + { _("timbre"), true }, + { _("releasetime"), true }, + { _("attacktime"), true }, + { _("brightness"), true }, + { _("decaytime"), true }, + { _("vibratorate"), true }, + { _("vibratodepth"), true }, + { _("vibratodelay"), true }, + { _("undefined"), true }, + { _("genpurpose5") }, // "General Purpose Controller 5", + { _("genpurpose6") }, // "General Purpose Controller 6", + { _("genpurpose7") }, // "General Purpose Controller 7", + { _("genpurpose8") }, // "General Purpose Controller 8", + { _("portamentoctrl"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + {0}, // high resolution velocity prefix (so discouraged to be used here) + { _("undefined"), true }, + { _("undefined"), true }, + { _("effect1depth") }, // "Effects 1 Depth", + { _("effect2depth") }, // "Effects 2 Depth", + { _("effect3depth") }, // "Effects 3 Depth", + { _("effect4depth") }, // "Effects 4 Depth", + { _("effect5depth") }, // "Effects 5 Depth" + { _("dataincrement"), true }, + { _("datadecrement"), true }, + {0}, // NRPN LSB (so discouraged to be used here) + {0}, // NRPN MSB (so discouraged to be used here) + {0}, // RPN LSB (so discouraged to be used here) + {0}, // RPN MSB (so discouraged to be used here) + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true }, + { _("undefined"), true } // CC 119 + // (all other ones that follow [CC 120- CC 127] are hard coded channel + // mode messages, so those are discouraged to be used here) }; } +#define controlChangeTextsSize (sizeof(controlChangeTexts) / sizeof(CCText)) + LabelWidget::LabelWidget(const char* labelText, Gtk::Widget& widget) : label(Glib::ustring(labelText) + ":"), widget(widget) { - label.set_alignment(Gtk::ALIGN_LEFT); +#if HAS_GTKMM_ALIGNMENT + label.set_alignment(Gtk::ALIGN_START); +#else + label.set_halign(Gtk::Align::START); +#endif + Settings::singleton()->showTooltips.get_proxy().signal_changed().connect( + sigc::mem_fun(this, &LabelWidget::on_show_tooltips_changed) + ); + + // workaround for a crash with certain gtkmm versions: postpone calling + // on_show_tooltips_changed() because widget.gobj() might be uninitialized + // at this point yet + Glib::signal_idle().connect_once( // timeout starts given amount of ms after the main loop became idle again ... + sigc::mem_fun(*this, &LabelWidget::on_show_tooltips_changed), + 300 + ); +} + +void LabelWidget::on_show_tooltips_changed() { + const bool b = Settings::singleton()->showTooltips; + label.set_has_tooltip(b); + widget.set_has_tooltip(b); } void LabelWidget::set_sensitive(bool sensitive) @@ -74,26 +170,63 @@ widget.set_sensitive(sensitive); } +ReadOnlyLabelWidget::ReadOnlyLabelWidget(const char* leftHandText) + : LabelWidget(leftHandText, text) +{ +#if HAS_GTKMM_ALIGNMENT + text.set_alignment(Gtk::ALIGN_START, Gtk::ALIGN_START); +#else + label.set_halign(Gtk::Align::START); + label.set_valign(Gtk::Align::START); +#endif +} + +ReadOnlyLabelWidget::ReadOnlyLabelWidget(const char* leftHandText, const char* rightHandText) + : LabelWidget(leftHandText, text) +{ +#if HAS_GTKMM_ALIGNMENT + text.set_alignment(Gtk::ALIGN_START, Gtk::ALIGN_START); +#else + text.set_halign(Gtk::Align::START); + text.set_valign(Gtk::Align::START); +#endif + text.set_text(rightHandText); +} + NumEntry::NumEntry(const char* labelText, double lower, double upper, int decimals) : + LabelWidget(labelText, box), +#if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2 adjust(lower, lower, upper, 1, 10), +#else + adjust(Gtk::Adjustment::create(lower, lower, upper, 1, 10)), +#endif scale(adjust), - spinbutton(adjust), - LabelWidget(labelText, box) + spinbutton(adjust) { + scale.set_size_request(70); spinbutton.set_digits(decimals); spinbutton.set_value(0); + spinbutton.set_numeric(); scale.set_draw_value(false); box.pack_start(spinbutton, Gtk::PACK_SHRINK); box.add(scale); } +void NumEntry::on_show_tooltips_changed() { + LabelWidget::on_show_tooltips_changed(); + + const bool b = Settings::singleton()->showTooltips; + spinbutton.set_has_tooltip(b); + scale.set_has_tooltip(b); +} + NumEntryGain::NumEntryGain(const char* labelText, double lower, double upper, int decimals, double coeff) : NumEntry(labelText, lower, upper, decimals), - coeff(coeff), value(0), + coeff(coeff), connected(true) { spinbutton.signal_value_changed().connect( @@ -138,6 +271,12 @@ sigc::mem_fun(*this, &BoolEntryPlus6::value_changed)); } +void BoolEntryPlus6::on_show_tooltips_changed() { + LabelWidget::on_show_tooltips_changed(); + + eGain.on_show_tooltips_changed(); +} + void BoolEntryPlus6::value_changed() { if (checkbutton.get_active()) eGain.set_value(plus6value); @@ -183,64 +322,139 @@ NoteEntry::NoteEntry(const char* labelText) : NumEntryTemp(labelText) { - spinbutton.signal_input().connect( - sigc::mem_fun(*this, &NoteEntry::on_input)); - spinbutton.signal_output().connect( - sigc::mem_fun(*this, &NoteEntry::on_output)); + spin_button_show_notes(spinbutton); } -const char* notes[] = { - _("C"), _("C#"), _("D"), _("D#"), _("E"), _("F"),_("F#"), - _("G"), _("G#"), _("A"), _("A#"), _("B") -}; +namespace { + const char* notes[] = { + _("C"), _("C#"), _("D"), _("D#"), _("E"), _("F"),_("F#"), + _("G"), _("G#"), _("A"), _("A#"), _("B") + }; + int note_value(const Glib::ustring& note, double* value) + { + const char* str = note.c_str(); -// Convert the Entry text to a number -int NoteEntry::on_input(double* new_value) + int i; + for (i = 11 ; i >= 0 ; i--) { + if (strncasecmp(str, notes[i], strlen(notes[i])) == 0) break; + } + if (i >= 0) { + char* endptr; + long x = strtol(str + strlen(notes[i]), &endptr, 10); + if (endptr != str + strlen(notes[i])) { + *value = std::max(0L, std::min(i + (x + 1) * 12, 127L)); + return true; + } + } else { + char* endptr; + long x = strtol(str, &endptr, 10); + if (endptr != str) { + *value = std::max(0L, std::min(x, 127L)); + return true; + } + } + +#if HAS_GTKMM_CPP11_ENUMS + return Gtk::SpinButton::INPUT_ERROR; +#else + return Gtk::INPUT_ERROR; +#endif + } +} + +int note_value(const Glib::ustring& note) { - const char* str = spinbutton.get_text().c_str(); + double value = 0; + note_value(note, &value); + return value; +} - int i; - for (i = 11 ; i >= 0 ; i--) { - if (strncmp(str, notes[i], strlen(notes[i])) == 0) break; +Glib::ustring note_str(int note) +{ + char buf[10]; + sprintf(buf, "%s%d", notes[note % 12], note / 12 - 1); + return buf; +} + +namespace { + // Convert the Entry text to a number +#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2 + int on_input(double& new_value, Gtk::SpinButton* spinbutton) { + return note_value(spinbutton->get_text(), &new_value); } - if (i >= 0) { - char* endptr; - long x = strtol(str + strlen(notes[i]), &endptr, 10); - if (endptr != str + strlen(notes[i])) { - *new_value = i + (x + 1) * 12; - return true; - } +#else + int on_input(double* new_value, Gtk::SpinButton* spinbutton) { + return note_value(spinbutton->get_text(), new_value); + } +#endif + + // Convert the Adjustment position to text + bool on_output(Gtk::SpinButton* spinbutton) { + spinbutton->set_text( + note_str(spinbutton->get_adjustment()->get_value() + 0.5)); + return true; } - return Gtk::INPUT_ERROR; } -// Convert the Adjustment position to text -bool NoteEntry::on_output() +// Make a SpinButton show notes instead of numbers +void spin_button_show_notes(Gtk::SpinButton& spin_button) { - int x = int(spinbutton.get_adjustment()->get_value() + 0.5); - char buf[10]; - sprintf(buf, "%s%d", notes[x % 12], x / 12 - 1); - spinbutton.set_text(buf); - return true; + spin_button.set_numeric(false); + spin_button.set_width_chars(4); + spin_button.signal_input().connect( + sigc::bind(sigc::ptr_fun(&on_input), &spin_button)); + spin_button.signal_output().connect( + sigc::bind(sigc::ptr_fun(&on_output), &spin_button)); +} + +void ChoiceEntryBase::on_show_tooltips_changed() { + LabelWidget::on_show_tooltips_changed(); + + const bool b = Settings::singleton()->showTooltips; + combobox.set_has_tooltip(b); } ChoiceEntryLeverageCtrl::ChoiceEntryLeverageCtrl(const char* labelText) : - align(0, 0, 0, 0), - LabelWidget(labelText, align) -{ - for (int i = 0 ; i < 99 ; i++) { - if (controlChangeTexts[i]) { - combobox.append_text(controlChangeTexts[i]); +#if HAS_GTKMM_ALIGNMENT + LabelWidget(labelText, align), + align(0, 0, 0, 0) +#else + LabelWidget(labelText, combobox) +#endif +{ + for (int i = 0 ; i < controlChangeTextsSize ; i++) { + if (controlChangeTexts[i].txt) { + const int cc = i - 3; + Glib::ustring s = (i < 3) + ? controlChangeTexts[i].txt + : Glib::ustring::compose("CC%1: %2%3", cc, controlChangeTexts[i].txt, controlChangeTexts[i].isExtension ? " [EXT]" : ""); +#if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 24) || GTKMM_MAJOR_VERSION < 2 + combobox.append_text(s); +#else + combobox.append(s); +#endif } } combobox.signal_changed().connect( sigc::mem_fun(*this, &ChoiceEntryLeverageCtrl::value_changed)); +#if HAS_GTKMM_ALIGNMENT align.add(combobox); +#else + combobox.set_halign(Gtk::Align::FILL); + combobox.set_valign(Gtk::Align::FILL); +#endif value.type = gig::leverage_ctrl_t::type_none; value.controller_number = 0; } +void ChoiceEntryLeverageCtrl::on_show_tooltips_changed() { + LabelWidget::on_show_tooltips_changed(); + + const bool b = Settings::singleton()->showTooltips; + combobox.set_has_tooltip(b); +} + void ChoiceEntryLeverageCtrl::value_changed() { int rowno = combobox.get_active_row_number(); @@ -260,10 +474,19 @@ default: value.type = gig::leverage_ctrl_t::type_controlchange; int x = 3; - for (int cc = 0 ; cc < 96 ; cc++) { - if (controlChangeTexts[cc + 3]) { + for (uint cc = 0 ; cc < controlChangeTextsSize - 3 ; cc++) { + if (controlChangeTexts[cc + 3].txt) { if (rowno == x) { value.controller_number = cc; + if (controlChangeTexts[cc + 3].isExtension && + Settings::singleton()->warnUserOnExtensions) + { + Glib::ustring txt = _("Format Extension\n\nAll controllers marked with \"[EXT]\" are an extension to the original gig sound format. They will only work with LinuxSampler, but they will not work with Gigasampler/GigaStudio!\n\n(You may disable this warning in the Settings menu.)"); + Gtk::MessageDialog msg( + txt, true, Gtk::MESSAGE_WARNING + ); + msg.run(); + } break; } x++; @@ -276,35 +499,51 @@ void ChoiceEntryLeverageCtrl::set_value(gig::leverage_ctrl_t value) { - int x; + int comboIndex; switch (value.type) { case gig::leverage_ctrl_t::type_none: - x = 0; + comboIndex = 0; break; case gig::leverage_ctrl_t::type_channelaftertouch: - x = 1; + comboIndex = 1; break; case gig::leverage_ctrl_t::type_velocity: - x = 2; + comboIndex = 2; break; - case gig::leverage_ctrl_t::type_controlchange: - x = -1; - for (int cc = 0 ; cc < 96 ; cc++) { - if (controlChangeTexts[cc + 3]) { - x++; + case gig::leverage_ctrl_t::type_controlchange: { + comboIndex = -1; + int x = 3; + for (uint cc = 0 ; cc < controlChangeTextsSize - 3 ; cc++) { + if (controlChangeTexts[cc + 3].txt) { if (value.controller_number == cc) { - x += 3; + comboIndex = x; break; } + x++; } } break; + } default: - x = -1; + comboIndex = -1; break; } - combobox.set_active(x); + combobox.set_active(comboIndex); +} + + +BoolBox::BoolBox(const char* labelText) : Gtk::CheckButton(labelText) { + signal_toggled().connect(sig_changed.make_slot()); + Settings::singleton()->showTooltips.get_proxy().signal_changed().connect( + sigc::mem_fun(this, &BoolBox::on_show_tooltips_changed) + ); + on_show_tooltips_changed(); +} + +void BoolBox::on_show_tooltips_changed() { + const bool b = Settings::singleton()->showTooltips; + set_has_tooltip(b); } @@ -322,6 +561,16 @@ entry.signal_changed().connect(sig_changed.make_slot()); } +gig::String StringEntry::get_value() const +{ + return gig_from_utf8(entry.get_text()); +} + +void StringEntry::set_value(const gig::String& value) { + entry.set_text(gig_to_utf8(value)); +} + + StringEntryMultiLine::StringEntryMultiLine(const char* labelText) : LabelWidget(labelText, frame) { @@ -336,12 +585,68 @@ Glib::ustring value = text_buffer->get_text(); for (int i = 0 ; (i = value.find("\x0a", i)) >= 0 ; i += 2) value.replace(i, 1, "\x0d\x0a"); - return value; + return gig_from_utf8(value); +} + +void StringEntryMultiLine::set_value(const gig::String& value) +{ + Glib::ustring text = gig_to_utf8(value); + for (int i = 0 ; (i = text.find("\x0d\x0a", i, 2)) >= 0 ; i++) + text.replace(i, 2, "\x0a"); + text_buffer->set_text(text); +} + +void StringEntryMultiLine::on_show_tooltips_changed() { + LabelWidget::on_show_tooltips_changed(); + + const bool b = Settings::singleton()->showTooltips; + text_view.set_has_tooltip(b); +} + + +Table::Table(int x, int y) : +#if USE_GTKMM_GRID + Gtk::Grid(), + cols(x), +#else + Gtk::Table(x, y), +#endif + rowno(0) +{ +} + +void Table::add(BoolEntry& boolentry) +{ +#if USE_GTKMM_GRID + attach(boolentry.widget, 0, rowno, 2); +#else + attach(boolentry.widget, 0, 2, rowno, rowno + 1, + Gtk::FILL, Gtk::SHRINK); +#endif + rowno++; +} + +void Table::add(BoolEntryPlus6& boolentry) +{ +#if USE_GTKMM_GRID + attach(boolentry.widget, 0, rowno, 2); +#else + attach(boolentry.widget, 0, 2, rowno, rowno + 1, + Gtk::FILL, Gtk::SHRINK); +#endif + rowno++; } -void StringEntryMultiLine::set_value(gig::String value) +void Table::add(LabelWidget& prop) { - for (int i = 0 ; (i = value.find("\x0d\x0a", i, 2)) >= 0 ; i++) - value.replace(i, 2, "\x0a"); - text_buffer->set_text(value); +#if USE_GTKMM_GRID + attach(prop.label, 1, rowno); + attach(prop.widget, 2, rowno); +#else + attach(prop.label, 1, 2, rowno, rowno + 1, + Gtk::FILL, Gtk::SHRINK); + attach(prop.widget, 2, 3, rowno, rowno + 1, + Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK); +#endif + rowno++; }