]> git.sesse.net Git - nageru/commitdiff
Support group-local extrapolation in the MIDI mapping editor.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 17 Oct 2016 16:48:45 +0000 (18:48 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 22 Oct 2016 13:16:23 +0000 (15:16 +0200)
midi_mapping_dialog.cpp
midi_mapping_dialog.h
ui_midi_mapping.ui

index 20c2dee46f0c0061b80f215dbd8b0b67178fade1..6d2159d8a079dfb0d2e49f630895d36587f6f6d8 100644 (file)
@@ -121,10 +121,10 @@ MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper)
        ui->treeWidget->setColumnCount(num_buses + 3);
        ui->treeWidget->setHeaderLabels(labels);
 
-       add_controls("Per-bus controllers", ControlType::CONTROLLER, mapping_proto, per_bus_controllers);
-       add_controls("Per-bus buttons", ControlType::BUTTON, mapping_proto, per_bus_buttons);
-       add_controls("Global controllers", ControlType::CONTROLLER, mapping_proto, global_controllers);
-       add_controls("Global buttons", ControlType::BUTTON, mapping_proto, global_buttons);
+       add_controls("Per-bus controllers", ControlType::CONTROLLER, SpinnerGroup::PER_BUS_CONTROLLERS, mapping_proto, per_bus_controllers);
+       add_controls("Per-bus buttons", ControlType::BUTTON, SpinnerGroup::PER_BUS_BUTTONS, mapping_proto, per_bus_buttons);
+       add_controls("Global controllers", ControlType::CONTROLLER, SpinnerGroup::GLOBAL_CONTROLLERS, mapping_proto, global_controllers);
+       add_controls("Global buttons", ControlType::BUTTON, SpinnerGroup::GLOBAL_BUTTONS, mapping_proto, global_buttons);
        fill_controls_from_mapping(mapping_proto);
 
        // Auto-resize every column but the last.
@@ -132,7 +132,10 @@ MIDIMappingDialog::MIDIMappingDialog(MIDIMapper *mapper)
                ui->treeWidget->resizeColumnToContents(column_idx);
        }
 
-       connect(ui->guess_button, &QPushButton::clicked, this, &MIDIMappingDialog::guess_clicked);
+       connect(ui->guess_bus_button, &QPushButton::clicked,
+               bind(&MIDIMappingDialog::guess_clicked, this, false));
+       connect(ui->guess_group_button, &QPushButton::clicked,
+               bind(&MIDIMappingDialog::guess_clicked, this, true));
        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);
@@ -150,34 +153,40 @@ 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) {
+               // We ignore the guess buttons themselves; it should be allowed
+               // to navigate from a spinner to focus on a button (to click it).
+               if (obj != ui->guess_bus_button && obj != ui->guess_group_button) {
                        update_guess_button_state();
                }
        }
        return false;
 }
 
-void MIDIMappingDialog::guess_clicked()
+void MIDIMappingDialog::guess_clicked(bool limit_to_group)
 {
-       int focus_bus_idx = find_focus_bus();
-       if (focus_bus_idx == -1) {
+       FocusInfo focus = find_focus();
+       if (focus.bus_idx == -1) {
                // The guess button probably took the focus away from us.
-               focus_bus_idx = last_focus_bus_idx;
+               focus = last_focus;
        }
-       assert(focus_bus_idx != -1);  // The button should have been disabled.
-       pair<int, int> bus_and_offset = guess_offset(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, limit_to_group ? focus.spinner_group : SpinnerGroup::ALL_GROUPS);
        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]) {
+       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;
+               QSpinBox *spinner = field_number_and_spinner.second.spinner;
+               SpinnerGroup this_spinner_group = field_number_and_spinner.second.group;
+
+               if (limit_to_group && this_spinner_group != focus.spinner_group) {
+                       continue;
+               }
 
                assert(spinners[source_bus_idx].count(field_number));
-               QSpinBox *source_spinner = spinners[source_bus_idx][field_number];
+               QSpinBox *source_spinner = spinners[source_bus_idx][field_number].spinner;
+               assert(spinners[source_bus_idx][field_number].group == this_spinner_group);
 
                if (source_spinner->value() != 0) {
                        spinner->setValue(source_spinner->value() + offset);
@@ -301,6 +310,7 @@ void MIDIMappingDialog::add_bank_selector(QTreeWidgetItem *item, const MIDIMappi
 
 void MIDIMappingDialog::add_controls(const string &heading,
                                      MIDIMappingDialog::ControlType control_type,
+                                     MIDIMappingDialog::SpinnerGroup spinner_group,
                                      const MIDIMappingProto &mapping_proto,
                                      const vector<MIDIMappingDialog::Control> &controls)
 {
@@ -323,12 +333,12 @@ void MIDIMappingDialog::add_controls(const string &heading,
                        ui->treeWidget->setItemWidget(item, bus_idx + 2, spinner);
 
                        if (control_type == ControlType::CONTROLLER) {
-                               controller_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, control.field_number });
+                               controller_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, spinner_group, control.field_number });
                        } else {
                                assert(control_type == ControlType::BUTTON);
-                               button_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, control.field_number });
+                               button_spinners.push_back(InstantiatedSpinner{ spinner, bus_idx, spinner_group, control.field_number });
                        }
