Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "mididevicemappingmodel.h"
24 :
25 : #include "rcommand/commandtypes.h"
26 : #include "translation.h"
27 :
28 : #include "log.h"
29 : #include <vector>
30 :
31 : using namespace muse::midi;
32 : using namespace muse::midiremote;
33 : using namespace muse::rcommand;
34 :
35 : static const QString TITLE_KEY("title");
36 : static const QString ICON_KEY("icon");
37 : static const QString STATUS_KEY("status");
38 : static const QString ENABLED_KEY("enabled");
39 : static const QString MAPPED_TYPE_KEY("mappedType");
40 : static const QString MAPPED_VALUE_KEY("mappedValue");
41 :
42 0 : inline static std::vector<Command> allMidiCommands()
43 : {
44 : //! TODO Move to config
45 : return {
46 0 : Command("command://playback/rewind"),
47 0 : Command("command://playback/loop-toggle"),
48 0 : Command("command://playback/play"),
49 0 : Command("command://playback/stop"),
50 0 : Command("command://notation/toggle-note-input"),
51 0 : Command("command://notation/set-duration-whole"),
52 0 : Command("command://notation/set-duration-half"),
53 0 : Command("command://notation/set-duration-quarter"),
54 0 : Command("command://notation/set-duration-eighth"),
55 0 : Command("command://notation/set-duration-16th"),
56 0 : Command("command://notation/set-duration-32nd"),
57 0 : Command("command://notation/set-duration-64th"),
58 0 : Command("command://notation/undo"),
59 0 : Command("command://notation/enter-rest"),
60 0 : Command("command://notation/toggle-rest"),
61 0 : Command("command://notation/toggle-tie"),
62 0 : Command("command://notation/toggle-dot"),
63 0 : Command("command://notation/toggle-dot2"),
64 0 : Command("command://notation/realtime-advance")
65 0 : };
66 0 : }
67 :
68 0 : MidiDeviceMappingModel::MidiDeviceMappingModel(QObject* parent)
69 0 : : QAbstractListModel(parent), Contextable(muse::iocCtxForQmlObject(this))
70 : {
71 0 : }
72 :
73 0 : QVariant MidiDeviceMappingModel::data(const QModelIndex& index, int role) const
74 : {
75 0 : if (!index.isValid()) {
76 0 : return QVariant();
77 : }
78 :
79 0 : QVariantMap obj = midiMappingToObject(m_midiMappings[index.row()]);
80 :
81 0 : switch (role) {
82 0 : case RoleTitle: return obj[TITLE_KEY].toString();
83 0 : case RoleIcon: return obj[ICON_KEY].toInt();
84 0 : case RoleStatus: return obj[STATUS_KEY].toString();
85 0 : case RoleEnabled: return obj[ENABLED_KEY].toBool();
86 0 : case RoleMappedType: return obj[MAPPED_TYPE_KEY].toInt();
87 0 : case RoleMappedValue: return obj[MAPPED_VALUE_KEY].toInt();
88 : }
89 :
90 0 : return QVariant();
91 0 : }
92 :
93 0 : QVariantMap MidiDeviceMappingModel::midiMappingToObject(const MidiControlsMapping& midiMapping) const
94 : {
95 0 : const CommandInfo& info = commandsRegister()->commandInfo(midiMapping.command);
96 :
97 0 : QVariantMap obj;
98 0 : obj[TITLE_KEY] = !info.description.isEmpty() ? info.description.qTranslated() : info.title.qTranslatedWithoutMnemonic();
99 0 : obj[ICON_KEY] = static_cast<int>(info.decoration.iconCode);
100 0 : obj[ENABLED_KEY] = midiMapping.isValid();
101 0 : obj[STATUS_KEY] = midiMapping.isValid() ? midiMapping.event.name().toQString() : muse::qtrc("global", "Inactive");
102 0 : obj[MAPPED_TYPE_KEY] = static_cast<int>(midiMapping.event.type);
103 0 : obj[MAPPED_VALUE_KEY] = midiMapping.event.value;
104 :
105 0 : return obj;
106 0 : }
107 :
108 0 : int MidiDeviceMappingModel::rowCount(const QModelIndex&) const
109 : {
110 0 : return static_cast<int>(m_midiMappings.size());
111 : }
112 :
113 0 : QHash<int, QByteArray> MidiDeviceMappingModel::roleNames() const
114 : {
115 : return {
116 0 : { RoleTitle, TITLE_KEY.toUtf8() },
117 0 : { RoleIcon, ICON_KEY.toUtf8() },
118 0 : { RoleStatus, STATUS_KEY.toUtf8() },
119 0 : { RoleEnabled, ENABLED_KEY.toUtf8() },
120 0 : { RoleMappedType, MAPPED_TYPE_KEY.toUtf8() },
121 0 : { RoleMappedValue, MAPPED_VALUE_KEY.toUtf8() }
122 0 : };
123 0 : }
124 :
125 0 : void MidiDeviceMappingModel::load()
126 : {
127 0 : midiConfiguration()->useRemoteControlChanged().onReceive(this, [this](bool val) {
128 0 : emit useRemoteControlChanged(val);
129 0 : }, muse::async::Asyncable::Mode::SetReplace);
130 :
131 0 : beginResetModel();
132 0 : m_midiMappings.clear();
133 :
134 0 : const MidiMappingList& midiMappings = midiRemote()->midiMappings();
135 :
136 0 : auto remoteEvent = [&midiMappings](const Command& command) {
137 0 : for (const MidiControlsMapping& midiMapping : midiMappings) {
138 0 : if (midiMapping.command == command) {
139 0 : return midiMapping.event;
140 : }
141 : }
142 :
143 0 : return RemoteEvent();
144 0 : };
145 :
146 0 : for (const Command& command : allMidiCommands()) {
147 0 : const CommandInfo& info = commandsRegister()->commandInfo(command);
148 :
149 0 : if (info.isValid()) {
150 0 : MidiControlsMapping midiMapping(command);
151 0 : midiMapping.event = remoteEvent(command);
152 0 : m_midiMappings.push_back(midiMapping);
153 0 : }
154 0 : }
155 :
156 0 : midiRemote()->midiMappingsChanged().onNotify(this, [this](){
157 0 : load();
158 0 : }, muse::async::Asyncable::Mode::SetReplace);
159 :
160 0 : endResetModel();
161 0 : }
162 :
163 0 : bool MidiDeviceMappingModel::apply()
164 : {
165 0 : Ret ret = midiRemote()->setMidiMappings(m_midiMappings);
166 0 : if (!ret) {
167 0 : LOGE() << ret.toString();
168 : }
169 :
170 0 : return ret;
171 0 : }
172 :
173 0 : void MidiDeviceMappingModel::reset()
174 : {
175 0 : midiRemote()->resetMidiMappings();
176 0 : }
177 :
178 0 : bool MidiDeviceMappingModel::useRemoteControl() const
179 : {
180 0 : return midiConfiguration()->useRemoteControl();
181 : }
182 :
183 0 : void MidiDeviceMappingModel::setUseRemoteControl(bool value)
184 : {
185 0 : if (value == useRemoteControl()) {
186 0 : return;
187 : }
188 :
189 0 : midiConfiguration()->setUseRemoteControl(value);
190 0 : emit useRemoteControlChanged(value);
191 : }
192 :
193 0 : QItemSelection MidiDeviceMappingModel::selection() const
194 : {
195 0 : return m_selection;
196 : }
197 :
198 0 : bool MidiDeviceMappingModel::canEditAction() const
199 : {
200 0 : return currentAction().isValid();
201 : }
202 :
203 0 : void MidiDeviceMappingModel::setSelection(const QItemSelection& selection)
204 : {
205 0 : if (selection == m_selection) {
206 0 : return;
207 : }
208 :
209 0 : m_selection = selection;
210 0 : emit selectionChanged(selection);
211 : }
212 :
213 0 : void MidiDeviceMappingModel::clearSelectedActions()
214 : {
215 0 : for (const QModelIndex& index : m_selection.indexes()) {
216 0 : m_midiMappings[index.row()].event = RemoteEvent();
217 0 : emit dataChanged(index, index);
218 0 : }
219 0 : }
220 :
221 0 : void MidiDeviceMappingModel::clearAllActions()
222 : {
223 0 : beginResetModel();
224 :
225 0 : for (MidiControlsMapping& midiMapping: m_midiMappings) {
226 0 : midiMapping.event = RemoteEvent();
227 : }
228 :
229 0 : endResetModel();
230 0 : }
231 :
232 0 : QVariant MidiDeviceMappingModel::currentAction() const
233 : {
234 0 : QModelIndexList indexes = m_selection.indexes();
235 0 : if (indexes.empty()) {
236 0 : return QVariant();
237 : }
238 :
239 0 : const MidiControlsMapping& midiMapping = m_midiMappings[indexes.first().row()];
240 0 : return midiMappingToObject(midiMapping);
241 0 : }
242 :
243 0 : void MidiDeviceMappingModel::mapCurrentActionToMidiEvent(const QVariant& event)
244 : {
245 0 : QModelIndexList indexes = m_selection.indexes();
246 0 : if (indexes.empty()) {
247 0 : return;
248 : }
249 :
250 0 : QVariantMap eventMap = event.toMap();
251 0 : RemoteEventType type = static_cast<RemoteEventType>(eventMap["type"].toInt());
252 0 : int value = eventMap["value"].toInt();
253 :
254 0 : QModelIndex first = indexes.first();
255 :
256 0 : m_midiMappings[first.row()].event = RemoteEvent(type, value);
257 0 : emit dataChanged(first, first);
258 0 : }
|