]> git.sesse.net Git - nageru/commitdiff
Make the guess button work.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 16 Oct 2016 21:10:32 +0000 (23:10 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 22 Oct 2016 13:16:23 +0000 (15:16 +0200)
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
midi_mapping_dialog.h

index a6f6c68702f077cbd61dac57f687eec9fa70db86..20c2dee46f0c0061b80f215dbd8b0b67178fade1 100644 (file)
@@ -10,6 +10,7 @@
 #include <QMessageBox>
 #include <QSpinBox>
 
+#include <algorithm>
 #include <string>
 
 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<int, int> 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<MIDIMappingProto> 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<void(QSpinBox::*)(int)>(&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<int, int> MIDIMappingDialog::guess_offset(unsigned bus_idx)
+{
+       constexpr pair<int, int> 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<int, int> 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;
+}
index 6d9919bbb93da5a70610f6faeec14632dd1eb5cc..efc770d28a1cc81433bd73cda828acd2e138062e 100644 (file)
@@ -3,6 +3,7 @@
 
 #include <QDialog>
 #include <string>
+#include <utility>
 #include <vector>
 #include <sys/time.h>
 
@@ -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<Control> &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<int, int> guess_offset(unsigned bus_idx);
+       bool bus_is_empty(unsigned bus_idx);
+
+       void update_guess_button_state();
+       int find_focus_bus();
+
        std::unique_ptr<MIDIMappingProto> 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<InstantiatedSpinner> controller_spinners;
        std::vector<InstantiatedSpinner> button_spinners;
        std::vector<InstantiatedComboBox> bank_combo_boxes;
+
+       // Keyed on bus index, then field number.
+       std::map<unsigned, std::map<unsigned, QSpinBox *>> spinners;
 };
 
 #endif  // !defined(_MIDI_MAPPING_DIALOG_H)