X-Git-Url: https://git.sesse.net/?p=nageru;a=blobdiff_plain;f=midi_mapping_dialog.cpp;h=20c2dee46f0c0061b80f215dbd8b0b67178fade1;hp=a6f6c68702f077cbd61dac57f687eec9fa70db86;hb=a94d4fe6192a2f0111515b952dc53f5bda8324db;hpb=c93433b2669d3447f8c2e1dfcae0d491b2d3a4dc 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; +}