1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Rosegarden
5     A MIDI and audio sequencer and musical notation editor.
6     Copyright 2000-2021 the Rosegarden development team.
7 
8     Other copyrights also apply to some parts of this work.  Please
9     see the AUTHORS file and individual file headers for details.
10 
11     This program is free software; you can redistribute it and/or
12     modify it under the terms of the GNU General Public License as
13     published by the Free Software Foundation; either version 2 of the
14     License, or (at your option) any later version.  See the file
15     COPYING included with this distribution for more information.
16 */
17 
18 #define RG_MODULE_STRING "[BankEditorDialog]"
19 
20 #include "BankEditorDialog.h"
21 #include "MidiBankTreeWidgetItem.h"
22 #include "MidiDeviceTreeWidgetItem.h"
23 #include "MidiKeyMapTreeWidgetItem.h"
24 #include "MidiKeyMappingEditor.h"
25 #include "MidiProgramsEditor.h"
26 
27 #include "misc/Debug.h"
28 #include "misc/Strings.h"
29 #include "misc/ConfigGroups.h"
30 #include "base/Device.h"
31 #include "base/MidiDevice.h"
32 #include "base/MidiProgram.h"
33 #include "base/NotationTypes.h"
34 #include "base/Studio.h"
35 #include "commands/studio/ModifyDeviceCommand.h"
36 #include "document/CommandHistory.h"
37 #include "document/RosegardenDocument.h"
38 #include "misc/ConfigGroups.h"
39 #include "gui/dialogs/ExportDeviceDialog.h"
40 #include "gui/dialogs/ImportDeviceDialog.h"
41 #include "gui/widgets/TmpStatusMsg.h"
42 #include "gui/widgets/FileDialog.h"
43 #include "gui/general/ResourceFinder.h"
44 #include "gui/general/ThornStyle.h"
45 #include "gui/dialogs/AboutDialog.h"
46 #include "document/Command.h"
47 
48 #include <QLayout>
49 #include <QApplication>
50 #include <QAction>
51 #include <QComboBox>
52 #include <QTreeWidget>
53 #include <QMainWindow>
54 #include <QMessageBox>
55 #include <QCheckBox>
56 #include <QDialog>
57 #include <QDir>
58 #include <QFileInfo>
59 #include <QFrame>
60 #include <QGroupBox>
61 #include <QPushButton>
62 #include <QSizePolicy>
63 #include <QString>
64 #include <QToolTip>
65 #include <QWidget>
66 #include <QVBoxLayout>
67 #include <QHBoxLayout>
68 #include <QShortcut>
69 #include <QDesktopServices>
70 #if QT_VERSION >= 0x050000
71 #include <QStandardPaths>
72 #endif
73 #include <QDialogButtonBox>
74 
75 
76 namespace Rosegarden
77 {
78 
BankEditorDialog(QWidget * parent,RosegardenDocument * doc,DeviceId defaultDevice)79 BankEditorDialog::BankEditorDialog(QWidget *parent,
80                                    RosegardenDocument *doc,
81                                    DeviceId defaultDevice):
82         QMainWindow(parent),
83         m_studio(&doc->getStudio()),
84         m_doc(doc),
85         m_copyBank(Device::NO_DEVICE, -1),
86         m_keepBankList(false),
87         m_deleteAllReally(false),
88         m_lastDevice(Device::NO_DEVICE),
89         m_updateDeviceList(false)
90 {
91     QWidget* mainFrame = new QWidget(this);
92     mainFrame->setContentsMargins(1, 1, 1, 1);
93     setCentralWidget(mainFrame);
94     QVBoxLayout *mainFrameLayout = new QVBoxLayout;
95     mainFrameLayout->setContentsMargins(0, 0, 0, 0);
96     mainFrameLayout->setSpacing(2);
97     mainFrame->setLayout(mainFrameLayout);
98 
99     setWindowTitle(tr("Manage MIDI Banks and Programs"));
100 
101     QWidget *splitter = new QWidget;
102     QHBoxLayout *splitterLayout = new QHBoxLayout;
103     splitterLayout->setContentsMargins(0, 0, 0, 0);
104     splitter->setLayout(splitterLayout);
105 
106     mainFrameLayout->addWidget(splitter);
107 
108     //
109     // Left-side list view
110     //
111     QWidget *leftPart = new QWidget;
112     QVBoxLayout *leftPartLayout = new QVBoxLayout;
113     leftPartLayout->setContentsMargins(2, 2, 2, 2);
114     leftPart->setLayout(leftPartLayout);
115     splitterLayout->addWidget(leftPart);
116 
117     m_treeWidget = new QTreeWidget;
118     leftPartLayout->addWidget(m_treeWidget);
119 
120     m_treeWidget->setColumnCount(4);
121     QStringList sl;
122     sl << tr("Device and Banks")
123        << tr("Type")
124        << tr("MSB")
125        << tr("LSB");
126     m_treeWidget->setHeaderLabels(sl);
127     m_treeWidget->setRootIsDecorated(true);
128     m_treeWidget->setSelectionBehavior(QAbstractItemView::SelectRows);    //qt4
129     m_treeWidget->setSelectionMode(QAbstractItemView::SingleSelection);    //qt4
130 //    m_treeWidget->setAllColumnsShowFocus(true);
131     m_treeWidget->setSortingEnabled(true);
132 
133     /*
134     m_treeWidget->setShowSortIndicator(true);        //&&&
135     m_treeWidget->setItemsRenameable(true);
136     m_treeWidget->restoreLayout(BankEditorConfigGroup);
137     */
138 
139     QFrame *bankBox = new QFrame(leftPart);
140     leftPartLayout->addWidget(bankBox);
141     bankBox->setContentsMargins(1, 1, 1, 1);
142     QGridLayout *gridLayout = new QGridLayout(bankBox);
143     gridLayout->setSpacing(4);
144 
145     m_addBank = new QPushButton(tr("Add Bank"), bankBox);
146     m_addKeyMapping = new QPushButton(tr("Add Key Mapping"), bankBox);
147     m_delete = new QPushButton(tr("Delete"), bankBox);
148     m_deleteAll = new QPushButton(tr("Delete All"), bankBox);
149     gridLayout->addWidget(m_addBank, 0, 0);
150     gridLayout->addWidget(m_addKeyMapping, 0, 1);
151     gridLayout->addWidget(m_delete, 1, 0);
152     gridLayout->addWidget(m_deleteAll, 1, 1);
153 
154     // Tips
155     //
156     m_addBank->setToolTip(tr("Add a Bank to the current device"));
157 
158     m_addKeyMapping->setToolTip(tr("Add a Percussion Key Mapping to the current device"));
159 
160     m_delete->setToolTip(tr("Delete the current Bank or Key Mapping"));
161 
162     m_deleteAll->setToolTip(tr("Delete all Banks and Key Mappings from the current Device"));
163 
164     m_importBanks = new QPushButton(tr("Import..."), bankBox);
165     m_exportBanks = new QPushButton(tr("Export..."), bankBox);
166     gridLayout->addWidget(m_importBanks, 2, 0);
167     gridLayout->addWidget(m_exportBanks, 2, 1);
168 
169     // Tips
170     //
171     m_importBanks->setToolTip(tr("Import Bank and Program data from a Rosegarden file to the current Device"));
172     m_exportBanks->setToolTip(tr("Export all Device and Bank information to a Rosegarden format  interchange file"));
173 
174     m_copyPrograms = new QPushButton(tr("Copy"), bankBox);
175     m_pastePrograms = new QPushButton(tr("Paste"), bankBox);
176     gridLayout->addWidget(m_copyPrograms, 3, 0);
177     gridLayout->addWidget(m_pastePrograms, 3, 1);
178 
179     bankBox->setLayout(gridLayout);
180 
181     // Tips
182     //
183     m_copyPrograms->setToolTip(tr("Copy all Program names from current Bank to clipboard"));
184 
185     m_pastePrograms->setToolTip(tr("Paste Program names from clipboard to current Bank"));
186 
187 //     connect(m_treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*)),
188 //             this, SLOT(slotPopulateDeviceEditors(QTreeWidgetItem*)));
189     // note: above connection moved to setupActions
190 
191     // Right side layout
192     m_rightSide = new QFrame;
193 
194     m_rightSide->setContentsMargins(8, 8, 8, 8);
195     QVBoxLayout *rightSideLayout = new QVBoxLayout;
196     rightSideLayout->setContentsMargins(0, 0, 0, 0);
197     rightSideLayout->setSpacing(6);
198     m_rightSide->setLayout(rightSideLayout);
199 
200     splitterLayout->addWidget(m_rightSide);
201 
202     m_programEditor = new MidiProgramsEditor(this, m_rightSide);
203     rightSideLayout->addWidget(m_programEditor);
204 
205     m_keyMappingEditor = new MidiKeyMappingEditor(this, m_rightSide);
206     rightSideLayout->addWidget(m_keyMappingEditor);
207     m_keyMappingEditor->hide();
208 
209     m_programEditor->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred));
210     m_keyMappingEditor->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred));
211 
212     m_optionBox = new QGroupBox(tr("Options"), m_rightSide);
213     QVBoxLayout *optionBoxLayout = new QVBoxLayout;
214     optionBoxLayout->setContentsMargins(0, 0, 0, 0);
215     rightSideLayout->addWidget(m_optionBox);
216 
217     QWidget *variationBox = new QWidget(m_optionBox);
218     QHBoxLayout *variationBoxLayout = new QHBoxLayout;
219     variationBoxLayout->setContentsMargins(4, 4, 4, 4);
220     optionBoxLayout->addWidget(variationBox);
221 
222     m_variationToggle = new QCheckBox(tr("Show Variation list based on "), variationBox);
223     variationBoxLayout->addWidget(m_variationToggle);
224     m_variationCombo = new QComboBox(variationBox);
225     variationBoxLayout->addWidget(m_variationCombo);
226     variationBox->setLayout(variationBoxLayout);
227     m_variationCombo->addItem(tr("LSB"));
228     m_variationCombo->addItem(tr("MSB"));
229 
230     m_optionBox->setLayout(optionBoxLayout);
231 
232     // device/bank modification
233 
234     connect(m_treeWidget, &QTreeWidget::itemDoubleClicked,
235             this, &BankEditorDialog::slotEdit);
236 
237     connect(m_addBank, &QAbstractButton::clicked,
238             this, &BankEditorDialog::slotAddBank);
239 
240     connect(m_addKeyMapping, &QAbstractButton::clicked,
241             this, &BankEditorDialog::slotAddKeyMapping);
242 
243     connect(m_delete, &QAbstractButton::clicked,
244             this, &BankEditorDialog::slotDelete);
245 
246     connect(m_deleteAll, &QAbstractButton::clicked,
247             this, &BankEditorDialog::slotDeleteAll);
248 
249     connect(m_importBanks, &QAbstractButton::clicked,
250             this, &BankEditorDialog::slotImport);
251 
252     connect(m_exportBanks, &QAbstractButton::clicked,
253             this, &BankEditorDialog::slotExport);
254 
255     connect(m_copyPrograms, &QAbstractButton::clicked,
256             this, &BankEditorDialog::slotEditCopy);
257 
258     connect(m_pastePrograms, &QAbstractButton::clicked,
259             this, &BankEditorDialog::slotEditPaste);
260 
261     connect(m_variationToggle, &QAbstractButton::clicked,
262             this, &BankEditorDialog::slotVariationToggled);
263 
264     connect(m_variationCombo, SIGNAL(activated(int)),
265             this, SLOT(slotVariationChanged(int)));
266 
267 //     CommandHistory::getInstance()->attachView(actionCollection());    //&&&
268 
269     connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()),
270             this, SLOT(slotUpdate()));
271 
272     // Button box
273     QDialogButtonBox *btnBox = new QDialogButtonBox(/*QDialogButtonBox::Apply  | */
274                                                     QDialogButtonBox::Reset  |
275                                                     QDialogButtonBox::Close);
276 
277     mainFrameLayout->addWidget(btnBox);
278 
279     m_closeButton = btnBox->button(QDialogButtonBox::Close);
280     m_resetButton = btnBox->button(QDialogButtonBox::Reset);
281 
282     connect(m_resetButton, &QAbstractButton::clicked, this, &BankEditorDialog::slotReset);
283 
284     // Initialize the dialog
285     //
286     initDialog();
287 
288     setupActions();
289 
290     // Check for no Midi devices and disable everything
291     //
292     DeviceList *devices = m_studio->getDevices();
293     DeviceListIterator it;
294     bool haveMidiPlayDevice = false;
295     for (it = devices->begin(); it != devices->end(); ++it) {
296         MidiDevice *md =
297             dynamic_cast<MidiDevice *>(*it);
298         if (md && md->getDirection() == MidiDevice::Play) {
299             haveMidiPlayDevice = true;
300             break;
301         }
302     }
303     if (!haveMidiPlayDevice) {
304         leftPart->setDisabled(true);
305         m_programEditor->setDisabled(true);
306         m_keyMappingEditor->setDisabled(true);
307         m_optionBox->setDisabled(true);
308     }
309 
310     if (defaultDevice != Device::NO_DEVICE) {
311         setCurrentDevice(defaultDevice);
312     }
313 
314     setAttribute(Qt::WA_DeleteOnClose);
315 //     setAutoSaveSettings(BankEditorConfigGroup, true);    //&&&
316 }
317 
~BankEditorDialog()318 BankEditorDialog::~BankEditorDialog()
319 {
320     RG_DEBUG << "~BankEditorDialog()\n";
321 
322 //     m_treeWidget->saveLayout(BankEditorConfigGroup);    //&&&
323 
324 //     if (m_doc) // see slotFileClose() for an explanation on why we need to test m_doc
325 //         CommandHistory::getInstance()->detachView(actionCollection());
326 }
327 
328 void
setupActions()329 BankEditorDialog::setupActions()
330 {
331 //     KAction* close = KStandardAction::close (this, SLOT(slotFileClose()), actionCollection());
332 
333     createAction("file_close", SLOT(slotFileClose()));
334 
335     connect(m_closeButton, &QAbstractButton::clicked, this, &BankEditorDialog::slotFileClose);
336 
337     createAction("edit_copy", SLOT(slotEditCopy()));
338     createAction("edit_paste", SLOT(slotEditPaste()));
339     createAction("bank_help", SLOT(slotHelpRequested()));
340     createAction("help_about_app", SLOT(slotHelpAbout()));
341 
342 
343 //     connect(m_treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*)),
344 //             this, SLOT(slotPopulateDeviceEditors(QTreeWidgetItem*)));
345 
346 //     connect( m_treeWidget, SIGNAL(itemActivated(QTreeWidgetItem*, int)),  //twItem, column
347 //              this, SLOT(slotPopulateDeviceEditors(QTreeWidgetItem*, int))  );
348 
349 
350 //     currentItemChanged( QTreeWidgetItem* current, QTreeWidgetItem* previous )
351     connect( m_treeWidget, &QTreeWidget::currentItemChanged,  //twItemCurr, teItemPrev
352              this, &BankEditorDialog::slotPopulateDeviceEditors  );
353 
354     connect(m_treeWidget, &QTreeWidget::itemChanged, this,
355             &BankEditorDialog::slotModifyDeviceOrBankName);
356 
357     // some adjustments
358 
359 /*
360     new KToolBarPopupAction(tr("Und&o"),
361                             "undo",
362                             KStandardShortcut::key(KStandardShortcut::Undo),
363                             actionCollection(),
364                             KStandardAction::stdName(KStandardAction::Undo));
365 
366     new KToolBarPopupAction(tr("Re&do"),
367                             "redo",
368                             KStandardShortcut::key(KStandardShortcut::Redo),
369                             actionCollection(),
370                             KStandardAction::stdName(KStandardAction::Redo));
371     */
372 
373     createMenusAndToolbars("bankeditor.rc"); //@@@ JAS orig. 0
374 }
375 
376 void
initDialog()377 BankEditorDialog::initDialog()
378 {
379     // Clear down
380     //
381     m_deviceNameMap.clear();
382     m_treeWidget->clear();
383 
384     // Fill list view
385     //
386     MidiDevice* midiDevice = nullptr;
387     QTreeWidgetItem* twItemDevice = nullptr;
388     DeviceList *devices = m_studio->getDevices();
389     DeviceListIterator it;
390     Device *devx;
391 //     unsigned int i = 0;
392 
393     // iterates over devices and create device-TreeWidgetItems (level: topLevelItem)
394     // then calls populateDeviceItem() to create bank-TreeWidgetItems (level: topLevelItem-child)
395     for (it = devices->begin(); it != devices->end(); ++it) {
396         devx = *it;
397 //     for ( i=0; i < int(devices->size()); i++ ){
398 //         devx = devices->at( i );
399 
400         if (devx->getType() == Device::Midi) {
401 
402             midiDevice = dynamic_cast<MidiDevice*>(devx);
403 
404             if (!midiDevice) continue;
405             // skip read-only devices
406             if (midiDevice->getDirection() == MidiDevice::Record) continue;
407 
408             m_deviceNameMap[midiDevice->getId()] = midiDevice->getName();
409             QString itemName = strtoqstr(midiDevice->getName());
410 
411             RG_DEBUG << "BankEditorDialog::initDialog - adding " << itemName;
412 
413             twItemDevice = new MidiDeviceTreeWidgetItem(midiDevice->getId(), m_treeWidget, itemName);
414             //twItemDevice->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable);
415 
416             m_treeWidget->addTopLevelItem(twItemDevice);  //
417 
418             twItemDevice->setExpanded(true);
419 
420             populateDeviceItem(twItemDevice, midiDevice);
421         }
422     }
423 
424     populateDeviceEditors(m_treeWidget->topLevelItem(0));
425 
426     // select the first device item
427     m_treeWidget->topLevelItem(0)->setSelected(true);
428     m_treeWidget->resizeColumnToContents(0);
429     m_treeWidget->setMinimumWidth(500);
430 }
431 
432 void
updateDialog()433 BankEditorDialog::updateDialog()
434 {
435     // Update list view
436     //
437     m_treeWidget->blockSignals(true);
438     DeviceList *devices = m_studio->getDevices();
439     DeviceListIterator it;
440     bool deviceLabelUpdate = false;
441 
442     MidiDevice* midiDevice = nullptr;
443     QTreeWidgetItem* currentIndex = nullptr;
444     MidiDeviceTreeWidgetItem* deviceItem = nullptr;
445     QTreeWidgetItem *child = nullptr;
446     unsigned int cnt, i;
447 
448     for (it = devices->begin(); it != devices->end(); ++it) {
449 
450         if ((*it)->getType() != Device::Midi){
451             continue;
452         }
453 
454         midiDevice = dynamic_cast<MidiDevice*>(*it);
455         if (!midiDevice){
456             continue;
457         }
458 
459         // skip read-only devices
460         if (midiDevice->getDirection() == MidiDevice::Record){
461             continue;
462         }
463 
464         if (m_deviceNameMap.find(midiDevice->getId()) != m_deviceNameMap.end()) {
465             // Device already displayed but make sure the label is up to date
466             //
467 
468             //QTreeWidgetItem*
469             currentIndex = m_treeWidget->currentItem();
470 
471             if (currentIndex) {
472                 //MidiDeviceTreeWidgetItem*
473                 deviceItem = getParentDeviceItem(currentIndex);
474 
475                 if (deviceItem &&
476                         deviceItem->getDeviceId() == midiDevice->getId()) {
477 
478                     if( deviceItem->text(0) != strtoqstr(midiDevice->getName()) ){
479 
480                         deviceItem->setText(0, strtoqstr(midiDevice->getName()));
481                         m_deviceNameMap[midiDevice->getId()] =
482                             midiDevice->getName();
483 
484                         /*
485                         cout << "NEW TEXT FOR DEVICE " << midiDevice->getId()
486                             << " IS " << midiDevice->getName() << endl;
487                         cout << "LIST ITEM ID = "
488                             << deviceItem->getDeviceId() << endl;
489                             */
490 
491                         deviceLabelUpdate = true;
492                     }
493 
494                     //QTreeWidgetItem *
495                     //child = deviceItem->child(0);   // bank/keymap item
496                     cnt = deviceItem->childCount();
497 
498                     for( i=0; i<cnt; i++ ){
499 
500                         //QTreeWidgetItem*
501                         child = deviceItem->child( i );
502 
503                         MidiBankTreeWidgetItem *bankItem =
504                             dynamic_cast<MidiBankTreeWidgetItem *>(child);
505 
506                         if (bankItem) {
507                             bool percussion = bankItem->isPercussion();
508                             int msb = bankItem->text(2).toInt();
509                             int lsb = bankItem->text(3).toInt();
510                             std::string bankName =
511                                 midiDevice->getBankName( MidiBank(percussion, msb, lsb) );
512                             if (bankName != "" &&
513                                     bankItem->text(0) != strtoqstr(bankName)) {
514                                 bankItem->setText(0, strtoqstr(bankName));
515                             }
516                         }
517 
518                     }// end for(i)
519 
520                 }
521             }// end if( currentIndex )
522 
523             continue;
524         }
525         else
526         {
527 
528         m_deviceNameMap[midiDevice->getId()] = midiDevice->getName();
529         QString itemName = strtoqstr(midiDevice->getName());
530 
531         RG_DEBUG << "BankEditorDialog::updateDialog - adding "
532         << itemName;
533 
534         QTreeWidgetItem* deviceItem = new MidiDeviceTreeWidgetItem
535                                     (midiDevice->getId(), m_treeWidget, itemName);
536 
537         deviceItem->setExpanded(true);
538 
539         populateDeviceItem(deviceItem, midiDevice);
540         }
541 
542     }// end for( it = device ...)
543 
544 
545     // delete items whose corresponding devices are no longer present,
546     // and update the other ones
547     //
548     std::vector<MidiDeviceTreeWidgetItem*> itemsToDelete;
549 
550     MidiDeviceTreeWidgetItem* sibling = dynamic_cast<MidiDeviceTreeWidgetItem*>
551                                       (m_treeWidget->topLevelItem(0));
552 
553 //     while (sibling) {
554     cnt = m_treeWidget->topLevelItemCount();
555     for( i=0; i<cnt; i++ ){
556         sibling = dynamic_cast<MidiDeviceTreeWidgetItem*>( m_treeWidget->topLevelItem(i) );
557 
558         if (m_deviceNameMap.find(sibling->getDeviceId()) == m_deviceNameMap.end())
559             itemsToDelete.push_back(sibling);
560         else
561             updateDeviceItem(sibling);
562 
563     }
564 
565     for (size_t i = 0; i < itemsToDelete.size(); ++i)
566         delete itemsToDelete[i];
567 
568 //    m_treeWidget->sortItems(0, Qt::AscendingOrder);    // column, order  // sort by device (column 0)
569 //     m_treeWidget->sortChildren();
570 
571     m_treeWidget->blockSignals(false);
572     if (deviceLabelUpdate)
573         emit deviceNamesChanged();
574 }
575 
576 void
setCurrentDevice(DeviceId device)577 BankEditorDialog::setCurrentDevice(DeviceId device)
578 {
579     unsigned int i, cnt;
580     QTreeWidgetItem* twItem;
581     MidiDeviceTreeWidgetItem* deviceItem;
582 
583     cnt = m_treeWidget->topLevelItemCount();
584     for( i = 0; i < cnt; i++ ){
585         twItem = m_treeWidget->topLevelItem( i );
586         deviceItem = dynamic_cast<MidiDeviceTreeWidgetItem *>(twItem);
587         if (deviceItem && deviceItem->getDeviceId() == device) {
588 //             m_treeWidget->setSelected(item, true);
589             m_treeWidget->setCurrentItem(twItem);
590             break;
591         }
592     }
593 }
594 
595 void
populateDeviceItem(QTreeWidgetItem * deviceItem,MidiDevice * midiDevice)596 BankEditorDialog::populateDeviceItem(QTreeWidgetItem* deviceItem, MidiDevice* midiDevice)
597 {
598     clearItemChildren(deviceItem);
599 
600     QString itemName = strtoqstr(midiDevice->getName());
601 
602     BankList banks = midiDevice->getBanks();
603     // add banks for this device
604     for (size_t i = 0; i < banks.size(); ++i) {
605         RG_DEBUG << "BankEditorDialog::populateDeviceItem - adding "
606                  << itemName << " - " << strtoqstr(banks[i].getName());
607         new MidiBankTreeWidgetItem(midiDevice->getId(), i, deviceItem,
608                                  strtoqstr(banks[i].getName()),
609                                  banks[i].isPercussion(),
610                                  banks[i].getMSB(), banks[i].getLSB());
611     }
612 
613     const KeyMappingList &mappings = midiDevice->getKeyMappings();
614     for (size_t i = 0; i < mappings.size(); ++i) {
615         RG_DEBUG << "BankEditorDialog::populateDeviceItem - adding key mapping "
616                  << itemName << " - " << strtoqstr(mappings[i].getName());
617         new MidiKeyMapTreeWidgetItem(midiDevice->getId(), deviceItem,
618                                    strtoqstr(mappings[i].getName()));
619     }
620 }
621 
622 void
updateDeviceItem(MidiDeviceTreeWidgetItem * deviceItem)623 BankEditorDialog::updateDeviceItem(MidiDeviceTreeWidgetItem* deviceItem)
624 {
625     MidiDevice* midiDevice = getMidiDevice(deviceItem->getDeviceId());
626     if (!midiDevice) {
627         RG_DEBUG << "BankEditorDialog::updateDeviceItem : WARNING no midi device for this item\n";
628         return ;
629     }
630 
631     QString itemName = strtoqstr(midiDevice->getName());
632 
633     BankList banks = midiDevice->getBanks();
634     KeyMappingList keymaps = midiDevice->getKeyMappings();
635 
636     // add missing banks for this device
637     //
638     for (size_t i = 0; i < banks.size(); ++i) {
639         if (deviceItemHasBank(deviceItem, i))
640             continue;
641 
642         RG_DEBUG << "BankEditorDialog::updateDeviceItem - adding "
643                  << itemName << " - " << strtoqstr(banks[i].getName());
644         new MidiBankTreeWidgetItem(midiDevice->getId(), i, deviceItem,
645                                  strtoqstr(banks[i].getName()),
646                                  banks[i].isPercussion(),
647                                  banks[i].getMSB(), banks[i].getLSB());
648     }
649 
650     int cnt, i, n;
651 
652     cnt = int(keymaps.size());
653     for ( i = 0; i < cnt; ++i) {
654 
655 //         QTreeWidgetItem *child = deviceItem->firstChild();
656         bool have = false;
657 
658         n = 0;
659         while (n < deviceItem->childCount()) {
660             QTreeWidgetItem *child = deviceItem->child(n);
661 
662             MidiKeyMapTreeWidgetItem *keyItem =
663                 dynamic_cast<MidiKeyMapTreeWidgetItem*>(child);
664             if (keyItem) {
665                 if (keyItem->getName() == strtoqstr(keymaps[i].getName())) {
666                     have = true;
667                 }
668             }
669             n += 1;
670         }
671 
672         if (have)
673             continue;
674 
675         RG_DEBUG << "BankEditorDialog::updateDeviceItem - adding "
676                  << itemName << " - " << strtoqstr(keymaps[i].getName());
677         new MidiKeyMapTreeWidgetItem(midiDevice->getId(), deviceItem,
678                                    strtoqstr(keymaps[i].getName()));
679     }
680 
681     // delete banks which are no longer present
682     //
683     std::vector<QTreeWidgetItem*> childrenToDelete;
684 
685 
686     n = 0;
687     while (n < deviceItem->childCount()) {
688 
689         QTreeWidgetItem* child = deviceItem->child(n);
690 
691         MidiBankTreeWidgetItem *bankItem =
692             dynamic_cast<MidiBankTreeWidgetItem *>(child);
693         if (bankItem) {
694             if (bankItem->getBank() >= int(banks.size()))
695                 childrenToDelete.push_back(child);
696             else { // update the banks MSB/LSB which might have changed
697                 bankItem->setPercussion(banks[bankItem->getBank()].isPercussion());
698                 bankItem->setMSB(banks[bankItem->getBank()].getMSB());
699                 bankItem->setLSB(banks[bankItem->getBank()].getLSB());
700             }
701         }
702 
703         MidiKeyMapTreeWidgetItem *keyItem =
704             dynamic_cast<MidiKeyMapTreeWidgetItem *>(child);
705         if (keyItem) {
706             if (!midiDevice->getKeyMappingByName(qstrtostr(keyItem->getName()))) {
707                 childrenToDelete.push_back(child);
708             }
709         }
710 
711 //         child = child->nextSibling();
712         n += 1;
713     }
714 
715     for (size_t i = 0; i < childrenToDelete.size(); ++i)
716         delete childrenToDelete[i];
717 }
718 
719 bool
deviceItemHasBank(MidiDeviceTreeWidgetItem * deviceItem,int bankNb)720 BankEditorDialog::deviceItemHasBank(MidiDeviceTreeWidgetItem* deviceItem, int bankNb)
721 {
722 //     QTreeWidgetItem *child = deviceItem->firstChild();
723     MidiBankTreeWidgetItem *bankItem = nullptr;
724     QTreeWidgetItem *child = nullptr;
725 
726     int n = 0;
727     while (n < deviceItem->childCount()) {
728         child = deviceItem->child(n);
729         bankItem = dynamic_cast<MidiBankTreeWidgetItem*>(child);
730         if (bankItem) {
731             if (bankItem->getBank() == bankNb)
732                 return true;
733         }
734 //         child = child->nextSibling();
735         n += 1;
736     }
737 
738     return false;
739 }
740 
741 void
clearItemChildren(QTreeWidgetItem * item)742 BankEditorDialog::clearItemChildren(QTreeWidgetItem* item)
743 {
744 //     QTreeWidgetItem* child = 0;
745 
746 //     while ((child = item->child(0)))
747 //         delete child;
748     while ((item->childCount() > 0))
749         delete item->child(0);
750 }
751 
752 MidiDevice*
getCurrentMidiDevice()753 BankEditorDialog::getCurrentMidiDevice()
754 {
755     return getMidiDevice(m_treeWidget->currentItem());
756 }
757 
758 
slotPopulateDeviceEditors(QTreeWidgetItem * item,QTreeWidgetItem *)759 void BankEditorDialog::slotPopulateDeviceEditors(QTreeWidgetItem* item, QTreeWidgetItem*)
760 {
761     RG_DEBUG << "BankEditorDialog::slotPopulateDeviceEditors";
762 
763     if (!item)
764         return ;
765 
766     populateDeviceEditors(item);
767 }
768 
769 
populateDeviceEditors(QTreeWidgetItem * item)770 void BankEditorDialog::populateDeviceEditors(QTreeWidgetItem* item)
771 {
772     /**
773     *   shows the program and bank editors
774     *   and calls their populate( currentBank ) functions
775     **/
776     RG_DEBUG << "BankEditorDialog::populateDeviceEditors \n";
777 
778     if (!item)
779         return ;
780 
781     MidiKeyMapTreeWidgetItem *keyItem = dynamic_cast<MidiKeyMapTreeWidgetItem *>(item);
782 
783     if (keyItem) {
784 
785         enterActionState("on_key_item");
786         leaveActionState("on_bank_item");
787 
788         m_delete->setEnabled(true);
789 
790         MidiDevice *device = getMidiDevice(keyItem->getDeviceId());
791         if (!device)
792             return ;
793 
794         setProgramList(device);
795 
796         m_lastDevice = keyItem->getDeviceId();
797         m_keyMappingEditor->populate(item);
798 
799         m_programEditor->hide();
800         m_keyMappingEditor->show();
801 
802         m_rightSide->setEnabled(true);
803 
804         return ;
805     }
806 
807     MidiBankTreeWidgetItem* bankItem = dynamic_cast<MidiBankTreeWidgetItem*>(item);
808 
809     if (bankItem) {
810 
811         enterActionState("on_bank_item");
812         leaveActionState("on_key_item");
813 
814         m_delete->setEnabled(true);
815         m_copyPrograms->setEnabled(true);
816 
817         m_rightSide->setEnabled(true);
818 
819         if (m_copyBank.first != Device::NO_DEVICE) m_pastePrograms->setEnabled(true);
820 
821         MidiDevice *device = getMidiDevice(bankItem->getDeviceId());
822         if (!device)
823             return ;
824 
825         if (!m_keepBankList || m_bankList.size() == 0) {
826             m_bankList = device->getBanks();
827         } else {
828             m_keepBankList = false;
829         }
830 
831         setProgramList(device);
832 
833         m_variationToggle->setChecked(device->getVariationType() !=
834                                       MidiDevice::NoVariations);
835         m_variationCombo->setEnabled(m_variationToggle->isChecked());
836         m_variationCombo->setCurrentIndex
837         (device->getVariationType() ==
838          MidiDevice::VariationFromLSB ? 0 : 1);
839 
840         m_lastBank = m_bankList[bankItem->getBank()];
841 
842         m_programEditor->populate(item);
843 
844         m_keyMappingEditor->hide();
845         m_programEditor->show();
846 
847         m_lastDevice = bankItem->getDeviceId();
848 
849         return ;
850     }
851 
852     // Device, not bank or key mapping
853     // Ensure we fill these lists for the new device
854     //
855     MidiDeviceTreeWidgetItem* deviceItem = getParentDeviceItem(item);
856     if (!deviceItem) {
857         RG_DEBUG << "BankEditorDialog::populateDeviceEditors - got no deviceItem (banks parent item) \n";
858         return ;
859     }
860 
861 
862     m_lastDevice = deviceItem->getDeviceId();
863 
864     MidiDevice *device = getMidiDevice(deviceItem);
865     if (!device) {
866         RG_DEBUG << "BankEditorDialog::populateDeviceEditors - no device for this item\n";
867         return ;
868     }
869 
870     // User must have selected a device (top level Widget)
871     // Fetch The chosen device and info.
872     // Deactivate the rightside panel and clear its contents.
873     m_bankList = device->getBanks();
874     setProgramList(device);
875 
876     RG_DEBUG << "BankEditorDialog::populateDeviceEditors : not a bank item - disabling";
877     m_delete->setEnabled(false);
878     m_copyPrograms->setEnabled(false);
879     m_pastePrograms->setEnabled(false);
880     m_rightSide->setEnabled(false);
881 
882     m_variationToggle->setChecked(device->getVariationType() !=
883                                   MidiDevice::NoVariations);
884     m_variationCombo->setEnabled(m_variationToggle->isChecked());
885     m_variationCombo->setCurrentIndex
886                 (device->getVariationType() ==
887                      MidiDevice::VariationFromLSB ? 0 : 1);
888 
889     leaveActionState("on_bank_item");
890     leaveActionState("on_key_item");
891 
892     m_programEditor->clearAll();
893     m_keyMappingEditor->clearAll();
894 }
895 
896 void
slotApply()897 BankEditorDialog::slotApply()
898 {
899     RG_DEBUG << "BankEditorDialog::slotApply()\n";
900 
901     ModifyDeviceCommand *command = nullptr;
902 
903     MidiDevice *device = getMidiDevice(m_lastDevice);
904 
905     // Make sure that we don't delete all the banks and programs
906     // if we've not populated them here yet.
907     //
908     if (m_bankList.size() == 0 && m_programList.size() == 0 &&
909             m_deleteAllReally == true) {
910         RG_DEBUG << "BankEditorDialog::slotApply() : m_bankList size = 0\n";
911 
912         command = new ModifyDeviceCommand(m_studio,
913                                           m_lastDevice,
914                                           m_deviceNameMap[m_lastDevice],
915                                           device->getLibrarianName(),
916                                           device->getLibrarianEmail());
917 
918         command->clearBankAndProgramList();
919     } else {
920         MidiDevice::VariationType variation =
921             MidiDevice::NoVariations;
922         if (m_variationToggle->isChecked()) {
923             if (m_variationCombo->currentIndex() == 0) {
924                 variation = MidiDevice::VariationFromLSB;
925             } else {
926                 variation = MidiDevice::VariationFromMSB;
927             }
928         }
929 
930         RG_DEBUG << "BankEditorDialog::slotApply() : m_bankList size = "
931         << m_bankList.size();
932 
933         command = new ModifyDeviceCommand(m_studio,
934                                           m_lastDevice,
935                                           m_deviceNameMap[m_lastDevice],
936                                           device->getLibrarianName(),
937                                           device->getLibrarianEmail());
938 
939         MidiKeyMapTreeWidgetItem *keyItem = dynamic_cast<MidiKeyMapTreeWidgetItem*>
940                                           (m_treeWidget->currentItem());
941         if (keyItem) {
942             KeyMappingList kml(device->getKeyMappings());
943             for (size_t i = 0; i < kml.size(); ++i) {
944                 if (kml[i].getName() == qstrtostr(keyItem->getName())) {
945                     kml[i] = m_keyMappingEditor->getMapping();
946                     break;
947                 }
948             }
949             command->setKeyMappingList(kml);
950         }
951 
952         command->setVariation(variation);
953         command->setBankList(m_bankList);
954         command->setProgramList(m_programList);
955     }
956 
957     addCommandToHistory(command);
958 
959     // Our freaky fudge to update instrument/device names externally
960     //
961     if (m_updateDeviceList) {
962         emit deviceNamesChanged();
963         m_updateDeviceList = false;
964     }
965 
966 }
967 
968 void
slotReset()969 BankEditorDialog::slotReset()
970 {
971     resetProgramList();
972 
973     m_programEditor->reset();
974     m_programEditor->populate(m_treeWidget->currentItem());
975     m_keyMappingEditor->reset();
976     m_keyMappingEditor->populate(m_treeWidget->currentItem());
977 
978     MidiDeviceTreeWidgetItem* deviceItem = getParentDeviceItem
979                                          (m_treeWidget->currentItem());
980 
981     if (deviceItem) {
982         MidiDevice *device = getMidiDevice(deviceItem);
983         m_variationToggle->setChecked(device->getVariationType() !=
984                                       MidiDevice::NoVariations);
985         m_variationCombo->setEnabled(m_variationToggle->isChecked());
986         m_variationCombo->setCurrentIndex
987         (device->getVariationType() ==
988          MidiDevice::VariationFromLSB ? 0 : 1);
989     }
990 
991     updateDialog();
992 }
993 
994 void
resetProgramList()995 BankEditorDialog::resetProgramList()
996 {
997     m_programList = m_oldProgramList;
998 }
999 
1000 void
setProgramList(MidiDevice * device)1001 BankEditorDialog::setProgramList(MidiDevice *device)
1002 {
1003     m_programList = device->getPrograms();
1004     m_oldProgramList = m_programList;
1005 }
1006 
1007 void
slotUpdate()1008 BankEditorDialog::slotUpdate()
1009 {
1010     updateDialog();
1011 }
1012 
1013 MidiDeviceTreeWidgetItem*
getParentDeviceItem(QTreeWidgetItem * item)1014 BankEditorDialog::getParentDeviceItem(QTreeWidgetItem* item)
1015 {
1016     /**
1017     *   return the parent t.w.Item of a bank or keymap (which is a MidiDeviceTreeWidgetItem )
1018     **/
1019     if (!item)
1020         return nullptr;
1021 
1022     if (dynamic_cast<MidiBankTreeWidgetItem*>(item)){
1023         // go up to the parent device item
1024         item = item->parent();
1025     }
1026     else if (dynamic_cast<MidiKeyMapTreeWidgetItem*>(item)){
1027         // go up to the parent device item
1028         item = item->parent();
1029     }
1030 
1031     if (!item) {
1032         RG_DEBUG << "BankEditorDialog::getParentDeviceItem : missing parent device item for bank item - this SHOULD NOT HAPPEN";
1033         return nullptr;
1034     }
1035 
1036     return dynamic_cast<MidiDeviceTreeWidgetItem*>(item);
1037 //    QTreeWidgetItem *parent = item->parent();
1038 //    if (!parent) {
1039 //        // item must have been a top level widget
1040 //        parent = item;
1041 //    }
1042 //
1043 //    return dynamic_cast<MidiDeviceTreeWidgetItem*>(parent);
1044 }
1045 
1046 void
slotAddBank()1047 BankEditorDialog::slotAddBank()
1048 {
1049     if (!m_treeWidget->currentItem())
1050         return ;
1051 
1052     QTreeWidgetItem* currentIndex = m_treeWidget->currentItem();
1053 
1054     MidiDeviceTreeWidgetItem* deviceItem = getParentDeviceItem(currentIndex);
1055     MidiDevice *device = getMidiDevice(currentIndex);
1056 
1057     if (device) {
1058         // If the bank and program lists are empty then try to
1059         // populate them.
1060         //
1061         if (m_bankList.size() == 0 && m_programList.size() == 0) {
1062             m_bankList = device->getBanks();
1063             setProgramList(device);
1064         }
1065 
1066         QString name = "";
1067         int n = 0;
1068         while (name == "" || device->getBankByName(qstrtostr(name)) != nullptr) {
1069             ++n;
1070             if (n == 1)
1071                 name = tr("<new bank>");
1072             else
1073                 name = tr("<new bank %1>").arg(n);
1074         }
1075         std::pair<int, int> bank = getFirstFreeBank(m_treeWidget->currentItem());
1076 
1077         MidiBank newBank(false,
1078                          bank.first, bank.second,
1079                          qstrtostr(name));
1080         m_bankList.push_back(newBank);
1081 
1082 RG_DEBUG << "BankEditorDialog::slotAddBank() : deviceItem->getDeviceId() = " << deviceItem->getDeviceId();
1083         QTreeWidgetItem* newBankItem =
1084             new MidiBankTreeWidgetItem(deviceItem->getDeviceId(),
1085                                      m_bankList.size() - 1,
1086                                      deviceItem,
1087                                      strtoqstr(newBank.getName()),
1088                                      newBank.isPercussion(),
1089                                      newBank.getMSB(), newBank.getLSB());
1090         keepBankListForNextPopulate();
1091         m_treeWidget->setCurrentItem(newBankItem);
1092 
1093         slotApply();
1094     }
1095 }
1096 
1097 void
slotAddKeyMapping()1098 BankEditorDialog::slotAddKeyMapping()
1099 {
1100     if (!m_treeWidget->currentItem())
1101         return ;
1102 
1103     QTreeWidgetItem* currentIndex = m_treeWidget->currentItem();
1104 //    MidiDeviceTreeWidgetItem* deviceItem = getParentDeviceItem(currentIndex);
1105     MidiDevice *device = getMidiDevice(currentIndex);
1106 
1107     if (device) {
1108 
1109         QString name = "";
1110         int n = 0;
1111         while (name == "" || device->getKeyMappingByName(qstrtostr(name)) != nullptr) {
1112             ++n;
1113             if (n == 1)
1114                 name = tr("<new mapping>");
1115             else
1116                 name = tr("<new mapping %1>").arg(n);
1117         }
1118 
1119         MidiKeyMapping newKeyMapping(qstrtostr(name));
1120 
1121         ModifyDeviceCommand *command = new ModifyDeviceCommand
1122                                        (m_studio,
1123                                         device->getId(),
1124                                         device->getName(),
1125                                         device->getLibrarianName(),
1126                                         device->getLibrarianEmail());
1127 
1128         KeyMappingList kml;
1129         kml.push_back(newKeyMapping);
1130         command->setKeyMappingList(kml);
1131         command->setOverwrite(false);
1132         command->setRename(false);
1133 
1134         addCommandToHistory(command);
1135 
1136         updateDialog();
1137         selectDeviceItem(device);
1138     }
1139 }
1140 
1141 void
slotDelete()1142 BankEditorDialog::slotDelete()
1143 {
1144     if (!m_treeWidget->currentItem())
1145         return ;
1146 
1147     QTreeWidgetItem* currentIndex = m_treeWidget->currentItem();
1148 
1149     MidiBankTreeWidgetItem* bankItem = dynamic_cast<MidiBankTreeWidgetItem*>(currentIndex);
1150 
1151     MidiDevice *device = getMidiDevice(currentIndex);
1152 
1153     if (device && bankItem) {
1154         int currentBank = bankItem->getBank();
1155 
1156         int reply =
1157             QMessageBox::warning(
1158               dynamic_cast<QWidget*>(this),
1159               "", /* no title */
1160               tr("Really delete this bank?"),
1161               QMessageBox::Yes | QMessageBox::No,
1162               QMessageBox::No);
1163 
1164         if (reply == QMessageBox::Yes) {
1165             MidiBank bank = m_bankList[currentBank];
1166 
1167             // Copy across all programs that aren't in the doomed bank
1168             //
1169             ProgramList newProgramList;
1170             for (ProgramList::iterator it = m_programList.begin();
1171                  it != m_programList.end();
1172                  ++it) {
1173                 // If this program isn't in the bank that is being deleted,
1174                 // add it to the new program list.  We use partialCompare()
1175                 // because the MidiBank objects in the program list do not
1176                 // have their name fields filled in.
1177                 if (!it->getBank().partialCompare(bank))
1178                     newProgramList.push_back(*it);
1179             }
1180 
1181             // Erase the bank and repopulate
1182             //
1183             BankList::iterator er =
1184                 m_bankList.begin();
1185             er += currentBank;
1186             m_bankList.erase(er);
1187             m_programList = newProgramList;
1188             keepBankListForNextPopulate();
1189 
1190             // the listview automatically selects a new current item
1191             m_treeWidget->blockSignals(true);
1192             delete currentIndex;
1193             m_treeWidget->blockSignals(false);
1194 
1195             // Don't allow pasting from this defunct device
1196             //
1197             if (m_copyBank.first == bankItem->getDeviceId() &&
1198                     m_copyBank.second == bankItem->getBank()) {
1199                 m_pastePrograms->setEnabled(false);
1200                 m_copyBank = std::pair<DeviceId, int>
1201                              (Device::NO_DEVICE, -1);
1202             }
1203 
1204             slotApply();
1205             selectDeviceItem(device);
1206         }
1207 
1208         return ;
1209     }
1210 
1211     MidiKeyMapTreeWidgetItem* keyItem = dynamic_cast<MidiKeyMapTreeWidgetItem*>(currentIndex);
1212 
1213     if (keyItem && device) {
1214 
1215         int reply =
1216             QMessageBox::warning(
1217               dynamic_cast<QWidget*>(this),
1218               "", /* no title */
1219               tr("Really delete this key mapping?"),
1220               QMessageBox::Yes | QMessageBox::No,
1221               QMessageBox::No);
1222 
1223         if (reply == QMessageBox::Yes) {
1224 
1225             std::string keyMappingName = qstrtostr(keyItem->getName());
1226 
1227             ModifyDeviceCommand *command = new ModifyDeviceCommand
1228                                            (m_studio,
1229                                             device->getId(),
1230                                             device->getName(),
1231                                             device->getLibrarianName(),
1232                                             device->getLibrarianEmail());
1233 
1234             KeyMappingList kml = device->getKeyMappings();
1235 
1236             for (KeyMappingList::iterator i = kml.begin();
1237                     i != kml.end(); ++i) {
1238                 if (i->getName() == keyMappingName) {
1239                     RG_DEBUG << "erasing " << keyMappingName;
1240                     kml.erase(i);
1241                     break;
1242                 }
1243             }
1244 
1245             RG_DEBUG << " setting " << kml.size() << " key mappings to device ";
1246 
1247             command->setKeyMappingList(kml);
1248             command->setOverwrite(true);
1249 
1250             addCommandToHistory(command);
1251 
1252             RG_DEBUG << " device has " << device->getKeyMappings().size() << " key mappings now ";
1253 
1254             updateDialog();
1255         }
1256 
1257         return ;
1258     }
1259 }
1260 
1261 void
slotDeleteAll()1262 BankEditorDialog::slotDeleteAll()
1263 {
1264     if (!m_treeWidget->currentItem())
1265         return ;
1266 
1267     QTreeWidgetItem* currentIndex = m_treeWidget->currentItem();
1268     MidiDeviceTreeWidgetItem* deviceItem = getParentDeviceItem(currentIndex);
1269     MidiDevice *device = getMidiDevice(deviceItem);
1270 
1271     QString question = tr("Really delete all banks for ") +
1272                        strtoqstr(device->getName()) + QString(" ?");
1273 
1274     int reply = QMessageBox::warning(
1275                   dynamic_cast<QWidget*>(this),
1276                   "", /* no title */
1277                   question,
1278                   QMessageBox::Yes | QMessageBox::No,
1279                   QMessageBox::No);
1280 
1281     if (reply == QMessageBox::Yes) {
1282 
1283         // erase all bank items
1284         QTreeWidgetItem* child = nullptr;
1285         while ((child = deviceItem->child(0)))
1286             delete child;
1287 
1288         m_bankList.clear();
1289         m_programList.clear();
1290 
1291         // Don't allow pasting from this defunct device
1292         //
1293         if (m_copyBank.first == deviceItem->getDeviceId()) {
1294             m_pastePrograms->setEnabled(false);
1295             m_copyBank = std::pair<DeviceId, int>
1296                          (Device::NO_DEVICE, -1);
1297         }
1298 
1299         // Urgh, we have this horrible flag that we're using to frig this.
1300         // (we might not need this anymore but I'm too scared to remove it
1301         // now).
1302         //
1303         m_deleteAllReally = true;
1304         slotApply();
1305         m_deleteAllReally = false;
1306 
1307         selectDeviceItem(device);
1308 
1309     }
1310 }
1311 
1312 MidiDevice*
getMidiDevice(DeviceId id)1313 BankEditorDialog::getMidiDevice(DeviceId id)
1314 {
1315     Device *device = m_studio->getDevice(id);
1316     MidiDevice *midiDevice =
1317         dynamic_cast<MidiDevice *>(device);
1318 
1319     /*
1320     if (device) {
1321     if (!midiDevice) {
1322      std::cerr << "ERROR: BankEditorDialog::getMidiDevice: device "
1323         << id << " is not a MIDI device" << std::endl;
1324     }
1325     } else {
1326     std::cerr
1327      << "ERROR: BankEditorDialog::getMidiDevice: no such device as "
1328      << id << std::endl;
1329     }
1330     */
1331 
1332     return midiDevice;
1333 }
1334 
1335 MidiDevice*
getMidiDevice(QTreeWidgetItem * item)1336 BankEditorDialog::getMidiDevice(QTreeWidgetItem* item)
1337 {
1338     MidiDeviceTreeWidgetItem* deviceItem =
1339         dynamic_cast<MidiDeviceTreeWidgetItem*>(item);
1340     if (!deviceItem)
1341         return nullptr;
1342 
1343     return getMidiDevice(deviceItem->getDeviceId());
1344 }
1345 
1346 std::pair<int, int>
getFirstFreeBank(QTreeWidgetItem *)1347 BankEditorDialog::getFirstFreeBank(QTreeWidgetItem* /* item */)
1348 {
1349     //!!! percussion? this is actually only called in the expectation
1350     // that percussion==false at the moment
1351 
1352     for (int msb = 0; msb < 128; ++msb) {
1353         for (int lsb = 0; lsb < 128; ++lsb) {
1354             BankList::iterator i = m_bankList.begin();
1355             for (; i != m_bankList.end(); ++i) {
1356                 if (i->getLSB() == lsb && i->getMSB() == msb) {
1357                     break;
1358                 }
1359             }
1360             if (i == m_bankList.end())
1361                 return std::pair<int, int>(msb, lsb);
1362         }
1363     }
1364 
1365     return std::pair<int, int>(0, 0);
1366 }
1367 
1368 void
slotModifyDeviceOrBankName(QTreeWidgetItem * item,int)1369 BankEditorDialog::slotModifyDeviceOrBankName(QTreeWidgetItem* item, int)
1370 {
1371     RG_DEBUG << "BankEditorDialog::slotModifyDeviceOrBankName";
1372 
1373     MidiDeviceTreeWidgetItem* deviceItem =
1374         dynamic_cast<MidiDeviceTreeWidgetItem*>(item);
1375     MidiBankTreeWidgetItem* bankItem =
1376         dynamic_cast<MidiBankTreeWidgetItem*>(item);
1377     MidiKeyMapTreeWidgetItem *keyItem =
1378         dynamic_cast<MidiKeyMapTreeWidgetItem*>(item);
1379 
1380     QString label = item->text(0);
1381     if (bankItem) {
1382 
1383         // renaming a bank item
1384 
1385         RG_DEBUG << "BankEditorDialog::slotModifyDeviceOrBankName - "
1386         << "modify bank name to " << label;
1387 
1388         if (m_bankList[bankItem->getBank()].getName() != qstrtostr(label)) {
1389             m_bankList[bankItem->getBank()].setName(qstrtostr(label));
1390             slotApply();
1391         }
1392 
1393     } else if (keyItem) {
1394 
1395         RG_DEBUG << "BankEditorDialog::slotModifyDeviceOrBankName - "
1396         << "modify key mapping name to " << label;
1397 
1398         QString oldName = keyItem->getName();
1399 
1400         QTreeWidgetItem* currentIndex = m_treeWidget->currentItem();
1401         MidiDevice *device = getMidiDevice(currentIndex);
1402 
1403         if (device) {
1404 
1405             ModifyDeviceCommand *command = new ModifyDeviceCommand
1406                                            (m_studio,
1407                                             device->getId(),
1408                                             device->getName(),
1409                                             device->getLibrarianName(),
1410                                             device->getLibrarianEmail());
1411 
1412             KeyMappingList kml = device->getKeyMappings();
1413 
1414             for (KeyMappingList::iterator i = kml.begin();
1415                     i != kml.end(); ++i) {
1416                 if (i->getName() == qstrtostr(oldName)) {
1417                     i->setName(qstrtostr(label));
1418                     break;
1419                 }
1420             }
1421 
1422             command->setKeyMappingList(kml);
1423             command->setOverwrite(true);
1424 
1425             addCommandToHistory(command);
1426 
1427             updateDialog();
1428         }
1429 
1430     } else if (deviceItem) { // must be last, as the others are subclasses
1431 
1432         // renaming a device item
1433 
1434         RG_DEBUG << "BankEditorDialog::slotModifyDeviceOrBankName - "
1435         << "modify device name to " << label;
1436 
1437         if (m_deviceNameMap[deviceItem->getDeviceId()] != qstrtostr(label)) {
1438             m_deviceNameMap[deviceItem->getDeviceId()] = qstrtostr(label);
1439             slotApply();
1440 
1441             m_updateDeviceList = true;
1442         }
1443 
1444     }
1445 
1446 }
1447 
1448 void
selectDeviceItem(MidiDevice * device)1449 BankEditorDialog::selectDeviceItem(MidiDevice *device)
1450 {
1451     /**
1452      * sets the device-TreeWidgetItem (visibly) selected
1453      **/
1454     QTreeWidgetItem *child;
1455     MidiDeviceTreeWidgetItem *midiDeviceItem;
1456     MidiDevice *midiDevice;
1457     unsigned int i, cnt;
1458 
1459     cnt = m_treeWidget->topLevelItemCount();
1460     for( i=0; i<cnt; i++ ){
1461         child = m_treeWidget->topLevelItem( i );
1462         midiDeviceItem = dynamic_cast<MidiDeviceTreeWidgetItem*>(child);
1463 
1464         if (midiDeviceItem) {
1465             midiDevice = getMidiDevice(midiDeviceItem);
1466 
1467             if (midiDevice == device) {
1468 //                 m_treeWidget->setSelected(child, true);
1469                 m_treeWidget->setCurrentItem(child);
1470                 return ;
1471             }
1472         }
1473 
1474     }
1475 
1476 }
1477 
1478 void
selectDeviceBankItem(DeviceId deviceId,int bank)1479 BankEditorDialog::selectDeviceBankItem(DeviceId deviceId,
1480                                        int bank)
1481 {
1482     /**
1483      * sets the device-TreeWidgetItem and bank-TreeWidgetItem with deviceId (visibly) selected
1484     **/
1485 //     QTreeWidgetItem *deviceChild = m_treeWidget->topLevelItem(0);
1486     QTreeWidgetItem *bankChild;
1487 //     int deviceCount = 0, bankCount = 0;
1488 
1489 
1490     QTreeWidgetItem *twItemDevice = nullptr;
1491     MidiDeviceTreeWidgetItem *midiDeviceItem = nullptr;
1492 
1493     int bankI;
1494     int devI = 0;
1495 //     while(n < m_treeWidget->childCount()){
1496     while( devI < m_treeWidget->topLevelItemCount() ){
1497 //         bankChild = deviceChild->firstChild();
1498 
1499         twItemDevice = m_treeWidget->topLevelItem(devI);
1500         midiDeviceItem = dynamic_cast<MidiDeviceTreeWidgetItem*>(twItemDevice);
1501 
1502         if (midiDeviceItem){    // && bankChild) {
1503 
1504             bankI = 0;
1505             while( bankI < twItemDevice->childCount() ){
1506                 bankChild = twItemDevice->child(bankI);
1507 
1508                 if ((deviceId == midiDeviceItem->getDeviceId()) && (bank == bankI)) {
1509 //                     m_treeWidget->setSelected(bankChild, true);
1510                     bankChild->setSelected(true);
1511                     return ;
1512                 }
1513                 bankI += 1;
1514 //             } while ((bankChild = bankChild->nextSibling()));
1515             }
1516         }
1517 
1518 //         deviceCount++;
1519 //         bankCount = 0;
1520         devI += 1;
1521 //     } while ((deviceChild = deviceChild->nextSibling()));
1522     }
1523 }
1524 
1525 void
slotVariationToggled()1526 BankEditorDialog::slotVariationToggled()
1527 {
1528     slotApply();
1529     m_variationCombo->setEnabled(m_variationToggle->isChecked());
1530 }
1531 
1532 void
slotVariationChanged(int)1533 BankEditorDialog::slotVariationChanged(int)
1534 {
1535     slotApply();
1536 }
1537 
1538 void
addCommandToHistory(Command * command)1539 BankEditorDialog::addCommandToHistory(Command *command)
1540 {
1541     CommandHistory::getInstance()->addCommand(command);
1542 }
1543 
1544 void
slotImport()1545 BankEditorDialog::slotImport()
1546 {
1547 #if QT_VERSION >= 0x050000
1548     QString home = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).path();
1549 #else
1550     QString home = QUrl::fromLocalFile(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)).path();
1551 #endif
1552     QString deviceDir = home + "/.local/share/rosegarden/library";
1553 
1554     QString url_str = FileDialog::getOpenFileName(this, tr("Import Banks from Device in File"), deviceDir,
1555                       tr("Rosegarden Device files") + " (*.rgd *.RGD)" + ";;" +
1556                       tr("Rosegarden files") + " (*.rg *.RG)" + ";;" +
1557                       tr("Sound fonts") + " (*.sf2 *.SF2)" + ";;" +
1558                       tr("LinuxSampler configuration files") + " (*.lscp *.LSCP)" + ";;" +
1559                       tr("All files") + " (*)", nullptr); ///!!! Should we allow 'All files' here since LinuxSampler files don't need to have an extension?!
1560 
1561     QUrl url(url_str);
1562 
1563     if (url.isEmpty())
1564         return ;
1565 
1566     ImportDeviceDialog *dialog = new ImportDeviceDialog(this, url);
1567     if (dialog->doImport() && dialog->exec() == QDialog::Accepted) {
1568 
1569         MidiDeviceTreeWidgetItem* deviceItem =
1570             dynamic_cast<MidiDeviceTreeWidgetItem*>
1571             (m_treeWidget->currentItem());    //### was ->selectedItem()
1572 
1573         if (!deviceItem) {
1574             QMessageBox::critical(
1575               dynamic_cast<QWidget*>(this),
1576               "", /* no title */
1577               tr("Some internal error: cannot locate selected device"),
1578               QMessageBox::Ok,
1579               QMessageBox::Ok);
1580             return ;
1581         }
1582 
1583         if (!dialog->haveDevice()) {
1584             QMessageBox::critical(
1585               dynamic_cast<QWidget*>(this),
1586               "", /* no title */
1587               tr("Some internal error: no device selected"),
1588               QMessageBox::Ok,
1589               QMessageBox::Ok);
1590             return ;
1591         }
1592 
1593         ModifyDeviceCommand *command = nullptr;
1594 
1595         BankList banks(dialog->getBanks());
1596         ProgramList programs(dialog->getPrograms());
1597         ControlList controls(dialog->getControllers());
1598         KeyMappingList keyMappings(dialog->getKeyMappings());
1599         MidiDevice::VariationType variation(dialog->getVariationType());
1600         std::string librarianName(dialog->getLibrarianName());
1601         std::string librarianEmail(dialog->getLibrarianEmail());
1602 
1603         // don't record the librarian when
1604         // merging banks -- it's misleading.
1605         // (also don't use variation type)
1606         if (!dialog->shouldOverwriteBanks()) {
1607             librarianName = "";
1608             librarianEmail = "";
1609         }
1610 
1611         command = new ModifyDeviceCommand(m_studio,
1612                                           deviceItem->getDeviceId(),
1613                                           dialog->getDeviceName(),
1614                                           librarianName,
1615                                           librarianEmail);
1616 
1617         if (dialog->shouldOverwriteBanks()) {
1618             command->setVariation(variation);
1619         }
1620         if (dialog->shouldImportBanks()) {
1621             command->setBankList(banks);
1622             command->setProgramList(programs);
1623         }
1624         if (dialog->shouldImportControllers()) {
1625             command->setControlList(controls);
1626         }
1627         if (dialog->shouldImportKeyMappings()) {
1628             command->setKeyMappingList(keyMappings);
1629         }
1630         command->setOverwrite(dialog->shouldOverwriteBanks());
1631         command->setRename(dialog->shouldRename());
1632 
1633         addCommandToHistory(command);
1634 
1635         // No need to redraw the dialog, this is done by
1636         // slotUpdate, signalled by the CommandHistory
1637         MidiDevice *device = getMidiDevice(deviceItem);
1638         if (device) {
1639             selectDeviceItem(device);
1640         }
1641     }
1642 
1643     delete dialog;
1644     updateDialog();
1645 }
1646 
1647 void
slotEdit(QTreeWidgetItem * item,int)1648 BankEditorDialog::slotEdit(QTreeWidgetItem * item, int)
1649 {
1650     RG_DEBUG << "BankEditorDialog::slotEdit()";
1651 
1652     if (item->flags() & Qt::ItemIsEditable)
1653         m_treeWidget->editItem(item);
1654 }
1655 
1656 void
slotEditCopy()1657 BankEditorDialog::slotEditCopy()
1658 {
1659     MidiBankTreeWidgetItem* bankItem
1660     = dynamic_cast<MidiBankTreeWidgetItem*>(m_treeWidget->currentItem());
1661 
1662     if (bankItem) {
1663         m_copyBank = std::pair<DeviceId, int>(bankItem->getDeviceId(),
1664                                               bankItem->getBank());
1665         m_pastePrograms->setEnabled(true);
1666     }
1667 }
1668 
1669 void
slotEditPaste()1670 BankEditorDialog::slotEditPaste()
1671 {
1672     MidiBankTreeWidgetItem* bankItem
1673     = dynamic_cast<MidiBankTreeWidgetItem*>(m_treeWidget->currentItem());
1674 
1675     if (bankItem) {
1676         // Get the full program and bank list for the source device
1677         //
1678         MidiDevice *device = getMidiDevice(m_copyBank.first);
1679         std::vector<MidiBank> tempBank = device->getBanks();
1680 
1681         ProgramList::iterator it;
1682         std::vector<MidiProgram> tempProg;
1683 
1684         // Remove programs that will be overwritten
1685         //
1686         for (it = m_programList.begin(); it != m_programList.end(); ++it) {
1687             if (!(it->getBank().partialCompare(m_lastBank)))
1688                 tempProg.push_back(*it);
1689         }
1690         m_programList = tempProg;
1691 
1692         // Now get source list and msb/lsb
1693         //
1694         tempProg = device->getPrograms();
1695         MidiBank sourceBank = tempBank[m_copyBank.second];
1696 
1697         // Add the new programs
1698         //
1699         for (it = tempProg.begin(); it != tempProg.end(); ++it) {
1700             if (it->getBank().partialCompare(sourceBank)) {
1701                 // Insert with new MSB and LSB
1702                 //
1703                 MidiProgram copyProgram(m_lastBank,
1704                                         it->getProgram(),
1705                                         it->getName());
1706 
1707                 m_programList.push_back(copyProgram);
1708             }
1709         }
1710 
1711         // Save these for post-apply
1712         //
1713         DeviceId devPos = bankItem->getDeviceId();
1714         int bankPos = bankItem->getBank();
1715 
1716         slotApply();
1717 
1718         // Select same bank
1719         //
1720         selectDeviceBankItem(devPos, bankPos);
1721     }
1722 }
1723 
1724 void
slotExport()1725 BankEditorDialog::slotExport()
1726 {
1727     QString extension = "rgd";
1728 
1729 /*
1730  * Qt4:
1731  * QString getSaveFileName (QWidget * parent = 0, const QString & caption =
1732  * QString(), const QString & dir = QString(), const QString & filter =
1733  * QString(), QString * selectedFilter = 0, Options options = 0)
1734  *
1735  * KDE3:
1736  * QString KFileDialog::getSaveFileName   ( const QString &   startDir =
1737  * QString::null,
1738  *   const QString &   filter = QString::null,
1739  *     QWidget *   parent = 0,
1740  *       const QString &   caption = QString::null
1741  *        )   [static]
1742  *
1743  */
1744 
1745     QString dir = ResourceFinder().getResourceSaveDir("library");
1746 
1747     QString name =
1748         FileDialog::getSaveFileName(this,
1749                                      tr("Export Device as..."),
1750                                      dir,
1751                                      (extension.isEmpty() ? QString("*") : ("*." + extension)));
1752 
1753     // Check for the existence of the name
1754     if (name.isEmpty())
1755         return ;
1756 
1757     // Append extension if we don't have one
1758     //
1759     if (!extension.isEmpty()) {
1760         if (!name.endsWith("." + extension)) {
1761             name += "." + extension;
1762         }
1763     }
1764 
1765     QFileInfo info(name);
1766 
1767     if (info.isDir()) {
1768         QMessageBox::warning(
1769           dynamic_cast<QWidget*>(this),
1770           "", /* no title */
1771           tr("You have specified a directory"),
1772           QMessageBox::Ok,
1773           QMessageBox::Ok);
1774         return ;
1775     }
1776 
1777     if (info.exists()) {
1778         int overwrite = QMessageBox::question(
1779                           dynamic_cast<QWidget*>(this),
1780                           "", /* no title */
1781                           tr("The specified file exists.  Overwrite?"),
1782                           QMessageBox::Yes | QMessageBox::No,
1783                           QMessageBox::No);
1784 
1785         if (overwrite != QMessageBox::Yes)
1786             return ;
1787     }
1788 
1789     MidiDeviceTreeWidgetItem* deviceItem =
1790             dynamic_cast<MidiDeviceTreeWidgetItem*>
1791                 (m_treeWidget->currentItem());
1792 
1793     std::vector<DeviceId> devices;
1794     MidiDevice *md = getMidiDevice(deviceItem);
1795 
1796     if (md) {
1797         ExportDeviceDialog *ed = new ExportDeviceDialog
1798                                  (this, strtoqstr(md->getName()));
1799         if (ed->exec() != QDialog::Accepted)
1800             return ;
1801         if (ed->getExportType() == ExportDeviceDialog::ExportOne) {
1802             devices.push_back(md->getId());
1803         }
1804     }
1805 
1806     QString errMsg;
1807     if (!m_doc->exportStudio(name, errMsg, devices)) {
1808         if (errMsg != "") {
1809             QMessageBox::critical
1810                 (nullptr, tr("Rosegarden"), tr(QString("Could not export studio to file at %1\n(%2)")
1811                            .arg(name).arg(errMsg).toStdString().c_str()));
1812         } else {
1813             QMessageBox::critical
1814                 (nullptr, tr("Rosegarden"), tr(QString("Could not export studio to file at %1")
1815                            .arg(name).toStdString().c_str()));
1816         }
1817     }
1818 }
1819 
1820 void
slotFileClose()1821 BankEditorDialog::slotFileClose()
1822 {
1823     RG_DEBUG << "BankEditorDialog::slotFileClose()\n";
1824 
1825     // We need to do this because we might be here due to a
1826     // documentAboutToChange signal, in which case the document won't
1827     // be valid by the time we reach the dtor, since it will be
1828     // triggered when the closeEvent is actually processed.
1829     //
1830 //     CommandHistory::getInstance()->detachView(actionCollection());    //&&&
1831     m_doc = nullptr;
1832     close();
1833 }
1834 
1835 void
closeEvent(QCloseEvent * e)1836 BankEditorDialog::closeEvent(QCloseEvent *e)
1837 {
1838     emit closing();
1839     QMainWindow::closeEvent(e);
1840 }
1841 
1842 
1843 void
slotHelpRequested()1844 BankEditorDialog::slotHelpRequested()
1845 {
1846     // TRANSLATORS: if the manual is translated into your language, you can
1847     // change the two-letter language code in this URL to point to your language
1848     // version, eg. "http://rosegardenmusic.com/wiki/doc:bankEditorDialog-es" for the
1849     // Spanish version. If your language doesn't yet have a translation, feel
1850     // free to create one.
1851     QString helpURL = tr("http://rosegardenmusic.com/wiki/doc:bankEditorDialog-en");
1852     QDesktopServices::openUrl(QUrl(helpURL));
1853 }
1854 
1855 void
slotHelpAbout()1856 BankEditorDialog::slotHelpAbout()
1857 {
1858     new AboutDialog(this);
1859 }
1860 }
1861