From a94d4fe6192a2f0111515b952dc53f5bda8324db Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 16 Oct 2016 23:10:32 +0200 Subject: [PATCH] Make the guess button work. Right now, it only works across the entire bus; next up will be group-local extrapolation for the cases where whole-bus extrapolationd doesn't work. --- midi_mapping_dialog.cpp | 137 ++++++++++++++++++++++++++++++++++++++++ midi_mapping_dialog.h | 18 ++++++ 2 files changed, 155 insertions(+) diff --git a/midi_mapping_dialog.cpp b/midi_mapping_dialog.cpp index a6f6c68..20c2dee 100644 --- a/midi_mapping_dialog.cpp +++ b/midi_mapping_dialog.cpp @@ -10,6 +10,7 @@ #include #include +#include #include using namespace google::protobuf; @@ -131,10 +132,13 @@ MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper) ui->treeWidget->resizeColumnToContents(column_idx); } + connect(ui->guess_button, &QPushButton::clicked, this, &MIDIMappingDialog::guess_clicked); connect(ui->ok_cancel_buttons, &QDialogButtonBox::accepted, this, &MIDIMappingDialog::ok_clicked); connect(ui->ok_cancel_buttons, &QDialogButtonBox::rejected, this, &MIDIMappingDialog::cancel_clicked); connect(ui->save_button, &QPushButton::clicked, this, &MIDIMappingDialog::save_clicked); connect(ui->load_button, &QPushButton::clicked, this, &MIDIMappingDialog::load_clicked); + + update_guess_button_state(); } MIDIMappingDialog::~MIDIMappingDialog() @@ -142,6 +146,45 @@ MIDIMappingDialog::~MIDIMappingDialog() mapper->set_receiver(old_receiver); } +bool MIDIMappingDialog::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::FocusIn || + event->type() == QEvent::FocusOut) { + // We ignore the guess button itself; it should be allowed + // to navigate from a spinner to focus on the button (to click it). + if (obj != ui->guess_button) { + update_guess_button_state(); + } + } + return false; +} + +void MIDIMappingDialog::guess_clicked() +{ + int focus_bus_idx = find_focus_bus(); + if (focus_bus_idx == -1) { + // The guess button probably took the focus away from us. + focus_bus_idx = last_focus_bus_idx; + } + assert(focus_bus_idx != -1); // The button should have been disabled. + pair bus_and_offset = guess_offset(focus_bus_idx); + const int source_bus_idx = bus_and_offset.first; + const int offset = bus_and_offset.second; + assert(source_bus_idx != -1); // The button should have been disabled. + + for (const auto &field_number_and_spinner : spinners[focus_bus_idx]) { + int field_number = field_number_and_spinner.first; + QSpinBox *spinner = field_number_and_spinner.second; + + assert(spinners[source_bus_idx].count(field_number)); + QSpinBox *source_spinner = spinners[source_bus_idx][field_number]; + + if (source_spinner->value() != 0) { + spinner->setValue(source_spinner->value() + offset); + } + } +} + void MIDIMappingDialog::ok_clicked() { unique_ptr new_mapping = construct_mapping_proto_from_ui(); @@ -276,6 +319,7 @@ void MIDIMappingDialog::add_controls(const string &heading, spinner->setRange(0, 127); spinner->setAutoFillBackground(true); spinner->setSpecialValueText("\u200d"); // Zero-width joiner (ie., empty). + spinner->installEventFilter(this); // So we know when the focus changes. ui->treeWidget->setItemWidget(item, bus_idx + 2, spinner); if (control_type == ControlType::CONTROLLER) { @@ -284,6 +328,9 @@ void MIDIMappingDialog::add_controls(const string &heading, assert(control_type == ControlType::BUTTON); button_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, control.field_number }); } + spinners[bus_idx][control.field_number] = spinner; + connect(spinner, static_cast(&QSpinBox::valueChanged), + bind(&MIDIMappingDialog::update_guess_button_state, this)); } } ui->treeWidget->addTopLevelItem(heading_item); @@ -321,3 +368,93 @@ void MIDIMappingDialog::note_on(unsigned note) } } } + +pair MIDIMappingDialog::guess_offset(unsigned bus_idx) +{ + constexpr pair not_found(-1, 0); + + if (bus_is_empty(bus_idx)) { + return not_found; + } + + // See if we can find a non-empty bus to source from (prefer from the left). + unsigned source_bus_idx; + if (bus_idx > 0 && !bus_is_empty(bus_idx - 1)) { + source_bus_idx = bus_idx - 1; + } else if (bus_idx < num_buses - 1 && !bus_is_empty(bus_idx + 1)) { + source_bus_idx = bus_idx + 1; + } else { + return not_found; + } + + // See if we can find a consistent offset. + bool found_offset = false; + int offset = 0; + for (const auto &field_number_and_spinner : spinners[bus_idx]) { + int field_number = field_number_and_spinner.first; + QSpinBox *spinner = field_number_and_spinner.second; + + if (spinner->value() == 0) { + continue; + } + + assert(spinners[source_bus_idx].count(field_number)); + QSpinBox *source_spinner = spinners[source_bus_idx][field_number]; + if (source_spinner->value() == 0) { + // The bus has a controller set that the source bus doesn't set. + return not_found; + } + + int candidate_offset = spinner->value() - source_spinner->value(); + if (!found_offset) { + offset = candidate_offset; + found_offset = true; + } else if (candidate_offset != offset) { + return not_found; + } + } + + if (!found_offset) { + // Given that the bus wasn't empty, this shouldn't happen. + assert(false); + return not_found; + } + return make_pair(source_bus_idx, offset); +} + +bool MIDIMappingDialog::bus_is_empty(unsigned bus_idx) +{ + for (const auto &field_number_and_spinner : spinners[bus_idx]) { + QSpinBox *spinner = field_number_and_spinner.second; + if (spinner->value() != 0) { + return false; + } + } + return true; +} + +void MIDIMappingDialog::update_guess_button_state() +{ + int focus_bus_idx = find_focus_bus(); + if (focus_bus_idx < 0) { + return; + } + pair bus_and_offset = guess_offset(focus_bus_idx); + ui->guess_button->setEnabled(bus_and_offset.first != -1); + last_focus_bus_idx = focus_bus_idx; +} + +int MIDIMappingDialog::find_focus_bus() +{ + for (const InstantiatedSpinner &is : controller_spinners) { + if (is.spinner->hasFocus()) { + return is.bus_idx; + } + } + for (const InstantiatedSpinner &is : button_spinners) { + if (is.spinner->hasFocus()) { + return is.bus_idx; + } + } + return -1; +} diff --git a/midi_mapping_dialog.h b/midi_mapping_dialog.h index 6d9919b..efc770d 100644 --- a/midi_mapping_dialog.h +++ b/midi_mapping_dialog.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -28,6 +29,8 @@ public: MIDIMappingDialog(MIDIMapper *mapper); ~MIDIMappingDialog(); + bool eventFilter(QObject *obj, QEvent *event) override; + // For use in midi_mapping_dialog.cpp only. struct Control { std::string label; @@ -58,6 +61,7 @@ public: void note_on(unsigned note) override; public slots: + void guess_clicked(); void ok_clicked(); void cancel_clicked(); void save_clicked(); @@ -73,11 +77,22 @@ private: const MIDIMappingProto &mapping_proto, const std::vector &controls); void fill_controls_from_mapping(const MIDIMappingProto &mapping_proto); + // Tries to find a source bus and an offset to it that would give + // a consistent offset for the rest of the mappings in this bus. + // Returns -1 for the bus (the first element) if no consistent offset + // can be found. + std::pair guess_offset(unsigned bus_idx); + bool bus_is_empty(unsigned bus_idx); + + void update_guess_button_state(); + int find_focus_bus(); + std::unique_ptr construct_mapping_proto_from_ui(); Ui::MIDIMappingDialog *ui; MIDIMapper *mapper; ControllerReceiver *old_receiver; + int last_focus_bus_idx{-1}; // All controllers actually laid out on the grid (we need to store them // so that we can move values back and forth between the controls and @@ -94,6 +109,9 @@ private: std::vector controller_spinners; std::vector button_spinners; std::vector bank_combo_boxes; + + // Keyed on bus index, then field number. + std::map> spinners; }; #endif // !defined(_MIDI_MAPPING_DIALOG_H) -- 2.39.2