-                       spinners[bus_idx][control.field_number] = spinner;
+                       spinners[bus_idx][control.field_number] = SpinnerAndGroup{ spinner, spinner_group };
                        connect(spinner, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
                                bind(&MIDIMappingDialog::update_guess_button_state, this));
                }
@@ -369,19 +379,19 @@ void MIDIMappingDialog::note_on(unsigned note)
        }
 }
 
-pair<int, int> MIDIMappingDialog::guess_offset(unsigned bus_idx)
+pair<int, int> MIDIMappingDialog::guess_offset(unsigned bus_idx, MIDIMappingDialog::SpinnerGroup spinner_group)
 {
        constexpr pair<int, int> not_found(-1, 0);
 
-       if (bus_is_empty(bus_idx)) {
+       if (bus_is_empty(bus_idx, spinner_group)) {
                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)) {
+       if (bus_idx > 0 && !bus_is_empty(bus_idx - 1, spinner_group)) {
                source_bus_idx = bus_idx - 1;
-       } else if (bus_idx < num_buses - 1 && !bus_is_empty(bus_idx + 1)) {
+       } else if (bus_idx < num_buses - 1 && !bus_is_empty(bus_idx + 1, spinner_group)) {
                source_bus_idx = bus_idx + 1;
        } else {
                return not_found;
@@ -392,14 +402,20 @@ pair<int, int> MIDIMappingDialog::guess_offset(unsigned bus_idx)
        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;
+               QSpinBox *spinner = field_number_and_spinner.second.spinner;
+               SpinnerGroup this_spinner_group = field_number_and_spinner.second.group;
 
                if (spinner->value() == 0) {
                        continue;
                }
+               if (spinner_group != SpinnerGroup::ALL_GROUPS &&
+                   spinner_group != this_spinner_group) {
+                       continue;
+               }
 
                assert(spinners[source_bus_idx].count(field_number));
-               QSpinBox *source_spinner = spinners[source_bus_idx][field_number];
+               QSpinBox *source_spinner = spinners[source_bus_idx][field_number].spinner;
+               assert(spinners[source_bus_idx][field_number].group == this_spinner_group);
                if (source_spinner->value() == 0) {
                        // The bus has a controller set that the source bus doesn't set.
                        return not_found;
@@ -422,10 +438,15 @@ pair<int, int> MIDIMappingDialog::guess_offset(unsigned bus_idx)
        return make_pair(source_bus_idx, offset);
 }
 
-bool MIDIMappingDialog::bus_is_empty(unsigned bus_idx)
+bool MIDIMappingDialog::bus_is_empty(unsigned bus_idx, SpinnerGroup spinner_group)
 {
        for (const auto &field_number_and_spinner : spinners[bus_idx]) {
-               QSpinBox *spinner = field_number_and_spinner.second;
+               QSpinBox *spinner = field_number_and_spinner.second.spinner;
+               SpinnerGroup this_spinner_group = field_number_and_spinner.second.group;
+               if (spinner_group != SpinnerGroup::ALL_GROUPS &&
+                   spinner_group != this_spinner_group) {
+                       continue;
+               }
                if (spinner->value() != 0) {
                        return false;
                }
@@ -435,26 +456,32 @@ bool MIDIMappingDialog::bus_is_empty(unsigned bus_idx)
 
 void MIDIMappingDialog::update_guess_button_state()
 {
-       int focus_bus_idx = find_focus_bus();
-       if (focus_bus_idx < 0) {
+       FocusInfo focus = find_focus();
+       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;
+       {
+               pair<int, int> bus_and_offset = guess_offset(focus.bus_idx, SpinnerGroup::ALL_GROUPS);
+               ui->guess_bus_button->setEnabled(bus_and_offset.first != -1);
+       }
+       {
+               pair<int, int> bus_and_offset = guess_offset(focus.bus_idx, focus.spinner_group);
+               ui->guess_group_button->setEnabled(bus_and_offset.first != -1);
+       }
+       last_focus = focus;
 }
 
-int MIDIMappingDialog::find_focus_bus()
+MIDIMappingDialog::FocusInfo MIDIMappingDialog::find_focus() const
 {
        for (const InstantiatedSpinner &is : controller_spinners) {
                if (is.spinner->hasFocus()) {
-                       return is.bus_idx;
+                       return FocusInfo{ int(is.bus_idx), is.spinner_group };
                }
        }
        for (const InstantiatedSpinner &is : button_spinners) {
                if (is.spinner->hasFocus()) {
-                       return is.bus_idx;
+                       return FocusInfo{ int(is.bus_idx), is.spinner_group };
                }
        }
-       return -1;
+       return FocusInfo{ -1, SpinnerGroup::ALL_GROUPS };
 }
index efc770d28a1cc81433bd73cda828acd2e138062e..df4e97f3c066b4cad0365d9d157b145bede6902b 100644 (file)
@@ -61,7 +61,7 @@ public:
        void note_on(unsigned note) override;
 
 public slots:
-       void guess_clicked();
+       void guess_clicked(bool limit_to_group);
        void ok_clicked();
        void cancel_clicked();
        void save_clicked();
@@ -70,29 +70,44 @@ public slots:
 private:
        static constexpr unsigned num_buses = 8;
 
+       // Each spinner belongs to exactly one group, corresponding to the
+       // subheadings in the UI. This is so that we can extrapolate data
+       // across only single groups if need be.
+       enum class SpinnerGroup {
+               ALL_GROUPS = -1,
+               PER_BUS_CONTROLLERS,
+               PER_BUS_BUTTONS,
+               GLOBAL_CONTROLLERS,
+               GLOBAL_BUTTONS
+       };
+
        void add_bank_selector(QTreeWidgetItem *item, const MIDIMappingProto &mapping_proto, int bank_field_number);
        
        enum class ControlType { CONTROLLER, BUTTON };
        void add_controls(const std::string &heading, ControlType control_type,
+                         SpinnerGroup spinner_group,
                          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);
+       // Returns -1 for the bus if no consistent offset can be found.
+       std::pair<int, int> guess_offset(unsigned bus_idx, SpinnerGroup spinner_group);
+       bool bus_is_empty(unsigned bus_idx, SpinnerGroup spinner_group);
 
        void update_guess_button_state();
-       int find_focus_bus();
+       struct FocusInfo {
+               int bus_idx;  // -1 for none.
+               SpinnerGroup spinner_group;
+       };
+       FocusInfo find_focus() const;
 
        std::unique_ptr<MIDIMappingProto> construct_mapping_proto_from_ui();
 
        Ui::MIDIMappingDialog *ui;
        MIDIMapper *mapper;
        ControllerReceiver *old_receiver;
-       int last_focus_bus_idx{-1};
+       FocusInfo last_focus{-1, SpinnerGroup::ALL_GROUPS};
 
        // 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
@@ -100,6 +115,7 @@ private:
        struct InstantiatedSpinner {
                QSpinBox *spinner;
                unsigned bus_idx;
+               SpinnerGroup spinner_group;
                int field_number;  // In MIDIMappingBusProto.
        };
        struct InstantiatedComboBox {
@@ -111,7 +127,11 @@ private:
        std::vector<InstantiatedComboBox> bank_combo_boxes;
 
        // Keyed on bus index, then field number.
-       std::map<unsigned, std::map<unsigned, QSpinBox *>> spinners;
+       struct SpinnerAndGroup {
+               QSpinBox *spinner;
+               SpinnerGroup group;
+       };
+       std::map<unsigned, std::map<unsigned, SpinnerAndGroup>> spinners;
 };
 
 #endif  // !defined(_MIDI_MAPPING_DIALOG_H)
index 9e4c6ec32fe5379b0ac1e12e03e486f52cab135e..2771dfff587824d95e135dc3e05dbf0a193551a3 100644 (file)
     </widget>
    </item>
    <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1,0,0,1,0">
+    <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0,1,0,0,1,0">
      <item>
-      <widget class="QPushButton" name="guess_button">
+      <widget class="QPushButton" name="guess_bus_button">
        <property name="text">
         <string>Guess &amp;bus</string>
        </property>
       </widget>
      </item>
+     <item>
+      <widget class="QPushButton" name="guess_group_button">
+       <property name="text">
+        <string>Guess &amp;group</string>
+       </property>
+      </widget>
+     </item>
      <item>
       <spacer name="horizontalSpacer_2">
        <property name="orientation">