Merge lp:~hile/mixxx/presetinfo into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Ilkka Tuohela
Status: Merged
Merged at revision: 3228
Proposed branch: lp:~hile/mixxx/presetinfo
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 1143 lines (+660/-83)
15 files modified
mixxx/src/controllers/controller.cpp (+2/-0)
mixxx/src/controllers/controller.h (+10/-0)
mixxx/src/controllers/controllermanager.cpp (+36/-31)
mixxx/src/controllers/controllermanager.h (+8/-1)
mixxx/src/controllers/controllerpreset.h (+7/-0)
mixxx/src/controllers/controllerpresetinfo.cpp (+232/-0)
mixxx/src/controllers/controllerpresetinfo.h (+87/-0)
mixxx/src/controllers/dlgprefcontroller.cpp (+64/-12)
mixxx/src/controllers/dlgprefcontroller.h (+5/-1)
mixxx/src/controllers/dlgprefcontrollerdlg.ui (+128/-38)
mixxx/src/controllers/hid/hidcontroller.cpp (+64/-0)
mixxx/src/controllers/hid/hidcontroller.h (+3/-0)
mixxx/src/controllers/midi/midicontroller.cpp (+5/-0)
mixxx/src/controllers/midi/midicontroller.h (+2/-0)
mixxx/src/dlgpreferences.cpp (+7/-0)
To merge this branch: bzr merge lp:~hile/mixxx/presetinfo
Reviewer Review Type Date Requested Status
Mixxx Development Team Pending
Review via email: mp+106001@code.launchpad.net

Description of the change

Patches for reading the info section from controller XML files, and using this information in the controller dialogs. Implements a presetInfo container and manager class, linked to ControllerManager.

Other minor details in the branch:
- added presetinfo product matching to controller, allows automatic mapping matching to controller
- implement controller ordering by name, use this in preferences
- added methods to process 'device category', for informational purposes only
- add a DeviceCategory field to controller preferences, show HID device info here. This helps choosing between keyboard and mice etc
- minor reformatting of the UI for controllers, adding description and making the text fields wrapped and static width

To post a comment you must log in.
lp:~hile/mixxx/presetinfo updated
3166. By Ilkka Tuohela

Merged to trunk

3167. By Ilkka Tuohela

Merged latest mixxx trunk

3168. By Ilkka Tuohela

Merged changes from trunk

3169. By Ilkka Tuohela

Imported latest changes from trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mixxx/src/controllers/controller.cpp'
2--- mixxx/src/controllers/controller.cpp 2012-05-26 23:32:16 +0000
3+++ mixxx/src/controllers/controller.cpp 2012-05-29 05:49:18 +0000
4@@ -22,6 +22,8 @@
5 // Get --controllerDebug command line option
6 QStringList commandLineArgs = QApplication::arguments();
7 m_bDebug = commandLineArgs.contains("--controllerDebug", Qt::CaseInsensitive);
8+ // Initialize device category to empty
9+ setDeviceCategory("");
10 }
11
12 Controller::~Controller() {
13
14=== modified file 'mixxx/src/controllers/controller.h'
15--- mixxx/src/controllers/controller.h 2012-05-26 23:19:28 +0000
16+++ mixxx/src/controllers/controller.h 2012-05-29 05:49:18 +0000
17@@ -54,6 +54,9 @@
18 inline QString getName() const {
19 return m_sDeviceName;
20 }
21+ inline QString getCategory() const {
22+ return m_sDeviceCategory;
23+ }
24 inline bool debugging() const {
25 return m_bDebug;
26 }
27@@ -62,6 +65,8 @@
28 return m_bLearning;
29 }
30
31+ virtual bool matchProductInfo(QHash <QString,QString > info) = 0;
32+
33 signals:
34 void learnedMessage(QString message);
35 // Emitted when a new preset is loaded. pPreset is a /clone/ of the loaded
36@@ -102,6 +107,9 @@
37 inline void setDeviceName(QString deviceName) {
38 m_sDeviceName = deviceName;
39 }
40+ inline void setDeviceCategory(QString deviceCategory) {
41+ m_sDeviceCategory = deviceCategory;
42+ }
43 inline void setOutputDevice(bool outputDevice) {
44 m_bIsOutputDevice = outputDevice;
45 }
46@@ -141,6 +149,8 @@
47
48 // Verbose and unique device name suitable for display.
49 QString m_sDeviceName;
50+ // Verbose and unique description of device type, defaults to empty
51+ QString m_sDeviceCategory;
52 // Flag indicating if this device supports output (receiving data from
53 // Mixxx)
54 bool m_bIsOutputDevice;
55
56=== modified file 'mixxx/src/controllers/controllermanager.cpp'
57--- mixxx/src/controllers/controllermanager.cpp 2012-05-01 14:45:33 +0000
58+++ mixxx/src/controllers/controllermanager.cpp 2012-05-29 05:49:18 +0000
59@@ -37,6 +37,10 @@
60 return filename;
61 }
62
63+bool controllerCompare(Controller *a,Controller *b) {
64+ return a->getName() < b->getName();
65+}
66+
67 ControllerManager::ControllerManager(ConfigObject<ConfigValue> * pConfig) :
68 QObject(),
69 m_pConfig(pConfig),
70@@ -58,6 +62,9 @@
71 QDir().mkpath(LOCAL_PRESETS_PATH);
72 }
73
74+ // Initialize preset info parsers
75+ presetInfoManager = new PresetInfoEnumerator(m_pConfig);
76+
77 // Instantiate all enumerators
78 m_enumerators.append(new PortMidiEnumerator());
79 #ifdef __HSS1394__
80@@ -253,34 +260,6 @@
81 }
82 }
83
84-QList<QString> ControllerManager::getPresetList(QString extension) {
85- // Paths to search for controller presets
86- QList<QString> controllerDirPaths;
87- QString configPath = m_pConfig->getConfigPath();
88- controllerDirPaths.append(configPath.append("controllers/"));
89- controllerDirPaths.append(LOCAL_PRESETS_PATH);
90-
91- // Should we also show the presets from USER_PRESETS_PATH? That could be
92- // confusing.
93-
94- QList<QString> presets;
95- foreach (QString dirPath, controllerDirPaths) {
96- QDirIterator it(dirPath);
97- while (it.hasNext()) {
98- // Advance iterator. We get the filename from the next line. (It's a
99- // bit weird.)
100- it.next();
101-
102- QString curPreset = it.fileName();
103- if (curPreset.endsWith(extension)) {
104- curPreset.chop(QString(extension).length()); //chop off the extension
105- presets.append(curPreset);
106- }
107- }
108- }
109- qSort(presets);
110- return presets;
111-}
112
113 void ControllerManager::openController(Controller* pController) {
114 if (!pController) {
115@@ -329,7 +308,6 @@
116 return true;
117 }
118
119-
120 bool ControllerManager::loadPreset(Controller* pController,
121 const QString &filename,
122 const bool force) {
123@@ -340,8 +318,18 @@
124 return false;
125 }
126
127- QString filenameWithExt = filename + pController->presetExtension();
128- QString filepath = USER_PRESETS_PATH + filenameWithExt;
129+ // Handle case when filename is already valid full path to mapping
130+ // (coming from presetInfo.path)
131+ QString filenameWithExt;
132+ QString filepath;
133+ QFileInfo fileinfo(filename);
134+ if (fileinfo.isFile()) {
135+ filenameWithExt = fileinfo.baseName();
136+ filepath = fileinfo.absoluteFilePath();
137+ } else {
138+ filenameWithExt = filename + pController->presetExtension();
139+ filepath = USER_PRESETS_PATH + filenameWithExt;
140+ }
141
142 // If the file isn't present in the user's directory, check the local
143 // presets path.
144@@ -374,6 +362,23 @@
145 return true;
146 }
147
148+PresetInfoEnumerator* ControllerManager::getPresetInfoManager() {
149+ return presetInfoManager;
150+}
151+
152+PresetInfo* ControllerManager::matchControllerPreset(Controller *controller) {
153+ QList<PresetInfo*> presets = presetInfoManager->getPresets(controller->presetExtension());
154+ foreach (PresetInfo* preset, presets) {
155+ QList< QHash<QString,QString> > products = preset->getProducts();
156+ for (int i=0;i<products.length();i++) {
157+ QHash <QString,QString> product = products[i];
158+ if (controller->matchProductInfo(product))
159+ return preset;
160+ }
161+ }
162+ return NULL;
163+}
164+
165 void ControllerManager::slotSavePresets(bool onlyActive) {
166 QList<Controller*> deviceList = getControllerList(false, true);
167 QSet<QString> filenames;
168
169=== modified file 'mixxx/src/controllers/controllermanager.h'
170--- mixxx/src/controllers/controllermanager.h 2012-04-28 04:12:06 +0000
171+++ mixxx/src/controllers/controllermanager.h 2012-05-29 05:49:18 +0000
172@@ -11,11 +11,15 @@
173 #include "configobject.h"
174 #include "controllers/controllerenumerator.h"
175 #include "controllers/controllerpreset.h"
176+#include "controllers/controllerpresetinfo.h"
177
178 //Forward declaration(s)
179 class Controller;
180 class ControllerLearningEventFilter;
181
182+// Function to sort controllers by name
183+bool controllerCompare(Controller *a,Controller *b);
184+
185 /** Manages enumeration/operation/deletion of hardware controllers. */
186 class ControllerManager : public QObject {
187 Q_OBJECT
188@@ -25,7 +29,6 @@
189
190 QList<Controller*> getControllers() const;
191 QList<Controller*> getControllerList(bool outputDevices=true, bool inputDevices=true);
192- QList<QString> getPresetList(QString extension);
193 ControllerLearningEventFilter* getControllerLearningEventFilter() const;
194
195 // Prevent other parts of Mixxx from having to manually connect to our slots
196@@ -47,6 +50,9 @@
197
198 // Writes out presets for currently connected input devices
199 void slotSavePresets(bool onlyActive=false);
200+
201+ PresetInfo* matchControllerPreset(Controller *controller);
202+ PresetInfoEnumerator* getPresetInfoManager();
203
204 private slots:
205 // Open whatever controllers are selected in the preferences. This currently
206@@ -76,6 +82,7 @@
207 QList<ControllerEnumerator*> m_enumerators;
208 QList<Controller*> m_controllers;
209 QThread* m_pThread;
210+ PresetInfoEnumerator* presetInfoManager;
211 };
212
213 #endif // CONTROLLERMANAGER_H
214
215=== modified file 'mixxx/src/controllers/controllerpreset.h'
216--- mixxx/src/controllers/controllerpreset.h 2012-04-29 04:31:26 +0000
217+++ mixxx/src/controllers/controllerpreset.h 2012-05-29 05:49:18 +0000
218@@ -12,6 +12,7 @@
219 #define CONTROLLERPRESET_H
220
221 #include <QtCore>
222+#include <QHash>
223 #include <QSharedPointer>
224
225 class ControllerPresetVisitor;
226@@ -87,6 +88,10 @@
227 return m_mixxxVersion;
228 }
229
230+ inline void addProductMatch(QHash<QString,QString> match) {
231+ m_productMatches.append(match);
232+ }
233+
234 virtual void accept(ControllerPresetVisitor* visitor) const = 0;
235 virtual bool isMappable() const = 0;
236
237@@ -94,6 +99,8 @@
238 // TODO(XXX) make private
239 QList<QString> scriptFileNames;
240 QList<QString> scriptFunctionPrefixes;
241+ // Optional list of controller device match details
242+ QList< QHash<QString,QString> > m_productMatches;
243
244 private:
245 QString m_deviceId;
246
247=== added file 'mixxx/src/controllers/controllerpresetinfo.cpp'
248--- mixxx/src/controllers/controllerpresetinfo.cpp 1970-01-01 00:00:00 +0000
249+++ mixxx/src/controllers/controllerpresetinfo.cpp 2012-05-29 05:49:18 +0000
250@@ -0,0 +1,232 @@
251+/**
252+* @file controllerpresetinfo.cpp
253+* @author Ilkka Tuohela hile@iki.fi
254+* @date Wed May 15 2012
255+* @brief Implement handling enumeration and parsing of preset info headers
256+*
257+* This class handles enumeration and parsing of controller XML description file
258+* <info> header tags. It can be used to match controllers automatically or to
259+* show details for a mapping.
260+*/
261+
262+#include "controllers/controllerpresetinfo.h"
263+
264+PresetInfo::PresetInfo(const QString preset_path) {
265+ // Parse <info> header section from a controller description XML file
266+ // Contents parsed by xml path:
267+ // info.name Mapping name, used for drop down menus in dialogs
268+ // info.author Mapping author
269+ // info.description Mapping description
270+ // info.devices.product List of device matches, specific to device type
271+ path = QFileInfo(preset_path).absoluteFilePath();
272+ name = "";
273+ author = "";
274+ description = "";
275+
276+ QDomElement root = XmlParse::openXMLFile(path, "controller");
277+ if (root.isNull()) {
278+ qDebug() << "ERROR parsing" << path;
279+ return;
280+ }
281+ QDomElement info = root.firstChildElement("info");
282+ if (info.isNull()) {
283+ qDebug() << "MISSING <info> ELEMENT: " << path;
284+ return;
285+ }
286+ QDomElement dom_name = info.firstChildElement("name");
287+ if (!dom_name.isNull()) name = dom_name.text();
288+
289+ QDomElement dom_author = info.firstChildElement("author");
290+ if (!dom_author.isNull()) author = dom_author.text();
291+
292+ QDomElement dom_description = info.firstChildElement("description");
293+ if (!dom_description.isNull()) description = dom_description.text();
294+
295+ QDomElement devices = info.firstChildElement("devices");
296+ if (!devices.isNull()) {
297+ QDomElement product = devices.firstChildElement("product");
298+ while (!product.isNull()) {
299+ QString protocol = product.attribute("protocol","");
300+ if (protocol=="hid") {
301+ products.append(parseHIDProduct(product));
302+ } else if (protocol=="midi") {
303+ qDebug("MIDI product info parsing not yet implemented");
304+ //products.append(parseMIDIProduct(product);
305+ } else if (protocol=="osc") {
306+ qDebug("OSC product info parsing not yet implemented");
307+ //products.append(parseOSCProduct(product);
308+ } else {
309+ qDebug("Product specification missing protocol attribute");
310+ }
311+ product = product.nextSiblingElement("product");
312+ }
313+ }
314+}
315+
316+QHash<QString,QString> PresetInfo::parseHIDProduct(const QDomElement& element) const {
317+ // HID device <product> element parsing. Example of valid element:
318+ // <product protocol="hid" vendor_id="0x1" product_id="0x2" usage_page="0x3" usage="0x4" interface_number="0x3" />
319+ // All numbers must be hex prefixed with 0x
320+ // Only vendor_id and product_id fields are required to map a device.
321+ // usage_page and usage are matched on OS/X and windows
322+ // interface_number is matched on linux, which does support usage_page/usage
323+
324+ QHash<QString,QString> product;
325+ product.insert("procotol",element.attribute("protocol",""));
326+ product.insert("vendor_id",element.attribute("vendor_id",""));
327+ product.insert("product_id",element.attribute("product_id",""));
328+ product.insert("usage_page",element.attribute("usage_page",""));
329+ product.insert("usage",element.attribute("usage",""));
330+ product.insert("interface_number",element.attribute("interface_number",""));
331+ return product;
332+}
333+
334+QHash<QString,QString> PresetInfo::parseMIDIProduct(const QDomElement& element) const {
335+ // TODO - implement parsing of MIDI attributes
336+ // When done, remember to fix switch() above to call this
337+ QHash<QString,QString> product;
338+ product.insert("procotol",element.attribute("protocol",""));
339+ return product;
340+}
341+
342+QHash<QString,QString> PresetInfo::parseOSCProduct(const QDomElement& element) const {
343+ // TODO - implement parsing of OSC product attributes
344+ // When done, remember to fix switch() above to call this
345+ QHash<QString,QString> product;
346+ product.insert("procotol",element.attribute("protocol",""));
347+ return product;
348+}
349+
350+PresetInfoEnumerator::PresetInfoEnumerator(ConfigObject<ConfigValue> *pConfig)
351+ : m_pConfig(pConfig) {
352+
353+ QString configPath = m_pConfig->getConfigPath();
354+ controllerDirPaths.append(configPath.append("controllers/"));
355+ controllerDirPaths.append(LOCAL_PRESETS_PATH);
356+
357+ // Static list of supported default extensions, sorted by popularity
358+ fileExtensions.append(QString(".midi.xml"));
359+ fileExtensions.append(QString(".cntrlr.xml"));
360+ fileExtensions.append(QString(".hid.xml"));
361+ fileExtensions.append(QString(".osc.xml"));
362+
363+ loadSupportedPresets();
364+}
365+
366+bool PresetInfoEnumerator::isValidExtension(const QString extension) {
367+ if (presetsByExtension.contains(extension))
368+ return true;
369+ return false;
370+}
371+
372+bool PresetInfoEnumerator::hasPresetInfo(const QString extension, const QString name) {
373+ // Check if preset info matching extension and preset name can be found
374+ if (!isValidExtension(extension))
375+ return false;
376+ foreach (QString extension, presetsByExtension.keys()) {
377+ QMap <QString,PresetInfo*> presets = presetsByExtension[extension];
378+ foreach (PresetInfo* preset, presets.values())
379+ if (name==preset->getName())
380+ return true;
381+ }
382+ return false;
383+}
384+
385+bool PresetInfoEnumerator::hasPresetInfo(const QString path) {
386+ foreach (QString extension, presetsByExtension.keys()) {
387+ QMap <QString,PresetInfo*> presets = presetsByExtension[extension];
388+ if (presets.contains(path))
389+ return true;
390+ }
391+ return false;
392+}
393+
394+PresetInfo* PresetInfoEnumerator::getPresetInfo(const QString extension, const QString name) {
395+ QList<PresetInfo*> extension_presets;
396+ if (!isValidExtension(extension))
397+ return NULL;
398+
399+ foreach (QString extension, presetsByExtension.keys()) {
400+ QMap <QString,PresetInfo*> presets = presetsByExtension[extension];
401+ foreach (PresetInfo* preset, presets.values())
402+ if (name==preset->getName())
403+ return preset;
404+ }
405+ return NULL;
406+}
407+
408+PresetInfo* PresetInfoEnumerator::getPresetInfo(const QString path) {
409+ // Lookup and return controller script preset info by script path
410+ // Return NULL if path is not found.
411+ foreach (QString extension, presetsByExtension.keys()) {
412+ QMap <QString,PresetInfo*> presets = presetsByExtension[extension];
413+ if (presets.contains(path))
414+ return presets[path];
415+ }
416+ return NULL;
417+}
418+
419+QList<PresetInfo*> PresetInfoEnumerator::getPresets(const QString extension) {
420+ // Return list of PresetInfo items matching extension
421+ // Returns empty list if no matching extension presets can be found
422+ QList <PresetInfo*> presets;
423+ if (presetsByExtension.contains(extension)) {
424+ presets = presetsByExtension[extension].values();
425+ return presetsByExtension[extension].values();
426+ }
427+ qDebug() << "Extension not registered to presetinfo" << extension;
428+ return presets;
429+}
430+
431+void PresetInfoEnumerator::addExtension(const QString extension) {
432+ if (presetsByExtension.contains(extension))
433+ return;
434+ QMap <QString,PresetInfo*> presets;
435+ presetsByExtension[extension] = presets;
436+}
437+
438+void PresetInfoEnumerator::loadSupportedPresets() {
439+
440+ foreach (QString dirPath, controllerDirPaths) {
441+ QDirIterator it(dirPath);
442+ while (it.hasNext()) {
443+ it.next();
444+ const QString path = it.filePath();
445+ foreach (QString extension, fileExtensions) {
446+ if (!path.endsWith(extension))
447+ continue;
448+ if (!presetsByExtension.contains(extension)) {
449+ addExtension(extension);
450+ }
451+ PresetInfo* preset = new PresetInfo(path);
452+ presetsByExtension[extension][path] = preset;
453+ }
454+ }
455+ }
456+
457+ foreach (QString extension, presetsByExtension.keys()) {
458+ QMap <QString,PresetInfo*> presets = presetsByExtension[extension];
459+ qDebug() << "Extension" << extension << "total" << presets.keys().length() << "presets";
460+ }
461+}
462+
463+void PresetInfoEnumerator::updatePresets(const QString extension) {
464+ QMap <QString,PresetInfo*> presets;
465+
466+ if (presetsByExtension.contains(extension))
467+ presetsByExtension.remove(extension);
468+
469+ foreach (QString dirPath, controllerDirPaths) {
470+ QDirIterator it(dirPath);
471+ while (it.hasNext()) {
472+ it.next();
473+ const QString path = it.filePath();
474+ if (!path.endsWith(extension))
475+ continue;
476+ PresetInfo* preset = new PresetInfo(path);
477+ presets[path] = preset;
478+ }
479+ }
480+
481+ presetsByExtension[extension] = presets;
482+}
483
484=== added file 'mixxx/src/controllers/controllerpresetinfo.h'
485--- mixxx/src/controllers/controllerpresetinfo.h 1970-01-01 00:00:00 +0000
486+++ mixxx/src/controllers/controllerpresetinfo.h 2012-05-29 05:49:18 +0000
487@@ -0,0 +1,87 @@
488+/**
489+* @file controllerpresetinfo.h
490+* @author Ilkka Tuohela hile@iki.fi
491+* @date Wed May 15 2012
492+* @brief Base class handling enumeration and parsing of preset info headers
493+*
494+* This class handles enumeration and parsing of controller XML description file
495+* <info> header tags. It can be used to match controllers automatically or to
496+* show details for a mapping.
497+*/
498+
499+#ifndef CONTROLLERPRESETINFO_H
500+#define CONTROLLERPRESETINFO_H
501+
502+#include <QtGui>
503+#include <QMap>
504+#include <QHash>
505+
506+#include "xmlparse.h"
507+#include "controllers/defs_controllers.h"
508+#include "configobject.h"
509+
510+class PresetInfo {
511+ public:
512+ PresetInfo(const QString path);
513+ ~PresetInfo() {};
514+
515+ inline const QString getPath() { return path; };
516+
517+ inline const QString getName() { return name; };
518+ inline const QString getDescription() { return description; };
519+ inline const QString getAuthor() { return author; };
520+
521+ inline const QList< QHash<QString,QString> > getProducts() { return products; };
522+
523+ private:
524+
525+ QHash<QString,QString> parseHIDProduct(const QDomElement& element) const;
526+ // Note - following are just stubs, not yet implemented
527+ QHash<QString,QString> parseMIDIProduct(const QDomElement& element) const;
528+ QHash<QString,QString> parseOSCProduct(const QDomElement& element) const;
529+
530+ QString path;
531+ QString name;
532+ QString author;
533+ QString description;
534+ QList< QHash<QString,QString> > products;
535+};
536+
537+class PresetInfoEnumerator {
538+ public:
539+ PresetInfoEnumerator(ConfigObject<ConfigValue> *pConfig);
540+ virtual ~PresetInfoEnumerator() {};
541+
542+ bool isValidExtension(const QString extension);
543+
544+ bool hasPresetInfo(const QString extension, const QString name);
545+ bool hasPresetInfo(const QString path);
546+
547+ PresetInfo* getPresetInfo(const QString extension, const QString name);
548+ PresetInfo* getPresetInfo(const QString path);
549+
550+ // Return cached list of presets for this extension
551+ QList <PresetInfo*> getPresets(const QString extension);
552+
553+ // Updates presets matching given extension
554+ void updatePresets(const QString extension);
555+
556+ protected:
557+ void addExtension(QString extension);
558+ void loadSupportedPresets();
559+
560+ private:
561+
562+ QList <QString> fileExtensions;
563+ ConfigObject<ConfigValue>* m_pConfig;
564+
565+ // List of paths for controller presets
566+ QList <QString> controllerDirPaths;
567+
568+ // Cached presets by extension. Map format is:
569+ // [extension,[preset_path,preset]]
570+ QMap <QString, QMap<QString,PresetInfo*> > presetsByExtension;
571+
572+};
573+
574+#endif
575\ No newline at end of file
576
577=== modified file 'mixxx/src/controllers/dlgprefcontroller.cpp'
578--- mixxx/src/controllers/dlgprefcontroller.cpp 2012-04-29 04:31:26 +0000
579+++ mixxx/src/controllers/dlgprefcontroller.cpp 2012-05-29 05:49:18 +0000
580@@ -32,7 +32,9 @@
581 m_ui.setupUi(this);
582 m_pLayout = m_ui.gridLayout_4;
583 const ControllerPresetPointer pPreset = controller->getPreset();
584+
585 m_ui.labelLoadedPreset->setText(presetShortName(pPreset));
586+ m_ui.labelLoadedPresetDescription->setText(presetDescription(pPreset));
587
588 //m_pVerticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
589 //m_pLayout->addItem(m_pVerticalSpacer, 4, 0, 1, 3);
590@@ -42,21 +44,28 @@
591 //setLayout(vLayout);
592
593 m_ui.labelDeviceName->setText(m_pController->getName());
594+ QString category = m_pController->getCategory();
595+ if (category!="") {
596+ m_ui.labelDeviceCategory->setText(category);
597+ } else {
598+ m_ui.labelDeviceCategory->hide();
599+ }
600
601 connect(m_ui.comboBoxPreset, SIGNAL(activated(const QString&)),
602 this, SLOT(slotLoadPreset(const QString&)));
603 connect(m_ui.comboBoxPreset, SIGNAL(activated(const QString&)),
604 this, SLOT(slotDirty()));
605
606+ // Connect if we wish to automatically set current mapping as autoload
607+ // connect(m_ui.comboBoxPreset, SIGNAL(currentIndexChanged(const QString&)),
608+ // this, SLOT(slotDirty()));
609+
610 connect(this, SIGNAL(openController(Controller*)),
611 m_pControllerManager, SLOT(openController(Controller*)));
612 connect(this, SIGNAL(closeController(Controller*)),
613 m_pControllerManager, SLOT(closeController(Controller*)));
614 connect(this, SIGNAL(loadPreset(Controller*, QString, bool)),
615 m_pControllerManager, SLOT(loadPreset(Controller*, QString, bool)));
616-
617- // Load the list of presets into the presets combobox.
618- enumeratePresets();
619 }
620
621 DlgPrefController::~DlgPrefController() {
622@@ -78,6 +87,16 @@
623 return presetName;
624 }
625
626+QString DlgPrefController::presetDescription(const ControllerPresetPointer pPreset) const {
627+ QString description = tr("No Description");
628+ if (pPreset) {
629+ QString descr = pPreset->description();
630+ if (description.length() > 0)
631+ description = descr;
632+ }
633+ return description;
634+}
635+
636 void DlgPrefController::addWidgetToLayout(QWidget* pWidget) {
637 // Remove the vertical spacer since we're adding stuff
638 //m_pLayout->removeItem(m_pVerticalSpacer);
639@@ -91,18 +110,40 @@
640 void DlgPrefController::enumeratePresets() {
641 m_ui.comboBoxPreset->clear();
642
643+ // qDebug() << "Enumerating presets for controller" << m_pController->getName();
644+
645 //Insert a dummy "..." item at the top to try to make it less confusing.
646 //(We don't want the first found file showing up as the default item
647 // when a user has their controller plugged in)
648 m_ui.comboBoxPreset->addItem("...");
649
650+ m_ui.comboBoxPreset->setInsertPolicy(QComboBox::InsertAlphabetically);
651 // Ask the controller manager for a list of applicable presets
652- QList<QString> presetsList =
653- m_pControllerManager->getPresetList(m_pController->presetExtension());
654-
655- //Sort in alphabetical order
656- qSort(presetsList);
657- m_ui.comboBoxPreset->addItems(presetsList);
658+ PresetInfoEnumerator* pie = m_pControllerManager->getPresetInfoManager();
659+ QList<PresetInfo*> presets = pie->getPresets(m_pController->presetExtension());
660+
661+ PresetInfo *match = NULL;
662+ int index = 1;
663+ foreach (PresetInfo* preset, presets) {
664+ // QVariant userdata = preset;
665+ m_ui.comboBoxPreset->addItem(preset->getName());
666+ QList< QHash<QString,QString> > products = preset->getProducts();
667+ for (int i=0;i<products.length();i++) {
668+ QHash <QString,QString> product = products[i];
669+ if (m_pController->matchProductInfo(product)) {
670+ match = preset;
671+ break;
672+ }
673+ }
674+ index++;
675+ }
676+
677+ // Jump to matching device in list if it was found.
678+ if (match!=NULL) {
679+ int index = m_ui.comboBoxPreset->findText(match->getName());
680+ if (index!=-1)
681+ m_ui.comboBoxPreset->setCurrentIndex(index);
682+ }
683 }
684
685 void DlgPrefController::slotUpdate() {
686@@ -137,14 +178,25 @@
687 }
688
689 void DlgPrefController::slotLoadPreset(const QString &name) {
690- if (name != "...") {
691- emit(loadPreset(m_pController, name, true));
692- // It's applied on prefs close
693+ if (name=="...")
694+ return;
695+
696+ // Lookup the name in preset from actual preset data
697+ PresetInfoEnumerator* presetmanager = m_pControllerManager->getPresetInfoManager();
698+ PresetInfo* preset = presetmanager->getPresetInfo(m_pController->presetExtension(),name);
699+ if (preset==NULL) {
700+ qDebug() << "ERROR preset matching name not found: " << name;
701+ return;
702 }
703+ // qDebug() << "PRESET path" << preset->getPath();
704+
705+ // Applied on prefs close
706+ emit(loadPreset(m_pController, preset->getPath(), true));
707 }
708
709 void DlgPrefController::slotPresetLoaded(ControllerPresetPointer preset) {
710 m_ui.labelLoadedPreset->setText(presetShortName(preset));
711+ m_ui.labelLoadedPresetDescription->setText(presetDescription(preset));
712 }
713
714 void DlgPrefController::slotDeviceState(int state) {
715
716=== modified file 'mixxx/src/controllers/dlgprefcontroller.h'
717--- mixxx/src/controllers/dlgprefcontroller.h 2012-04-29 04:31:26 +0000
718+++ mixxx/src/controllers/dlgprefcontroller.h 2012-05-29 05:49:18 +0000
719@@ -9,8 +9,10 @@
720 #define DLGPREFCONTROLLER_H_
721
722 #include <QtGui>
723+#include <QHash>
724
725 #include "controllers/controllerpreset.h"
726+#include "controllers/controllerpresetinfo.h"
727 #include "controllers/ui_dlgprefcontrollerdlg.h"
728 #include "configobject.h"
729
730@@ -36,6 +38,8 @@
731 void slotLoadPreset(const QString &name);
732 // Mark that we need to apply the settings.
733 void slotDirty ();
734+ // Reload the mappings in the dropdown dialog
735+ void enumeratePresets();
736
737 signals:
738 void deviceStateChanged(DlgPrefController*, bool);
739@@ -63,8 +67,8 @@
740
741 private:
742 QString presetShortName(const ControllerPresetPointer pPreset) const;
743+ QString presetDescription(const ControllerPresetPointer pPreset) const;
744 void savePreset(QString path);
745- void enumeratePresets();
746
747 void enableDevice();
748 void disableDevice();
749
750=== modified file 'mixxx/src/controllers/dlgprefcontrollerdlg.ui'
751--- mixxx/src/controllers/dlgprefcontrollerdlg.ui 2012-04-29 04:34:22 +0000
752+++ mixxx/src/controllers/dlgprefcontrollerdlg.ui 2012-05-29 05:49:18 +0000
753@@ -57,7 +57,58 @@
754 </property>
755 </widget>
756 </item>
757- <item row="4" column="0">
758+ <item row="5" column="0">
759+ <widget class="QPushButton" name="pushButtonLearning">
760+ <property name="enabled">
761+ <bool>false</bool>
762+ </property>
763+ <property name="sizePolicy">
764+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
765+ <horstretch>0</horstretch>
766+ <verstretch>0</verstretch>
767+ </sizepolicy>
768+ </property>
769+ <property name="maximumSize">
770+ <size>
771+ <width>300</width>
772+ <height>16777215</height>
773+ </size>
774+ </property>
775+ <property name="toolTip">
776+ <string>Click to start the Controller Learning wizard.</string>
777+ </property>
778+ <property name="text">
779+ <string>Learning Wizard (MIDI Only)</string>
780+ </property>
781+ </widget>
782+ </item>
783+ <item row="5" column="1">
784+ <spacer name="horizontalSpacer">
785+ <property name="orientation">
786+ <enum>Qt::Horizontal</enum>
787+ </property>
788+ <property name="sizeHint" stdset="0">
789+ <size>
790+ <width>40</width>
791+ <height>20</height>
792+ </size>
793+ </property>
794+ </spacer>
795+ </item>
796+ <item row="7" column="0">
797+ <spacer name="verticalSpacer">
798+ <property name="orientation">
799+ <enum>Qt::Vertical</enum>
800+ </property>
801+ <property name="sizeHint" stdset="0">
802+ <size>
803+ <width>20</width>
804+ <height>40</height>
805+ </size>
806+ </property>
807+ </spacer>
808+ </item>
809+ <item row="4" column="0" colspan="2">
810 <widget class="QGroupBox" name="groupBoxPresets">
811 <property name="sizePolicy">
812 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
813@@ -65,6 +116,12 @@
814 <verstretch>0</verstretch>
815 </sizepolicy>
816 </property>
817+ <property name="maximumSize">
818+ <size>
819+ <width>550</width>
820+ <height>16777215</height>
821+ </size>
822+ </property>
823 <property name="title">
824 <string/>
825 </property>
826@@ -81,7 +138,7 @@
827 <property name="margin">
828 <number>0</number>
829 </property>
830- <item row="1" column="2">
831+ <item row="2" column="2">
832 <widget class="QComboBox" name="comboBoxPreset">
833 <property name="sizePolicy">
834 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
835@@ -97,8 +154,20 @@
836 </property>
837 </widget>
838 </item>
839- <item row="1" column="1">
840+ <item row="2" column="1">
841 <widget class="QLabel" name="label">
842+ <property name="sizePolicy">
843+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
844+ <horstretch>0</horstretch>
845+ <verstretch>0</verstretch>
846+ </sizepolicy>
847+ </property>
848+ <property name="maximumSize">
849+ <size>
850+ <width>16777215</width>
851+ <height>16777215</height>
852+ </size>
853+ </property>
854 <property name="text">
855 <string>Load Preset:</string>
856 </property>
857@@ -113,7 +182,7 @@
858 <item row="0" column="1">
859 <widget class="QLabel" name="label_2">
860 <property name="sizePolicy">
861- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
862+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
863 <horstretch>0</horstretch>
864 <verstretch>0</verstretch>
865 </sizepolicy>
866@@ -122,14 +191,14 @@
867 <string>Loaded Preset:</string>
868 </property>
869 <property name="alignment">
870- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
871+ <set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
872 </property>
873 </widget>
874 </item>
875 <item row="0" column="2">
876 <widget class="QLabel" name="labelLoadedPreset">
877 <property name="sizePolicy">
878- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
879+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
880 <horstretch>0</horstretch>
881 <verstretch>0</verstretch>
882 </sizepolicy>
883@@ -137,50 +206,71 @@
884 <property name="text">
885 <string notr="true">(preset name goes here)</string>
886 </property>
887+ <property name="wordWrap">
888+ <bool>true</bool>
889+ </property>
890+ </widget>
891+ </item>
892+ <item row="1" column="1">
893+ <widget class="QLabel" name="label_description">
894+ <property name="sizePolicy">
895+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
896+ <horstretch>0</horstretch>
897+ <verstretch>0</verstretch>
898+ </sizepolicy>
899+ </property>
900+ <property name="text">
901+ <string>Description:</string>
902+ </property>
903+ <property name="alignment">
904+ <set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
905+ </property>
906+ </widget>
907+ </item>
908+ <item row="1" column="2">
909+ <widget class="QLabel" name="labelLoadedPresetDescription">
910+ <property name="sizePolicy">
911+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
912+ <horstretch>0</horstretch>
913+ <verstretch>0</verstretch>
914+ </sizepolicy>
915+ </property>
916+ <property name="minimumSize">
917+ <size>
918+ <width>400</width>
919+ <height>0</height>
920+ </size>
921+ </property>
922+ <property name="text">
923+ <string notr="true">(preset description goes here) even when it is a damn long text which should wrap but does not</string>
924+ </property>
925+ <property name="alignment">
926+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
927+ </property>
928+ <property name="wordWrap">
929+ <bool>true</bool>
930+ </property>
931 </widget>
932 </item>
933 </layout>
934 </widget>
935 </item>
936- <item row="5" column="0">
937- <widget class="QPushButton" name="pushButtonLearning">
938+ <item row="1" column="0">
939+ <widget class="QLabel" name="labelDeviceCategory">
940 <property name="enabled">
941- <bool>false</bool>
942+ <bool>true</bool>
943 </property>
944- <property name="toolTip">
945- <string>Click to start the Controller Learning wizard.</string>
946+ <property name="sizePolicy">
947+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
948+ <horstretch>0</horstretch>
949+ <verstretch>0</verstretch>
950+ </sizepolicy>
951 </property>
952 <property name="text">
953- <string>Learning Wizard (MIDI Only)</string>
954+ <string>TextLabel</string>
955 </property>
956 </widget>
957 </item>
958- <item row="5" column="1">
959- <spacer name="horizontalSpacer">
960- <property name="orientation">
961- <enum>Qt::Horizontal</enum>
962- </property>
963- <property name="sizeHint" stdset="0">
964- <size>
965- <width>40</width>
966- <height>20</height>
967- </size>
968- </property>
969- </spacer>
970- </item>
971- <item row="7" column="0">
972- <spacer name="verticalSpacer">
973- <property name="orientation">
974- <enum>Qt::Vertical</enum>
975- </property>
976- <property name="sizeHint" stdset="0">
977- <size>
978- <width>20</width>
979- <height>40</height>
980- </size>
981- </property>
982- </spacer>
983- </item>
984 </layout>
985 </widget>
986 <resources/>
987
988=== modified file 'mixxx/src/controllers/hid/hidcontroller.cpp'
989--- mixxx/src/controllers/hid/hidcontroller.cpp 2012-05-20 01:12:02 +0000
990+++ mixxx/src/controllers/hid/hidcontroller.cpp 2012-05-29 05:49:18 +0000
991@@ -80,6 +80,8 @@
992 wcslen(deviceInfo.product_string)
993 );
994
995+ guessDeviceCategory();
996+
997 // Set the Unique Identifier to the serial_number
998 m_sUID = QString::fromWCharArray(hid_serial,wcslen(hid_serial));
999
1000@@ -127,6 +129,68 @@
1001 return handler.save(m_preset, getName(), fileName);
1002 }
1003
1004+bool HidController::matchProductInfo(QHash <QString,QString > info) {
1005+ int value;
1006+ bool ok;
1007+ // Product and vendor match is always required
1008+ value = info["vendor_id"].toInt(&ok,16);
1009+ if (!ok || hid_vendor_id!=value) return false;
1010+ value = info["product_id"].toInt(&ok,16);
1011+ if (!ok || hid_product_id!=value) return false;
1012+
1013+ // Optionally check against interface_number / usage_page && usage
1014+ if (hid_interface_number!=-1) {
1015+ value = info["interface_number"].toInt(&ok,16);
1016+ if (!ok || hid_interface_number!=value) return false;
1017+ } else {
1018+ value = info["usage_page"].toInt(&ok,16);
1019+ if (!ok || hid_usage_page!=value) return false;
1020+
1021+ value = info["usage"].toInt(&ok,16);
1022+ if (!ok || hid_usage!=value) return false;
1023+ }
1024+ // Match found
1025+ return true;
1026+}
1027+void HidController::guessDeviceCategory() {
1028+ // This should be done somehow else, I know. But at least we get started with
1029+ // the idea of mapping this information
1030+ QString info;
1031+ if (hid_interface_number==-1) {
1032+ if (hid_usage_page==0x1) {
1033+ switch (hid_usage) {
1034+ case 0x2: info = "Generic HID Mouse"; break;
1035+ case 0x4: info = "Generic HID Joystick"; break;
1036+ case 0x5: info = "Generic HID Gamepad"; break;
1037+ case 0x6: info = "Generic HID Keyboard"; break;
1038+ case 0x8: info = "Gereric HID Multiaxis Controller"; break;
1039+ default:
1040+ info.sprintf(
1041+ "Unknown HID Desktop Device 0x%0x/0x%0x",hid_usage_page,hid_usage
1042+ );
1043+ break;
1044+ }
1045+ } else if (hid_vendor_id==0x5ac) {
1046+ // Apple laptop special HID devices
1047+ if (hid_product_id==0x8242) {
1048+ info = "HID Infrared Control";
1049+ } else {
1050+ info.sprintf(
1051+ "Unknown Apple HID Device 0x%0x/0x%0x",hid_usage_page,hid_usage
1052+ );
1053+ }
1054+ } else {
1055+ // Fill in the usage page and usage fields for debugging info
1056+ info.sprintf("HID Unknown Device 0x%0x/0x%0x",hid_usage_page,hid_usage);
1057+ }
1058+ } else {
1059+ // Guess linux device types somehow as well. Or maybe just
1060+ // fill in the interface number?
1061+ info.sprintf("HID Interface Number 0x%0x",hid_interface_number);
1062+ }
1063+ setDeviceCategory(info);
1064+}
1065+
1066 int HidController::open() {
1067 if (isOpen()) {
1068 qDebug() << "HID device" << getName() << "already open";
1069
1070=== modified file 'mixxx/src/controllers/hid/hidcontroller.h'
1071--- mixxx/src/controllers/hid/hidcontroller.h 2012-05-16 14:58:38 +0000
1072+++ mixxx/src/controllers/hid/hidcontroller.h 2012-05-29 05:49:18 +0000
1073@@ -62,6 +62,9 @@
1074 return m_preset.isMappable();
1075 }
1076
1077+ virtual bool matchProductInfo(QHash <QString,QString >);
1078+ virtual void guessDeviceCategory();
1079+
1080 protected:
1081 Q_INVOKABLE void send(QList<int> data, unsigned int length, unsigned int reportID = 0);
1082
1083
1084=== modified file 'mixxx/src/controllers/midi/midicontroller.cpp'
1085--- mixxx/src/controllers/midi/midicontroller.cpp 2012-05-26 23:32:16 +0000
1086+++ mixxx/src/controllers/midi/midicontroller.cpp 2012-05-29 05:49:18 +0000
1087@@ -56,6 +56,11 @@
1088 // TODO(XXX): throw a hissy fit.
1089 }
1090
1091+bool MidiController::matchProductInfo(QHash <QString,QString >) {
1092+ // Product info mapping not implemented for MIDI devices yet
1093+ return false;
1094+}
1095+
1096 bool MidiController::savePreset(const QString fileName) const {
1097 MidiControllerPresetFileHandler handler;
1098 return handler.save(m_preset, getName(), fileName);
1099
1100=== modified file 'mixxx/src/controllers/midi/midicontroller.h'
1101--- mixxx/src/controllers/midi/midicontroller.h 2012-05-25 04:24:48 +0000
1102+++ mixxx/src/controllers/midi/midicontroller.h 2012-05-29 05:49:18 +0000
1103@@ -48,6 +48,8 @@
1104 return m_preset.isMappable();
1105 }
1106
1107+ virtual bool matchProductInfo(QHash <QString,QString >);
1108+
1109 protected:
1110 Q_INVOKABLE void sendShortMsg(unsigned char status, unsigned char byte1, unsigned char byte2);
1111 // Alias for send()
1112
1113=== modified file 'mixxx/src/dlgpreferences.cpp'
1114--- mixxx/src/dlgpreferences.cpp 2012-04-29 05:43:31 +0000
1115+++ mixxx/src/dlgpreferences.cpp 2012-05-29 05:49:18 +0000
1116@@ -425,6 +425,11 @@
1117 {
1118 //For each controller, create a dialog and put a little link to it in the treepane on the left
1119 QList<Controller*> controllerList = m_pControllerManager->getControllerList(false, true);
1120+ qSort(
1121+ controllerList.begin(),
1122+ controllerList.end(),
1123+ controllerCompare
1124+ );
1125 QListIterator<Controller*> ctrlr(controllerList);
1126 while (ctrlr.hasNext())
1127 {
1128@@ -441,6 +446,7 @@
1129 this, SLOT(show()));
1130 m_controllerWindows.append(controllerDlg);
1131 addPageWidget(controllerDlg);
1132+ connect(this, SIGNAL(showDlg()), controllerDlg, SLOT(enumeratePresets()));
1133 connect(this, SIGNAL(showDlg()), controllerDlg, SLOT(slotUpdate()));
1134 connect(buttonBox, SIGNAL(accepted()), controllerDlg, SLOT(slotApply()));
1135 connect(controllerDlg, SIGNAL(deviceStateChanged(DlgPrefController*,bool)), this, SLOT(slotHighlightDevice(DlgPrefController*,bool)));
1136@@ -450,6 +456,7 @@
1137 config);
1138 m_controllerWindows.append(controllerDlg);
1139 addPageWidget(controllerDlg);
1140+ connect(this, SIGNAL(showDlg()), controllerDlg, SLOT(enumeratePresets()));
1141 connect(this, SIGNAL(showDlg()), controllerDlg, SLOT(slotUpdate()));
1142 connect(buttonBox, SIGNAL(accepted()), controllerDlg, SLOT(slotApply()));
1143 connect(controllerDlg, SIGNAL(deviceStateChanged(DlgPrefController*,bool)),