1 /*
2  * Copyright (C) 2018-2020 Rerrah
3  *
4  * Permission is hereby granted, free of charge, to any person
5  * obtaining a copy of this software and associated documentation
6  * files (the "Software"), to deal in the Software without
7  * restriction, including without limitation the rights to use,
8  * copy, modify, merge, publish, distribute, sublicense, and/or sell
9  * copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following
11  * conditions:
12  *
13  * The above copyright notice and this permission notice shall be
14  * included in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23  * OTHER DEALINGS IN THE SOFTWARE.
24  */
25 
26 #include "mainwindow.hpp"
27 #include "ui_mainwindow.h"
28 #include <algorithm>
29 #include <unordered_map>
30 #include <numeric>
31 #include <QString>
32 #include <QClipboard>
33 #include <QMenu>
34 #include <QAction>
35 #include <QDialog>
36 #include <QRegularExpression>
37 #include <QFileDialog>
38 #include <QFile>
39 #include <QFileInfo>
40 #include <QMimeData>
41 #include <QProgressDialog>
42 #include <QRect>
43 #include <QDesktopWidget>
44 #include <QMetaMethod>
45 #include <QScreen>
46 #include <QComboBox>
47 #include <QToolButton>
48 #include <QSignalBlocker>
49 #include <QTextCodec>
50 #include "jam_manager.hpp"
51 #include "song.hpp"
52 #include "track.hpp"
53 #include "instrument.hpp"
54 #include "bank.hpp"
55 #include "bank_io.hpp"
56 #include "file_io.hpp"
57 #include "version.hpp"
58 #include "gui/command/commands_qt.hpp"
59 #include "gui/instrument_editor/instrument_editor_fm_form.hpp"
60 #include "gui/instrument_editor/instrument_editor_ssg_form.hpp"
61 #include "gui/instrument_editor/instrument_editor_adpcm_form.hpp"
62 #include "gui/instrument_editor/instrument_editor_drumkit_form.hpp"
63 #include "gui/module_properties_dialog.hpp"
64 #include "gui/groove_settings_dialog.hpp"
65 #include "gui/configuration_dialog.hpp"
66 #include "gui/wave_export_settings_dialog.hpp"
67 #include "gui/vgm_export_settings_dialog.hpp"
68 #include "gui/instrument_selection_dialog.hpp"
69 #include "gui/s98_export_settings_dialog.hpp"
70 #include "gui/configuration_handler.hpp"
71 #include "gui/jam_layout.hpp"
72 #include "chips/scci/SCCIDefines.hpp"
73 #include "chips/c86ctl/c86ctl_wrapper.hpp"
74 #include "gui/file_history_handler.hpp"
75 #include "midi/midi.hpp"
76 #include "audio_stream_rtaudio.hpp"
77 #include "color_palette_handler.hpp"
78 #include "binary_container.hpp"
79 #include "wav_container.hpp"
80 #include "enum_hash.hpp"
81 #include "gui/go_to_dialog.hpp"
82 #include "gui/transpose_song_dialog.hpp"
83 #include "gui/swap_tracks_dialog.hpp"
84 #include "gui/hide_tracks_dialog.hpp"
85 #include "gui/track_visibility_memory_handler.hpp"
86 #include "gui/file_io_error_message_box.hpp"
87 #include "gui/gui_util.hpp"
88 
MainWindow(std::weak_ptr<Configuration> config,QString filePath,QWidget * parent)89 MainWindow::MainWindow(std::weak_ptr<Configuration> config, QString filePath, QWidget *parent) :
90 	QMainWindow(parent),
91 	ui(new Ui::MainWindow),
92 	config_(config),
93 	palette_(std::make_shared<ColorPalette>()),
94 	bt_(std::make_shared<BambooTracker>(config)),
95 	comStack_(std::make_shared<QUndoStack>(this)),
96 	fileHistory_(std::make_shared<FileHistory>()),
97 	scciDll_(std::make_unique<QLibrary>("scci")),
98 	c86ctlDll_(std::make_unique<QLibrary>("c86ctl")),
99 	instForms_(std::make_shared<InstrumentFormManager>()),
100 	renamingInstItem_(nullptr),
101 	renamingInstEdit_(nullptr),
102 	isModifiedForNotCommand_(false),
103 	hasLockedWigets_(false),
104 	isEditedPattern_(true),
105 	isEditedOrder_(false),
106 	isEditedInstList_(false),
107 	isSelectedPattern_(false),
108 	isSelectedOrder_(false),
109 	hasShownOnce_(false),
110 	firstViewUpdateRequest_(false),
111 	octUpSc_(nullptr),
112 	octDownSc_(nullptr),
113 	focusPtnSc_(this),
114 	focusOdrSc_(this),
115 	focusInstSc_(this),
116 	playAndStopSc_(nullptr),
117 	playStepSc_(nullptr),
118 	goPrevOdrSc_(nullptr),
119 	goNextOdrSc_(nullptr),
120 	prevInstSc_(nullptr),
121 	nextInstSc_(nullptr),
122 	incPtnSizeSc_(nullptr),
123 	decPtnSizeSc_(nullptr),
124 	incEditStepSc_(nullptr),
125 	decEditStepSc_(nullptr),
126 	prevSongSc_(nullptr),
127 	nextSongSc_(nullptr),
128 	jamVolUpSc_(nullptr),
129 	jamVolDownSc_(nullptr),
130 	bankJamMidiCtrl_(false)
131 {
132 	ui->setupUi(this);
133 
134 	if (config.lock()->getMainWindowX() == -1) {	// When unset
135 		QRect rec = geometry();
136 		rec.moveCenter(QGuiApplication::screens().front()->geometry().center());
137 		setGeometry(rec);
138 		config.lock()->setMainWindowX(x());
139 		config.lock()->setMainWindowY(y());
140 	}
141 	else {
142 		move(config.lock()->getMainWindowX(), config.lock()->getMainWindowY());
143 	}
144 	resize(config.lock()->getMainWindowWidth(), config.lock()->getMainWindowHeight());
145 	if (config.lock()->getMainWindowMaximized()) showMaximized();
146 	ui->action_Status_Bar->setChecked(config.lock()->getVisibleStatusBar());
147 	ui->statusBar->setVisible(config.lock()->getVisibleStatusBar());
148 	ui->actionFollow_Mode->setChecked(config.lock()->getFollowMode());
149 	ui->action_Instrument_Mask->setChecked(config.lock()->getInstrumentMask());
150 	ui->action_Volume_Mask->setChecked(config.lock()->getVolumeMask());
151 	ui->action_Wave_View->setChecked(config.lock()->getVisibleWaveView());
152 	ui->waveVisual->setVisible(config.lock()->getVisibleWaveView());
153 	bt_->setFollowPlay(config.lock()->getFollowMode());
154 	if (config.lock()->getPatternEditorHeaderFont().empty()) {
155 		config.lock()->setPatternEditorHeaderFont(ui->patternEditor->getHeaderFont().toStdString());
156 	}
157 	if (config.lock()->getPatternEditorRowsFont().empty()) {
158 		config.lock()->setPatternEditorRowsFont(ui->patternEditor->getRowsFont().toStdString());
159 	}
160 	if (config.lock()->getOrderListHeaderFont().empty()) {
161 		config.lock()->setOrderListHeaderFont(ui->orderList->getHeaderFont().toStdString());
162 	}
163 	if (config.lock()->getOrderListRowsFont().empty()) {
164 		config.lock()->setOrderListRowsFont(ui->orderList->getRowsFont().toStdString());
165 	}
166 	ui->patternEditor->setConfiguration(config_.lock());
167 	ui->orderList->setConfiguration(config_.lock());
168 	updateFonts();
169 	ui->orderList->setHorizontalScrollMode(config.lock()->getMoveCursorByHorizontalScroll(), false);
170 	ui->patternEditor->setHorizontalScrollMode(config.lock()->getMoveCursorByHorizontalScroll(), false);
171 	ui->patternEditor->setCore(bt_);
172 	ui->orderList->setCore(bt_);
173 	ColorPaletteHandler::loadPalette(palette_.get());
174 	ui->patternEditor->setColorPallete(palette_);
175 	ui->orderList->setColorPallete(palette_);
176 	updateInstrumentListColors();
177 	ui->waveVisual->setColorPalette(palette_);
178 
179 	/* Command stack */
180 	QObject::connect(comStack_.get(), &QUndoStack::indexChanged,
181 					 this, [&](int idx) {
182 		setWindowModified(idx || isModifiedForNotCommand_);
183 		ui->actionUndo->setEnabled(comStack_->canUndo());
184 		ui->actionRedo->setEnabled(comStack_->canRedo());
185 	});
186 
187 	/* File history */
188 	FileHistoryHandler::loadFileHistory(fileHistory_);
189 	for (size_t i = 0; i < fileHistory_->size(); ++i) {
190 		// Leave Before Qt5.7.0 style due to windows xp
191 		QAction* action = ui->menu_Recent_Files->addAction(QString("&%1 %2").arg(i + 1).arg(fileHistory_->at(i)));
192 		action->setData(fileHistory_->at(i));
193 	}
194 	QObject::connect(ui->menu_Recent_Files, &QMenu::triggered, this, [&](QAction* action) {
195 		if (action != ui->actionClear) {
196 			if (isWindowModified()) {
197 				QString modTitle = utf8ToQString(bt_->getModuleTitle());
198 				if (modTitle.isEmpty()) modTitle = tr("Untitled");
199 				QMessageBox dialog(QMessageBox::Warning,
200 								   "BambooTracker",
201 								   tr("Save changes to %1?").arg(modTitle),
202 								   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
203 				switch (dialog.exec()) {
204 				case QMessageBox::Yes:
205 					if (!on_actionSave_triggered()) return;
206 					break;
207 				case QMessageBox::No:
208 					break;
209 				case QMessageBox::Cancel:
210 					return;
211 				default:
212 					break;
213 				}
214 			}
215 			openModule(action->data().toString());
216 		}
217 	});
218 
219 	/* Menus */
220 	pasteModeGroup_ = std::make_unique<QActionGroup>(this);
221 	pasteModeGroup_->addAction(ui->action_Cursor);
222 	QObject::connect(ui->action_Cursor, &QAction::triggered, this, [&](bool checked) {
223 		if (checked) config_.lock()->setPasteMode(Configuration::CURSOR);
224 	});
225 	pasteModeGroup_->addAction(ui->action_Selection);
226 	QObject::connect(ui->action_Selection, &QAction::triggered, this, [&](bool checked) {
227 		if (checked) config_.lock()->setPasteMode(Configuration::SELECTION);
228 	});
229 	pasteModeGroup_->addAction(ui->action_Fill);
230 	QObject::connect(ui->action_Fill, &QAction::triggered, this, [&](bool checked) {
231 		if (checked) config_.lock()->setPasteMode(Configuration::FILL);
232 	});
233 	switch (config.lock()->getPasteMode()) {
234 	case Configuration::CURSOR:		ui->action_Cursor->setChecked(true);	break;
235 	case Configuration::SELECTION:	ui->action_Selection->setChecked(true);	break;
236 	case Configuration::FILL:		ui->action_Fill->setChecked(true);		break;
237 	}
238 
239 	/* Tool bars */
240 	auto octLab = new QLabel(tr("Octave"));
241 	octLab->setMargin(6);
242 	ui->subToolBar->addWidget(octLab);
243 	octave_ = new QSpinBox();
244 	octave_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
245 	octave_->setMaximumWidth(80);
246 	octave_->setMinimum(0);
247 	octave_->setMaximum(7);
248 	octave_->setValue(bt_->getCurrentOctave());
249 	// Leave Before Qt5.7.0 style due to windows xp
250 	QObject::connect(octave_, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
251 					 this, [&](int octave) { bt_->setCurrentOctave(octave); });
252 	ui->subToolBar->addWidget(octave_);
253 	auto volLab = new QLabel(tr("Volume"));
254 	volLab->setMargin(6);
255 	ui->subToolBar->addWidget(volLab);
256 	volume_ = new QSpinBox();
257 	volume_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
258 	volume_->setMaximumWidth(100);
259 	volume_->setMinimum(0);
260 	volume_->setMaximum(255);
261 	volume_->setDisplayIntegerBase(16);
262 	volume_->setValue(bt_->getCurrentVolume());
263 	// Leave Before Qt5.7.0 style due to windows xp
264 	QObject::connect(volume_, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
265 					 this, [&](int volume) { bt_->setCurrentVolume(volume); });
266 	ui->subToolBar->addWidget(volume_);
267 	ui->subToolBar->addSeparator();
268 	ui->subToolBar->addAction(ui->actionFollow_Mode);
269 	ui->subToolBar->addSeparator();
270 	auto hlLab1 = new QLabel(tr("Step highlight 1st"));
271 	hlLab1->setMargin(6);
272 	ui->subToolBar->addWidget(hlLab1);
273 	highlight1_ = new QSpinBox();
274 	highlight1_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
275 	highlight1_->setMaximumWidth(80);
276 	highlight1_->setMinimum(1);
277 	highlight1_->setMaximum(256);
278 	highlight1_->setValue(8);
279 	// Leave Before Qt5.7.0 style due to windows xp
280 	QObject::connect(highlight1_, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
281 					 this, [&](int count) {
282 		bt_->setModuleStepHighlight1Distance(static_cast<size_t>(count));
283 		ui->patternEditor->setPatternHighlight1Count(count);
284 	});
285 	ui->subToolBar->addWidget(highlight1_);
286 	auto hlLab2 = new QLabel(tr("2nd"));
287 	hlLab2->setMargin(6);
288 	ui->subToolBar->addWidget(hlLab2);
289 	highlight2_ = new QSpinBox();
290 	highlight2_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
291 	highlight2_->setMaximumWidth(80);
292 	highlight2_->setMinimum(1);
293 	highlight2_->setMaximum(256);
294 	highlight2_->setValue(8);
295 	// Leave Before Qt5.7.0 style due to windows xp
296 	QObject::connect(highlight2_, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
297 					 this, [&](int count) {
298 		bt_->setModuleStepHighlight2Distance(static_cast<size_t>(count));
299 		ui->patternEditor->setPatternHighlight2Count(count);
300 	});
301 	ui->subToolBar->addWidget(highlight2_);
302 	ui->subToolBar->addSeparator();
303 	auto& mainTbConfig = config.lock()->getMainToolbarConfiguration();
304 	if (mainTbConfig.getPosition() == Configuration::ToolbarConfiguration::FLOAT_POS) {
305 		ui->mainToolBar->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
306 		ui->mainToolBar->move(mainTbConfig.getX(), mainTbConfig.getY());
307 	}
308 	else {
309 		addToolBar(TB_POS_.at(mainTbConfig.getPosition()), ui->mainToolBar);
310 	}
311 	auto& subTbConfig = config.lock()->getSubToolbarConfiguration();
312 	if (subTbConfig.getPosition() == Configuration::ToolbarConfiguration::FLOAT_POS) {
313 		ui->subToolBar->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
314 		ui->subToolBar->move(subTbConfig.getX(), subTbConfig.getY());
315 	}
316 	else {
317 		auto pos = TB_POS_.at(subTbConfig.getPosition());
318 		if (subTbConfig.getNumber()) {
319 			if (subTbConfig.hasBreakBefore()) addToolBarBreak(pos);
320 			addToolBar(pos, ui->subToolBar);
321 		}
322 		else {
323 			if (mainTbConfig.getPosition() == subTbConfig.getPosition()) {
324 				insertToolBar(ui->mainToolBar, ui->subToolBar);
325 				if (mainTbConfig.hasBreakBefore()) insertToolBarBreak(ui->mainToolBar);
326 			}
327 			else {
328 				addToolBar(pos, ui->subToolBar);
329 			}
330 		}
331 	}
332 	ui->action_Toolbar->setChecked(config.lock()->getVisibleToolbar());
333 	ui->mainToolBar->setVisible(config.lock()->getVisibleToolbar());
334 	ui->subToolBar->setVisible(config.lock()->getVisibleToolbar());
335 
336 	/* Splitter */
337 	ui->splitter->setStretchFactor(0, 0);
338 	ui->splitter->setStretchFactor(1, 1);
339 
340 	/* Module settings */
341 	QObject::connect(ui->modTitleLineEdit, &QLineEdit::textEdited,
342 					 this, [&](QString str) {
343 		bt_->setModuleTitle(str.toUtf8().toStdString());
344 		setModifiedTrue();
345 		setWindowTitle();
346 	});
347 	QObject::connect(ui->authorLineEdit, &QLineEdit::textEdited,
348 					 this, [&](QString str) {
349 		bt_->setModuleAuthor(str.toUtf8().toStdString());
350 		setModifiedTrue();
351 	});
352 	QObject::connect(ui->copyrightLineEdit, &QLineEdit::textEdited,
353 					 this, [&](QString str) {
354 		bt_->setModuleCopyright(str.toUtf8().toStdString());
355 		setModifiedTrue();
356 	});
357 
358 	/* Edit settings */
359 	// Leave Before Qt5.7.0 style due to windows xp
360 	QObject::connect(ui->editableStepSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
361 					 this, [&](int n) {
362 		ui->patternEditor->setEditableStep(n);
363 		config_.lock()->setEditableStep(static_cast<size_t>(n));
364 	});
365 	ui->editableStepSpinBox->setValue(static_cast<int>(config.lock()->getEditableStep()));
366 	ui->patternEditor->setEditableStep(static_cast<int>(config.lock()->getEditableStep()));
367 
368 	ui->keyRepeatCheckBox->setCheckState(config.lock()->getKeyRepetition() ? Qt::Checked : Qt::Unchecked);
369 
370 	/* Song */
371 	// Leave Before Qt5.7.0 style due to windows xp
372 	QObject::connect(ui->songComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
373 					 this, [&](int num) {
374 		if (num == -1) return;
375 		freezeViews();
376 		if (!timer_) stream_->stop();
377 		bt_->setCurrentSongNumber(num);
378 		loadSong();
379 		if (!timer_) stream_->start();
380 	});
381 
382 	/* Song settings */
383 	// Leave Before Qt5.7.0 style due to windows xp
384 	QObject::connect(ui->tempoSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
385 					 this, [&](int tempo) {
386 		int curSong = bt_->getCurrentSongNumber();
387 		if (tempo != bt_->getSongTempo(curSong)) {
388 			bt_->setSongTempo(curSong, tempo);
389 			setModifiedTrue();
390 		}
391 	});
392 	// Leave Before Qt5.7.0 style due to windows xp
393 	QObject::connect(ui->speedSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
394 					 this, [&](int speed) {
395 		int curSong = bt_->getCurrentSongNumber();
396 		if (speed != bt_->getSongSpeed(curSong)) {
397 			bt_->setSongSpeed(curSong, speed);
398 			setModifiedTrue();
399 		}
400 	});
401 	// Leave Before Qt5.7.0 style due to windows xp
402 	QObject::connect(ui->patternSizeSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
403 					 this, [&](int size) {
404 		bt_->setDefaultPatternSize(bt_->getCurrentSongNumber(), static_cast<size_t>(size));
405 		ui->patternEditor->onDefaultPatternSizeChanged();
406 		setModifiedTrue();
407 	});
408 	// Leave Before Qt5.7.0 style due to windows xp
409 	QObject::connect(ui->grooveSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
410 					 this, [&](int n) {
411 		bt_->setSongGroove(bt_->getCurrentSongNumber(), n);
412 		setModifiedTrue();
413 	});
414 
415 	/* Instrument list */
416 	auto instToolBar = new QToolBar();
417 	instToolBar->setIconSize(QSize(16, 16));
418 	auto addMenu = new QMenu();
419 	addMenu->addActions({ ui->actionNew_Instrument, ui->actionNew_Drumki_t });
420 	auto addTB = new QToolButton();
421 	addTB->setPopupMode(QToolButton::MenuButtonPopup);
422 	addTB->setMenu(addMenu);
423 	addTB->setDefaultAction(ui->actionNew_Instrument);
424 	instToolBar->addWidget(addTB);
425 	instToolBar->addActions({ ui->actionRemove_Instrument, ui->actionClone_Instrument });
426 	instToolBar->addSeparator();
427 	instToolBar->addActions({ ui->actionLoad_From_File, ui->actionSave_To_File });
428 	instToolBar->addSeparator();
429 	instToolBar->addAction(ui->actionEdit);
430 	instToolBar->addSeparator();
431 	instToolBar->addAction(ui->actionRename_Instrument);
432 	ui->instrumentListGroupBox->layout()->addWidget(instToolBar);
433 	ui->instrumentList->installEventFilter(this);
434 	QObject::connect(ui->instrumentList, &DropDetectListWidget::itemDroppedAtItemIndex,
435 					 this, &MainWindow::swapInstruments);
436 
437 	/* Pattern editor */
438 	ui->patternEditor->setCommandStack(comStack_);
439 	ui->patternEditor->installEventFilter(this);
440 	QObject::connect(ui->patternEditor, &PatternEditor::currentTrackChanged,
441 					 ui->orderList, &OrderListEditor::onPatternEditorCurrentTrackChanged);
442 	QObject::connect(ui->patternEditor, &PatternEditor::currentOrderChanged,
443 					 ui->orderList, &OrderListEditor::onPatternEditorCurrentOrderChanged);
444 	QObject::connect(ui->patternEditor, &PatternEditor::focusIn,
445 					 this, &MainWindow::updateMenuByPattern);
446 	QObject::connect(ui->patternEditor, &PatternEditor::selected,
447 					 this, &MainWindow::updateMenuByPatternSelection);
448 	QObject::connect(ui->patternEditor, &PatternEditor::instrumentEntered,
449 					 this, [&](int num) {
450 		auto list = ui->instrumentList;
451 		if (num != -1) {
452 			for (int i = 0; i < list->count(); ++i) {
453 				if (list->item(i)->data(Qt::UserRole).toInt() == num) {
454 					list->setCurrentRow(i);
455 					return ;
456 				}
457 			}
458 		}
459 	});
460 	QObject::connect(ui->patternEditor, &PatternEditor::volumeEntered,
461 					 this, [&](int volume) { volume_->setValue(volume); });
462 	QObject::connect(ui->patternEditor, &PatternEditor::effectEntered,
463 					 this, [&](QString text) { statusDetail_->setText(text); });
464 	QObject::connect(ui->patternEditor, &PatternEditor::currentTrackChanged,
465 					 this, &MainWindow::onCurrentTrackChanged);
466 
467 	/* Order List */
468 	ui->orderList->setCommandStack(comStack_);
469 	ui->orderList->installEventFilter(this);
470 	QObject::connect(ui->orderList, &OrderListEditor::currentTrackChanged,
471 					 ui->patternEditor, &PatternEditor::onOrderListCurrentTrackChanged);
472 	QObject::connect(ui->orderList, &OrderListEditor::currentOrderChanged,
473 					 ui->patternEditor, &PatternEditor::onOrderListCrrentOrderChanged);
474 	QObject::connect(ui->orderList, &OrderListEditor::orderEdited,
475 					 ui->patternEditor, &PatternEditor::onOrderListEdited);
476 	QObject::connect(ui->orderList, &OrderListEditor::focusIn,
477 					 this, &MainWindow::updateMenuByOrder);
478 	QObject::connect(ui->orderList, &OrderListEditor::selected,
479 					 this, &MainWindow::updateMenuByOrderSelection);
480 	QObject::connect(ui->orderList, &OrderListEditor::currentTrackChanged,
481 					 this, &MainWindow::onCurrentTrackChanged);
482 
483 	/* Wave view */
484 	visualTimer_.reset(new QTimer);
485 	QObject::connect(visualTimer_.get(), &QTimer::timeout, this, &MainWindow::updateVisuals);
486 	if (config.lock()->getVisibleWaveView())
487 		visualTimer_->start(static_cast<int>(std::round(1000. / config.lock()->getWaveViewFrameRate())));
488 
489 	/* Status bar */
490 	statusDetail_ = new QLabel();
491 	statusDetail_->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
492 	statusStyle_ = new QLabel();
493 	statusStyle_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
494 	statusInst_ = new QLabel();
495 	statusInst_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
496 	statusOctave_ = new QLabel();
497 	statusOctave_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
498 	statusIntr_ = new QLabel();
499 	statusIntr_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
500 	statusMixer_ = new QLabel();
501 	statusMixer_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
502 	statusBpm_ = new QLabel();
503 	statusBpm_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
504 	statusPlayPos_ = new QLabel();
505 	statusPlayPos_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
506 	ui->statusBar->addWidget(statusDetail_, 4);
507 	ui->statusBar->addPermanentWidget(statusStyle_, 1);
508 	ui->statusBar->addPermanentWidget(statusInst_, 1);
509 	ui->statusBar->addPermanentWidget(statusOctave_, 1);
510 	ui->statusBar->addPermanentWidget(statusIntr_, 1);
511 	ui->statusBar->addPermanentWidget(statusMixer_, 1);
512 	ui->statusBar->addPermanentWidget(statusBpm_, 1);
513 	ui->statusBar->addPermanentWidget(statusPlayPos_, 1);
514 	statusOctave_->setText(tr("Octave: %1").arg(bt_->getCurrentOctave()));
515 	statusIntr_->setText(QString::number(bt_->getModuleTickFrequency()) + QString("Hz"));
516 
517 	/* Bookmark */
518 	bmManForm_ = std::make_unique<BookmarkManagerForm>(bt_, config_.lock()->getShowRowNumberInHex());
519 	QObject::connect(bmManForm_.get(), &BookmarkManagerForm::positionJumpRequested,
520 					 this, [&](int order, int step) {
521 		if (bt_->isPlaySong()) return;
522 		int song = bt_->getCurrentSongNumber();
523 		if (static_cast<int>(bt_->getOrderSize(song)) <= order) return;
524 		if (static_cast<int>(bt_->getPatternSizeFromOrderNumber(song, order)) <= step) return;
525 		bt_->setCurrentOrderNumber(order);
526 		bt_->setCurrentStepNumber(step);
527 		ui->orderList->updatePositionByPositionJump();
528 		ui->patternEditor->updatepositionByPositionJump();
529 		activateWindow();
530 	});
531 	QObject::connect(bmManForm_.get(), &BookmarkManagerForm::modified, this, &MainWindow::setModifiedTrue);
532 
533 	// Shortcuts
534 	auto linkShortcut = [&](QAction* ptr) {
535 		ptr->setShortcutContext(Qt::WidgetWithChildrenShortcut);
536 		ui->orderList->addAction(ptr);
537 		ui->patternEditor->addAction(ptr);
538 	};
539 
540 	linkShortcut(&octUpSc_);
541 	QObject::connect(&octUpSc_, &QAction::triggered, this, [&] { changeOctave(true); });
542 	linkShortcut(&octDownSc_);
543 	QObject::connect(&octDownSc_, &QAction::triggered, this, [&] { changeOctave(false); });
544 	QObject::connect(&focusPtnSc_, &QShortcut::activated, this, [&] { ui->patternEditor->setFocus(); });
545 	QObject::connect(&focusOdrSc_, &QShortcut::activated, this, [&] { ui->orderList->setFocus(); });
546 	QObject::connect(&focusInstSc_, &QShortcut::activated, this, [&] {
547 		ui->instrumentList->setFocus();
548 		updateMenuByInstrumentList();
549 	});
550 	auto playLinkShortcut = [&](QAction* ptr) {
551 		ptr->setShortcutContext(Qt::WidgetShortcut);
552 		ui->instrumentList->addAction(ptr);
553 		ui->orderList->addActionToPanel(ptr);
554 		ui->patternEditor->addActionToPanel(ptr);
555 	};
556 	playLinkShortcut(ui->actionPlay);
557 	playLinkShortcut(&playAndStopSc_);
558 	QObject::connect(&playAndStopSc_, &QAction::triggered, this, [&] {
559 		if (bt_->isPlaySong()) stopPlaySong();
560 		else startPlaySong();
561 	});
562 	playLinkShortcut(&playStepSc_);
563 	QObject::connect(&playStepSc_, &QAction::triggered, this, &MainWindow::playStep);
564 	playLinkShortcut(ui->actionPlay_From_Start);
565 	playLinkShortcut(ui->actionPlay_Pattern);
566 	playLinkShortcut(ui->actionPlay_From_Cursor);
567 	playLinkShortcut(ui->actionPlay_From_Marker);
568 	playLinkShortcut(ui->actionStop);
569 	instAddSc_ = std::make_unique<QShortcut>(Qt::Key_Insert, ui->instrumentList,
570 											 nullptr, nullptr, Qt::WidgetShortcut);
571 	QObject::connect(instAddSc_.get(), &QShortcut::activated, this, &MainWindow::addInstrument);
572 	linkShortcut(&goPrevOdrSc_);
573 	QObject::connect(&goPrevOdrSc_, &QAction::triggered, this, [&] {
574 		ui->orderList->onGoOrderRequested(false);
575 	});
576 	linkShortcut(&goNextOdrSc_);
577 	QObject::connect(&goNextOdrSc_, &QAction::triggered, this, [&] {
578 		ui->orderList->onGoOrderRequested(true);
579 	});
580 	linkShortcut(&prevInstSc_);
581 	QObject::connect(&prevInstSc_, &QAction::triggered, this, [&] {
582 		if (ui->instrumentList->count()) {
583 			int row = ui->instrumentList->currentRow();
584 			if (row == -1) ui->instrumentList->setCurrentRow(0);
585 			else if (row > 0) ui->instrumentList->setCurrentRow(row - 1);
586 		}
587 	});
588 	linkShortcut(&nextInstSc_);
589 	QObject::connect(&nextInstSc_, &QAction::triggered, this, [&] {
590 		int cnt = ui->instrumentList->count();
591 		if (cnt) {
592 			int row = ui->instrumentList->currentRow();
593 			if (row == -1) ui->instrumentList->setCurrentRow(cnt - 1);
594 			else if (row < cnt - 1) ui->instrumentList->setCurrentRow(row + 1);
595 		}
596 	});
597 	linkShortcut(&incPtnSizeSc_);
598 	QObject::connect(&incPtnSizeSc_, &QAction::triggered, this, [&] {
599 		ui->patternSizeSpinBox->setValue(ui->patternSizeSpinBox->value() + 1);
600 	});
601 	linkShortcut(&decPtnSizeSc_);
602 	QObject::connect(&decPtnSizeSc_, &QAction::triggered, this, [&] {
603 		ui->patternSizeSpinBox->setValue(ui->patternSizeSpinBox->value() - 1);
604 	});
605 	linkShortcut(&incEditStepSc_);
606 	QObject::connect(&incEditStepSc_, &QAction::triggered, this, [&] {
607 		ui->editableStepSpinBox->setValue(ui->editableStepSpinBox->value() + 1);
608 	});
609 	linkShortcut(&decEditStepSc_);
610 	QObject::connect(&decEditStepSc_, &QAction::triggered, this, [&] {
611 		ui->editableStepSpinBox->setValue(ui->editableStepSpinBox->value() - 1);
612 	});
613 	linkShortcut(&prevSongSc_);
614 	QObject::connect(&prevSongSc_, &QAction::triggered, this, [&] {
615 		if (ui->songComboBox->isEnabled()) {
616 			ui->songComboBox->setCurrentIndex(std::max(ui->songComboBox->currentIndex() - 1, 0));
617 		}
618 	});
619 	linkShortcut(&nextSongSc_);
620 	QObject::connect(&nextSongSc_, &QAction::triggered, this, [&] {
621 		if (ui->songComboBox->isEnabled()) {
622 			ui->songComboBox->setCurrentIndex(std::min(ui->songComboBox->currentIndex() + 1,
623 													   ui->songComboBox->count() - 1));
624 		}
625 	});
626 	linkShortcut(&jamVolUpSc_);
627 	QObject::connect(&jamVolUpSc_, &QAction::triggered, this, [&] {
628 		volume_->setValue(volume_->value() + 1);
629 	});
630 	linkShortcut(&jamVolDownSc_);
631 	QObject::connect(&jamVolDownSc_, &QAction::triggered, this, [&] {
632 		volume_->setValue(volume_->value() - 1);
633 	});
634 	setShortcuts();
635 
636 	/* Clipboard */
637 	QObject::connect(QApplication::clipboard(), &QClipboard::dataChanged,
638 					 this, [&]() {
639 		if (isEditedOrder_) updateMenuByOrder();
640 		else if (isEditedPattern_) updateMenuByPattern();
641 	});
642 
643 	/* MIDI */
644 	setMidiConfiguration();
645 	midiKeyEventMethod_ = metaObject()->indexOfSlot("midiKeyEvent(uchar,uchar,uchar)");
646 	Q_ASSERT(midiKeyEventMethod_ != -1);
647 	midiProgramEventMethod_ = metaObject()->indexOfSlot("midiProgramEvent(uchar,uchar)");
648 	Q_ASSERT(midiProgramEventMethod_ != -1);
649 	MidiInterface::instance().installInputHandler(&midiThreadReceivedEvent, this);
650 
651 	/* Audio stream */
652 	stream_ = std::make_shared<AudioStreamRtAudio>();
653 	stream_->setTickUpdateCallback(+[](void* cbPtr) -> int {
654 		auto bt = reinterpret_cast<BambooTracker*>(cbPtr);
655 		return bt->streamCountUp();
656 	}, bt_.get());
657 	stream_->setGenerateCallback(+[](int16_t* container, size_t nSamples, void* cbPtr) {
658 		auto bt = reinterpret_cast<BambooTracker*>(cbPtr);
659 		bt->getStreamSamples(container, nSamples);
660 	}, bt_.get());
661 	QObject::connect(stream_.get(), &AudioStream::streamInterrupted, this, &MainWindow::onNewTickSignaled);
662 	QString audioApi = utf8ToQString(config.lock()->getSoundAPI());
663 	if (audioApi.isEmpty()) {	// On the first launch
664 		bool streamState = false;
665 		QString streamErr;
666 		for (const QString& audioApi : stream_->getAvailableBackends()) {
667 			config.lock()->setSoundAPI(audioApi.toUtf8().toStdString());
668 			QString audioDevice = stream_->getDefaultOutputDevice(audioApi);
669 			config.lock()->setSoundDevice(audioDevice.toUtf8().toStdString());
670 			streamState = stream_->initialize(
671 							  static_cast<uint32_t>(bt_->getStreamRate()),
672 							  static_cast<uint32_t>(bt_->getStreamDuration()),
673 							  bt_->getModuleTickFrequency(), audioApi, audioDevice, &streamErr);
674 			if (streamState) break;
675 		}
676 		if (streamState) {
677 			uint32_t sr = stream_->getStreamRate();
678 			if (config.lock()->getSampleRate() != sr) {
679 				showStreamRateWarningDialog(sr);
680 				bt_->setStreamRate(sr);
681 			}
682 		}
683 		else {
684 			showStreamFailedDialog(streamErr);
685 		}
686 	}
687 	else {	// Ordinary launch
688 		bool savedApiExists = false;
689 		const std::vector<QString> audioApis = stream_->getAvailableBackends();
690 		for (const QString& api : audioApis) {
691 			if (api.toUtf8().toStdString() == config.lock()->getSoundAPI()) {
692 				savedApiExists = true;
693 				break;
694 			}
695 		}
696 		if (!savedApiExists) {
697 			audioApi = audioApis.front();
698 			config.lock()->setSoundAPI(audioApi.toUtf8().toStdString());
699 		}
700 		QString audioDevice = utf8ToQString(config.lock()->getSoundDevice());
701 		bool savedDeviceExists = false;
702 		for (const QString& device : stream_->getAvailableDevices(audioApi)) {
703 			if (device.toUtf8().toStdString() == config.lock()->getSoundDevice()) {
704 				savedDeviceExists = true;
705 				break;
706 			}
707 		}
708 		if (!savedDeviceExists) {
709 			audioDevice = stream_->getDefaultOutputDevice(audioApi);
710 			config.lock()->setSoundDevice(audioDevice.toUtf8().toStdString());
711 		}
712 		QString streamErr;
713 		bool streamState = stream_->initialize(
714 							   static_cast<uint32_t>(bt_->getStreamRate()),
715 							   static_cast<uint32_t>(bt_->getStreamDuration()),
716 							   bt_->getModuleTickFrequency(), audioApi, audioDevice, &streamErr);
717 		if (streamState) {
718 			uint32_t sr = stream_->getStreamRate();
719 			if (config.lock()->getSampleRate() != sr) {
720 				showStreamRateWarningDialog(sr);
721 				bt_->setStreamRate(sr);
722 			}
723 		}
724 		else {
725 			showStreamFailedDialog(streamErr);
726 		}
727 	}
728 	RealChipInterface intf = config.lock()->getRealChipInterface();
729 	if (intf == RealChipInterface::NONE) {
730 		bt_->useSCCI(nullptr);
731 		bt_->useC86CTL(nullptr);
732 	}
733 	else {
734 		timer_ = std::make_unique<Timer>();
735 		timer_->setInterval(1000000 / bt_->getModuleTickFrequency());
736 		tickEventMethod_ = metaObject()->indexOfSlot("onNewTickSignaledRealChip()");
737 		Q_ASSERT(tickEventMethod_ != -1);
738 		timer_->setFunction([&]{
739 			QMetaMethod method = this->metaObject()->method(this->tickEventMethod_);
740 			method.invoke(this, Qt::QueuedConnection);
741 		});
742 
743 		setRealChipInterface(intf);
744 
745 		timer_->start();
746 	}
747 
748 	/* Load module */
749 	if (filePath.isEmpty()) {
750 		loadModule();
751 		setInitialSelectedInstrument();
752 		assignADPCMSamples();
753 		if (!timer_) stream_->start();
754 	}
755 	else {
756 		openModule(filePath);	// If use emulation, stream stars
757 	}
758 
759 	/* Track visibility */
760 	SongType memSongType;
761 	std::vector<int> visTracks;
762 	if (config.lock()->getRestoreTrackVisibility()
763 			&& TrackVisibilityMemoryHandler::loadTrackVisibilityMemory(memSongType, visTracks)) {
764 		SongType songType = bt_->getSongStyle(bt_->getCurrentSongNumber()).type;
765 		visTracks = adaptVisibleTrackList(visTracks, songType, songType);
766 	}
767 	else {
768 		visTracks.resize(bt_->getSongStyle(0).trackAttribs.size());
769 		std::iota(visTracks.begin(), visTracks.end(), 0);
770 	}
771 	setTrackVisibility(visTracks);
772 }
773 
~MainWindow()774 MainWindow::~MainWindow()
775 {
776 	MidiInterface::instance().uninstallInputHandler(&midiThreadReceivedEvent, this);
777 	stream_->shutdown();
778 }
779 
eventFilter(QObject * watched,QEvent * event)780 bool MainWindow::eventFilter(QObject* watched, QEvent* event)
781 {
782 	if (auto fmForm = qobject_cast<InstrumentEditorFMForm*>(watched)) {
783 		// Change current instrument by activating FM editor
784 		if (event->type() == QEvent::WindowActivate) {
785 			int row = findRowFromInstrumentList(fmForm->getInstrumentNumber());
786 			ui->instrumentList->setCurrentRow(row);
787 		}
788 		else if (event->type() == QEvent::Resize) {
789 			config_.lock()->setInstrumentFMWindowWidth(fmForm->width());
790 			config_.lock()->setInstrumentFMWindowHeight(fmForm->height());
791 		}
792 		return false;
793 	}
794 
795 	if (auto ssgForm = qobject_cast<InstrumentEditorSSGForm*>(watched)) {
796 		// Change current instrument by activating SSG editor
797 		if (event->type() == QEvent::WindowActivate) {
798 			int row = findRowFromInstrumentList(ssgForm->getInstrumentNumber());
799 			ui->instrumentList->setCurrentRow(row);
800 		}
801 		else if (event->type() == QEvent::Resize) {
802 			config_.lock()->setInstrumentSSGWindowWidth(ssgForm->width());
803 			config_.lock()->setInstrumentSSGWindowHeight(ssgForm->height());
804 		}
805 		return false;
806 	}
807 
808 	if (auto adpcmForm = qobject_cast<InstrumentEditorADPCMForm*>(watched)) {
809 		// Change current instrument by activating ADPCM editor
810 		if (event->type() == QEvent::WindowActivate) {
811 			int row = findRowFromInstrumentList(adpcmForm->getInstrumentNumber());
812 			ui->instrumentList->setCurrentRow(row);
813 		}
814 		else if (event->type() == QEvent::Resize) {
815 			config_.lock()->setInstrumentADPCMWindowWidth(adpcmForm->width());
816 			config_.lock()->setInstrumentADPCMWindowHeight(adpcmForm->height());
817 		}
818 		return false;
819 	}
820 
821 	if (auto kitForm = qobject_cast<InstrumentEditorDrumkitForm*>(watched)) {
822 		// Change current instrument by activating drumkit editor
823 		if (event->type() == QEvent::WindowActivate) {
824 			int row = findRowFromInstrumentList(kitForm->getInstrumentNumber());
825 			ui->instrumentList->setCurrentRow(row);
826 		}
827 		else if (event->type() == QEvent::Resize) {
828 			config_.lock()->setInstrumentDrumkitWindowWidth(kitForm->width());
829 			config_.lock()->setInstrumentDrumkitWindowHeight(kitForm->height());
830 		}
831 		return false;
832 	}
833 
834 	if (watched == ui->instrumentList) {
835 		if (event->type() == QEvent::FocusIn) updateMenuByInstrumentList();
836 		return false;
837 	}
838 
839 	if (watched == renamingInstEdit_) {
840 		if (event->type() == QEvent::FocusOut) finishRenamingInstrument();
841 		return false;
842 	}
843 
844 	return false;
845 }
846 
showEvent(QShowEvent * event)847 void MainWindow::showEvent(QShowEvent* event)
848 {
849 	Q_UNUSED(event)
850 
851 	if (!hasShownOnce_)	{
852 		int y = config_.lock()->getMainWindowVerticalSplit();
853 		if (y == -1) {
854 			config_.lock()->setMainWindowVerticalSplit(ui->splitter->sizes().front());
855 		}
856 		else {
857 			ui->splitter->setSizes({ y, ui->splitter->height() - ui->splitter->handleWidth() - y });
858 		}
859 		hasShownOnce_ = true;
860 	}
861 }
862 
keyPressEvent(QKeyEvent * event)863 void MainWindow::keyPressEvent(QKeyEvent *event)
864 {
865 	if (!event->isAutoRepeat()) {
866 		// Musical keyboard
867 		Qt::Key qtKey = static_cast<Qt::Key>(event->key());
868 		try {
869 			bt_->jamKeyOn(getJamKeyFromLayoutMapping(qtKey, config_),
870 						  !config_.lock()->getFixJammingVolume());
871 		} catch (std::invalid_argument&) {}
872 	}
873 }
874 
keyReleaseEvent(QKeyEvent * event)875 void MainWindow::keyReleaseEvent(QKeyEvent *event)
876 {
877 	int key = event->key();
878 
879 	if (!event->isAutoRepeat()) {
880 		// Musical keyboard
881 		Qt::Key qtKey = static_cast<Qt::Key>(key);
882 		try {
883 			bt_->jamKeyOff(getJamKeyFromLayoutMapping(qtKey, config_));
884 		} catch (std::invalid_argument&) {}
885 	}
886 }
887 
dragEnterEvent(QDragEnterEvent * event)888 void MainWindow::dragEnterEvent(QDragEnterEvent* event)
889 {
890 	auto mime = event->mimeData();
891 	if (mime->hasUrls() && mime->urls().length() == 1) {
892 		switch (FileIO::judgeFileTypeFromExtension(
893 					QFileInfo(mime->urls().first().toLocalFile()).suffix().toStdString())) {
894 		case FileIO::FileType::Mod:
895 		case FileIO::FileType::Inst:
896 		case FileIO::FileType::Bank:
897 			event->acceptProposedAction();
898 			break;
899 		default:
900 			break;
901 		}
902 	}
903 }
904 
dropEvent(QDropEvent * event)905 void MainWindow::dropEvent(QDropEvent* event)
906 {
907 	QString file = event->mimeData()->urls().first().toLocalFile();
908 
909 	switch (FileIO::judgeFileTypeFromExtension(QFileInfo(file).suffix().toStdString())) {
910 	case FileIO::FileType::Mod:
911 	{
912 		if (isWindowModified()) {
913 			QString modTitle = utf8ToQString(bt_->getModuleTitle());
914 			if (modTitle.isEmpty()) modTitle = tr("Untitled");
915 			QMessageBox dialog(QMessageBox::Warning,
916 							   "BambooTracker",
917 							   tr("Save changes to %1?").arg(modTitle),
918 							   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
919 			switch (dialog.exec()) {
920 			case QMessageBox::Yes:
921 				if (!on_actionSave_triggered()) return;
922 				break;
923 			case QMessageBox::No:
924 				break;
925 			case QMessageBox::Cancel:
926 				return;
927 			default:
928 				break;
929 			}
930 		}
931 
932 		bt_->stopPlaySong();
933 		lockWidgets(false);
934 
935 		openModule(file);
936 		break;
937 	}
938 	case FileIO::FileType::Inst:
939 	{
940 		funcLoadInstrument(file);
941 		break;
942 	}
943 	case FileIO::FileType::Bank:
944 	{
945 		funcImportInstrumentsFromBank(file);
946 		break;
947 	}
948 	default:
949 		break;
950 	}
951 }
952 
resizeEvent(QResizeEvent * event)953 void MainWindow::resizeEvent(QResizeEvent* event)
954 {
955 	QWidget::resizeEvent(event);
956 
957 	if (!isMaximized()) {	// Check previous size
958 		config_.lock()->setMainWindowWidth(event->oldSize().width());
959 		config_.lock()->setMainWindowHeight(event->oldSize().height());
960 	}
961 }
962 
moveEvent(QMoveEvent * event)963 void MainWindow::moveEvent(QMoveEvent* event)
964 {
965 	QWidget::moveEvent(event);
966 
967 	if (!isMaximized()) {	// Check previous position
968 		config_.lock()->setMainWindowX(event->oldPos().x());
969 		config_.lock()->setMainWindowY(event->oldPos().y());
970 	}
971 }
972 
closeEvent(QCloseEvent * event)973 void MainWindow::closeEvent(QCloseEvent *event)
974 {
975 	if (isWindowModified()) {
976 		QString modTitle = utf8ToQString(bt_->getModuleTitle());
977 		if (modTitle.isEmpty()) modTitle = tr("Untitled");
978 		QMessageBox dialog(QMessageBox::Warning,
979 						   "BambooTracker",
980 						   tr("Save changes to %1?").arg(modTitle),
981 						   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
982 		switch (dialog.exec()) {
983 		case QMessageBox::Yes:
984 			if (!on_actionSave_triggered()) {
985 				event->ignore();
986 				return;
987 			}
988 			break;
989 		case QMessageBox::No:
990 			break;
991 		case QMessageBox::Cancel:
992 			event->ignore();
993 			return;
994 		default:
995 			break;
996 		}
997 	}
998 
999 	if (isMaximized()) {
1000 		config_.lock()->setMainWindowMaximized(true);
1001 	}
1002 	else {
1003 		config_.lock()->setMainWindowMaximized(false);
1004 		config_.lock()->setMainWindowWidth(width());
1005 		config_.lock()->setMainWindowHeight(height());
1006 		config_.lock()->setMainWindowX(x());
1007 		config_.lock()->setMainWindowY(y());
1008 	}
1009 	config_.lock()->setMainWindowVerticalSplit(ui->splitter->sizes().front());
1010 
1011 	auto& mainTbConfig = config_.lock()->getMainToolbarConfiguration();
1012 	auto& subTbConfig = config_.lock()->getSubToolbarConfiguration();
1013 	auto mainTbArea = toolBarArea(ui->mainToolBar);
1014 	auto subTbArea = toolBarArea(ui->subToolBar);
1015 	if (ui->mainToolBar->isFloating()) {
1016 		mainTbConfig.setPosition(ToolbarPos::FLOAT_POS);
1017 		mainTbConfig.setX(ui->mainToolBar->x());
1018 		mainTbConfig.setY(ui->mainToolBar->y());
1019 	}
1020 	else {
1021 		mainTbConfig.setPosition(std::find_if(TB_POS_.begin(), TB_POS_.end(), [mainTbArea](auto pair) {
1022 			return (pair.second == mainTbArea);
1023 		})->first);
1024 		mainTbConfig.setNumber(0);
1025 		mainTbConfig.setBreakBefore(false);
1026 	}
1027 	if (ui->subToolBar->isFloating()) {
1028 		subTbConfig.setPosition(ToolbarPos::FLOAT_POS);
1029 		subTbConfig.setX(ui->subToolBar->x());
1030 		subTbConfig.setY(ui->subToolBar->y());
1031 	}
1032 	else {
1033 		subTbConfig.setPosition(std::find_if(TB_POS_.begin(), TB_POS_.end(), [subTbArea](auto pair) {
1034 			return (pair.second == subTbArea);
1035 		})->first);
1036 		subTbConfig.setNumber(0);
1037 		subTbConfig.setBreakBefore(false);
1038 	}
1039 	if (mainTbArea == subTbArea) {
1040 		auto mainPos = ui->mainToolBar->pos();
1041 		auto subPos = ui->subToolBar->pos();
1042 		switch (mainTbArea) {
1043 		case Qt::TopToolBarArea:
1044 		{
1045 			if (mainPos.x() == subPos.x()) {
1046 				bool cond = (mainPos.y() < subPos.y());
1047 				mainTbConfig.setNumber(cond ? 0 : 1);
1048 				mainTbConfig.setBreakBefore(!cond);
1049 				subTbConfig.setNumber(cond ? 1 : 0);
1050 				subTbConfig.setBreakBefore(cond);
1051 			}
1052 			else {
1053 				bool cond = (mainPos.x() < subPos.x());
1054 				mainTbConfig.setNumber(cond ? 0 : 1);
1055 				mainTbConfig.setBreakBefore(false);
1056 				subTbConfig.setNumber(cond ? 1 : 0);
1057 				subTbConfig.setBreakBefore(false);
1058 			}
1059 			break;
1060 		}
1061 		case Qt::BottomToolBarArea:
1062 		{
1063 			if (mainPos.x() == subPos.x()) {
1064 				bool cond = (subPos.y() < mainPos.y());
1065 				mainTbConfig.setNumber(cond ? 0 : 1);
1066 				mainTbConfig.setBreakBefore(!cond);
1067 				subTbConfig.setNumber(cond ? 1 : 0);
1068 				subTbConfig.setBreakBefore(cond);
1069 			}
1070 			else {
1071 				bool cond = (mainPos.x() < subPos.x());
1072 				mainTbConfig.setNumber(cond ? 0 : 1);
1073 				mainTbConfig.setBreakBefore(false);
1074 				subTbConfig.setNumber(cond ? 1 : 0);
1075 				subTbConfig.setBreakBefore(false);
1076 			}
1077 			break;
1078 		}
1079 		case Qt::LeftToolBarArea:
1080 		{
1081 			if (mainPos.x() == subPos.x()) {
1082 				bool cond = (mainPos.y() < subPos.y());
1083 				mainTbConfig.setNumber(cond ? 0 : 1);
1084 				mainTbConfig.setBreakBefore(false);
1085 				subTbConfig.setNumber(cond ? 1 : 0);
1086 				subTbConfig.setBreakBefore(false);
1087 			}
1088 			else {
1089 				bool cond = (mainPos.x() < subPos.x());
1090 				mainTbConfig.setNumber(cond ? 0 : 1);
1091 				mainTbConfig.setBreakBefore(!cond);
1092 				subTbConfig.setNumber(cond ? 1 : 0);
1093 				subTbConfig.setBreakBefore(cond);
1094 			}
1095 			break;
1096 		}
1097 		case Qt::RightToolBarArea:
1098 		{
1099 			if (mainPos.x() == subPos.x()) {
1100 				bool cond = (mainPos.y() < subPos.y());
1101 				mainTbConfig.setNumber(cond ? 0 : 1);
1102 				mainTbConfig.setBreakBefore(false);
1103 				subTbConfig.setNumber(cond ? 1 : 0);
1104 				subTbConfig.setBreakBefore(false);
1105 			}
1106 			else {
1107 				bool cond = (subPos.x() < mainPos.x());
1108 				mainTbConfig.setNumber(cond ? 0 : 1);
1109 				mainTbConfig.setBreakBefore(!cond);
1110 				subTbConfig.setNumber(cond ? 1 : 0);
1111 				subTbConfig.setBreakBefore(cond);
1112 			}
1113 			break;
1114 		}
1115 		default:
1116 			break;
1117 		}
1118 	}
1119 	config_.lock()->setVisibleToolbar(ui->mainToolBar->isVisible());
1120 	config_.lock()->setVisibleStatusBar(ui->statusBar->isVisible());
1121 	config_.lock()->setFollowMode(bt_->isFollowPlay());
1122 
1123 	instForms_->closeAll();
1124 
1125 	FileHistoryHandler::saveFileHistory(fileHistory_);
1126 	TrackVisibilityMemoryHandler::saveTrackVisibilityMemory(
1127 				bt_->getSongStyle(bt_->getCurrentSongNumber()).type,
1128 				ui->patternEditor->getVisibleTracks());
1129 
1130 	if (effListDiag_) effListDiag_->close();
1131 	if (shortcutsDiag_) shortcutsDiag_->close();
1132 	bmManForm_->close();
1133 	if (commentDiag_) commentDiag_->close();
1134 
1135 	event->accept();
1136 }
1137 
freezeViews()1138 void MainWindow::freezeViews()
1139 {
1140 	ui->orderList->freeze();
1141 	ui->patternEditor->freeze();
1142 }
1143 
unfreezeViews()1144 void MainWindow::unfreezeViews()
1145 {
1146 	ui->orderList->unfreeze();
1147 	ui->patternEditor->unfreeze();
1148 }
1149 
setShortcuts()1150 void MainWindow::setShortcuts()
1151 {
1152 	auto shortcuts = config_.lock()->getShortcuts();
1153 	octUpSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::OctaveUp)));
1154 	octDownSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::OctaveDown)));
1155 	focusPtnSc_.setKey(strToKeySeq(shortcuts.at(Configuration::FocusOnPattern)));
1156 	focusOdrSc_.setKey(strToKeySeq(shortcuts.at(Configuration::FocusOnOrder)));
1157 	focusInstSc_.setKey(strToKeySeq(shortcuts.at(Configuration::FocusOnInstrument)));
1158 	playAndStopSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayAndStop)));
1159 	ui->actionPlay->setShortcut(strToKeySeq(shortcuts.at(Configuration::Play)));
1160 	ui->actionPlay_From_Start->setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayFromStart)));
1161 	ui->actionPlay_Pattern->setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayPattern)));
1162 	ui->actionPlay_From_Cursor->setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayFromCursor)));
1163 	ui->actionPlay_From_Marker->setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayFromMarker)));
1164 	playStepSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::PlayStep)));
1165 	ui->actionStop->setShortcut(strToKeySeq(shortcuts.at(Configuration::Stop)));
1166 	ui->actionEdit_Mode->setShortcut(strToKeySeq(shortcuts.at(Configuration::ToggleEditJam)));
1167 	ui->actionSet_Ro_w_Marker->setShortcut(strToKeySeq(shortcuts.at(Configuration::SetMarker)));
1168 	ui->actionMix->setShortcut(strToKeySeq(shortcuts.at(Configuration::PasteMix)));
1169 	ui->actionOverwrite->setShortcut(strToKeySeq(shortcuts.at(Configuration::PasteOverwrite)));
1170 	ui->action_Insert->setShortcut(strToKeySeq(shortcuts.at(Configuration::PasteInsert)));
1171 	ui->actionAll->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectAll)));
1172 	ui->actionNone->setShortcut(strToKeySeq(shortcuts.at(Configuration::Deselect)));
1173 	ui->actionRow->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectRow)));
1174 	ui->actionColumn->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectColumn)));
1175 	ui->actionPattern->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectColumn)));
1176 	ui->actionOrder->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectOrder)));
1177 	ui->action_Go_To->setShortcut(strToKeySeq(shortcuts.at(Configuration::GoToStep)));
1178 	ui->actionToggle_Track->setShortcut(strToKeySeq(shortcuts.at(Configuration::ToggleTrack)));
1179 	ui->actionSolo_Track->setShortcut(strToKeySeq(shortcuts.at(Configuration::SoloTrack)));
1180 	ui->actionInterpolate->setShortcut(strToKeySeq(shortcuts.at(Configuration::Interpolate)));
1181 	goPrevOdrSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::GoToPrevOrder)));
1182 	goNextOdrSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::GoToNextOrder)));
1183 	ui->action_Toggle_Bookmark->setShortcut(strToKeySeq(shortcuts.at(Configuration::ToggleBookmark)));
1184 	ui->action_Previous_Bookmark->setShortcut(strToKeySeq(shortcuts.at(Configuration::PrevBookmark)));
1185 	ui->action_Next_Bookmark->setShortcut(strToKeySeq(shortcuts.at(Configuration::NextBookmark)));
1186 	ui->actionDecrease_Note->setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreaseNote)));
1187 	ui->actionIncrease_Note->setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreaseNote)));
1188 	ui->actionDecrease_Octave->setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreaseOctave)));
1189 	ui->actionIncrease_Octave->setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreaseOctave)));
1190 	prevInstSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::PrevInstrument)));
1191 	nextInstSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::NextInstrument)));
1192 	ui->action_Instrument_Mask->setShortcut(strToKeySeq(shortcuts.at(Configuration::MaskInstrument)));
1193 	ui->action_Volume_Mask->setShortcut(strToKeySeq(shortcuts.at(Configuration::MaskVolume)));
1194 	ui->actionEdit->setShortcut(strToKeySeq(shortcuts.at(Configuration::EditInstrument)));
1195 	ui->actionFollow_Mode->setShortcut(strToKeySeq(shortcuts.at(Configuration::FollowMode)));
1196 	ui->actionDuplicate_Order->setShortcut(strToKeySeq(shortcuts.at(Configuration::DuplicateOrder)));
1197 	ui->actionClone_Patterns->setShortcut(strToKeySeq(shortcuts.at(Configuration::ClonePatterns)));
1198 	ui->actionClone_Order->setShortcut(strToKeySeq(shortcuts.at(Configuration::CloneOrder)));
1199 	ui->actionReplace_Instrument->setShortcut(strToKeySeq(shortcuts.at(Configuration::ReplaceInstrument)));
1200 	ui->actionExpand->setShortcut(strToKeySeq(shortcuts.at(Configuration::ExpandPattern)));
1201 	ui->actionShrink->setShortcut(strToKeySeq(shortcuts.at(Configuration::ShrinkPattern)));
1202 	ui->actionFine_Decrease_Values->setShortcut(strToKeySeq(shortcuts.at(Configuration::FineDecreaseValues)));
1203 	ui->actionFine_Increase_Values->setShortcut(strToKeySeq(shortcuts.at(Configuration::FineIncreaseValues)));
1204 	ui->actionCoarse_D_ecrease_Values->setShortcut(strToKeySeq(shortcuts.at(Configuration::CoarseDecreaseValues)));
1205 	ui->actionCoarse_I_ncrease_Values->setShortcut(strToKeySeq(shortcuts.at(Configuration::CoarseIncreaseValuse)));
1206 	incPtnSizeSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreasePatternSize)));
1207 	decPtnSizeSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreasePatternSize)));
1208 	incEditStepSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreaseEditStep)));
1209 	decEditStepSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreaseEditStep)));
1210 	ui->action_Effect_List->setShortcut(strToKeySeq(shortcuts.at(Configuration::DisplayEffectList)));
1211 	prevSongSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::PreviousSong)));
1212 	nextSongSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::NextSong)));
1213 	jamVolUpSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::JamVolumeUp)));
1214 	jamVolDownSc_.setShortcut(strToKeySeq(shortcuts.at(Configuration::JamVolumeDown)));
1215 
1216 	ui->orderList->onShortcutUpdated();
1217 	ui->patternEditor->onShortcutUpdated();
1218 }
1219 
setTrackVisibility(const std::vector<int> & visTracks)1220 void MainWindow::setTrackVisibility(const std::vector<int>& visTracks)
1221 {
1222 	ui->orderList->setVisibleTracks(visTracks);
1223 	setOrderListGroupMaximumWidth();
1224 	ui->patternEditor->setVisibleTracks(visTracks);
1225 	if (config_.lock()->getMuteHiddenTracks()) {
1226 		int all = static_cast<int>(bt_->getSongStyle(bt_->getCurrentSongNumber()).trackAttribs.size());
1227 		for (int i = 0; i < all; ++i) {
1228 			if (std::none_of(visTracks.begin(), visTracks.end(), [i](const int& n) { return i == n; }))
1229 				bt_->setTrackMuteState(i, true);
1230 		}
1231 	}
1232 }
1233 
updateInstrumentListColors()1234 void MainWindow::updateInstrumentListColors()
1235 {
1236 	ui->instrumentList->setStyleSheet(
1237 				QString("QListWidget { color: %1; background: %2; }")
1238 				.arg(palette_->ilistTextColor.name(QColor::HexArgb),
1239 					 palette_->ilistBackColor.name(QColor::HexArgb))
1240 				+ QString("QListWidget::item:hover { color: %1; background: %2; }")
1241 				.arg(palette_->ilistTextColor.name(QColor::HexArgb),
1242 					 palette_->ilistHovBackColor.name(QColor::HexArgb))
1243 				+ QString("QListWidget::item:selected { color: %1; background: %2; }")
1244 				.arg(palette_->ilistTextColor.name(QColor::HexArgb),
1245 					 palette_->ilistSelBackColor.name(QColor::HexArgb))
1246 				+ QString("QListWidget::item:selected:hover { color: %1; background: %2; }")
1247 				.arg(palette_->ilistTextColor.name(QColor::HexArgb),
1248 					 palette_->ilistHovSelBackColor.name(QColor::HexArgb)));
1249 }
1250 
setOrderListGroupMaximumWidth()1251 void MainWindow::setOrderListGroupMaximumWidth()
1252 {
1253 	ui->orderListGroupBox->setMaximumWidth(
1254 				ui->orderListGroupBox->contentsMargins().left()
1255 				+ ui->orderListGroupBox->layout()->contentsMargins().left()
1256 				+ ui->orderList->maximumWidth()
1257 				+ ui->orderListGroupBox->layout()->contentsMargins().right()
1258 				+ ui->orderListGroupBox->contentsMargins().right());
1259 }
1260 
1261 /********** MIDI **********/
midiThreadReceivedEvent(double delay,const uint8_t * msg,size_t len,void * userData)1262 void MainWindow::midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData)
1263 {
1264 	MainWindow *self = reinterpret_cast<MainWindow *>(userData);
1265 
1266 	Q_UNUSED(delay)
1267 
1268 	// Note-On/Note-Off
1269 	if (len == 3 && (msg[0] & 0xe0) == 0x80) {
1270 		uint8_t status = msg[0];
1271 		uint8_t key = msg[1];
1272 		uint8_t velocity = msg[2];
1273 		QMetaMethod method = self->metaObject()->method(self->midiKeyEventMethod_);
1274 		method.invoke(self, Qt::QueuedConnection,
1275 					  Q_ARG(uchar, status), Q_ARG(uchar, key), Q_ARG(uchar, velocity));
1276 	}
1277 	// Program change
1278 	else if (len == 2 && (msg[0] & 0xf0) == 0xc0) {
1279 		uint8_t status = msg[0];
1280 		uint8_t program = msg[1];
1281 		QMetaMethod method = self->metaObject()->method(self->midiProgramEventMethod_);
1282 		method.invoke(self, Qt::QueuedConnection,
1283 					  Q_ARG(uchar, status), Q_ARG(uchar, program));
1284 	}
1285 }
1286 
midiKeyEvent(uchar status,uchar key,uchar velocity)1287 void MainWindow::midiKeyEvent(uchar status, uchar key, uchar velocity)
1288 {
1289 	bool release = ((status & 0xf0) == 0x80) || velocity == 0;
1290 	int k = static_cast<int>(key) - 12;
1291 
1292 	octave_->setValue(k / 12);
1293 
1294 	if (importBankDiag_) {
1295 		if (bankJamMidiCtrl_.load()) return;
1296 		importBankDiag_->onJamKeyOffByMidi(k);
1297 		if (!release) importBankDiag_->onJamKeyOnByMidi(k);
1298 		return;
1299 	}
1300 
1301 	int n = instForms_->checkActivatedFormNumber();
1302 	if (n == -1) {
1303 		bt_->jamKeyOff(k); // possibility to recover on stuck note
1304 		if (!release) bt_->jamKeyOn(k, !config_.lock()->getFixJammingVolume());
1305 	}
1306 	else {
1307 		SoundSource src = instForms_->getFormInstrumentSoundSource(n);
1308 		bt_->jamKeyOffForced(k, src); // possibility to recover on stuck note
1309 		if (!release) bt_->jamKeyOnForced(k, src, !config_.lock()->getFixJammingVolume());
1310 	}
1311 }
1312 
midiProgramEvent(uchar status,uchar program)1313 void MainWindow::midiProgramEvent(uchar status, uchar program)
1314 {
1315 	Q_UNUSED(status)
1316 	int row = findRowFromInstrumentList(program);
1317 	ui->instrumentList->setCurrentRow(row);
1318 }
1319 
1320 /********** Instrument list **********/
addInstrument()1321 void MainWindow::addInstrument()
1322 {
1323 	SoundSource src = bt_->getCurrentTrackAttribute().source;
1324 	switch (src) {
1325 	case SoundSource::FM:
1326 	case SoundSource::SSG:
1327 	case SoundSource::ADPCM:
1328 	{
1329 		std::unordered_map<SoundSource, InstrumentType> map = {
1330 			{ SoundSource::FM, InstrumentType::FM },
1331 			{ SoundSource::SSG, InstrumentType::SSG },
1332 			{ SoundSource::ADPCM, InstrumentType::ADPCM },
1333 		};
1334 		auto& list = ui->instrumentList;
1335 
1336 		int num = bt_->findFirstFreeInstrumentNumber();
1337 		if (num == -1) return;	// Maximum count check
1338 
1339 		QString name = tr("Instrument %1").arg(num);
1340 		bt_->addInstrument(num, map.at(src), name.toUtf8().toStdString());
1341 
1342 		comStack_->push(new AddInstrumentQtCommand(
1343 							list, num, name, map.at(src), instForms_, this,
1344 							config_.lock()->getWriteOnlyUsedSamples()));
1345 		ui->instrumentList->setCurrentRow(num);
1346 		break;
1347 	}
1348 	case SoundSource::RHYTHM:
1349 		break;
1350 	}
1351 }
1352 
addDrumkit()1353 void MainWindow::addDrumkit()
1354 {
1355 	auto& list = ui->instrumentList;
1356 
1357 	int num = bt_->findFirstFreeInstrumentNumber();
1358 	if (num == -1) return;	// Maximum count check
1359 
1360 	QString name = tr("Instrument %1").arg(num);
1361 	bt_->addInstrument(num, InstrumentType::Drumkit, name.toUtf8().toStdString());
1362 
1363 	comStack_->push(new AddInstrumentQtCommand(
1364 						list, num, name, InstrumentType::Drumkit, instForms_, this,
1365 						config_.lock()->getWriteOnlyUsedSamples()));
1366 	ui->instrumentList->setCurrentRow(num);
1367 }
1368 
removeInstrument(int row)1369 void MainWindow::removeInstrument(int row)
1370 {
1371 	if (row < 0) return;
1372 
1373 	auto& list = ui->instrumentList;
1374 	int num = list->item(row)->data(Qt::UserRole).toInt();
1375 
1376 	auto inst = bt_->getInstrument(num);
1377 
1378 	bool updateRequest = false;
1379 	if (config_.lock()->getWriteOnlyUsedSamples()){
1380 		if (inst->getSoundSource() == SoundSource::ADPCM) {
1381 			size_t size = bt_->getSampleADPCMUsers(dynamic_cast<InstrumentADPCM*>(
1382 													   inst.get())->getSampleNumber()).size();
1383 			if (size == 1) updateRequest = true;
1384 		}
1385 	}
1386 
1387 	bt_->removeInstrument(num);
1388 	comStack_->push(new RemoveInstrumentQtCommand(list, num, row, utf8ToQString(inst->getName()),
1389 												  inst->getType(), instForms_, this, updateRequest));
1390 }
1391 
openInstrumentEditor()1392 void MainWindow::openInstrumentEditor()
1393 {
1394 	auto item = ui->instrumentList->currentItem();
1395 	int num = item->data(Qt::UserRole).toInt();
1396 
1397 	if (!instForms_->getForm(num)) {	// Create form
1398 		std::shared_ptr<QWidget> form;
1399 		auto inst = bt_->getInstrument(num);
1400 
1401 		switch (inst->getType()) {
1402 		case InstrumentType::FM:
1403 		{
1404 			auto fmForm = std::make_shared<InstrumentEditorFMForm>(num);
1405 			form = fmForm;
1406 			fmForm->setCore(bt_);
1407 			fmForm->setConfiguration(config_.lock());
1408 			fmForm->setColorPalette(palette_);
1409 			fmForm->resize(config_.lock()->getInstrumentFMWindowWidth(),
1410 						   config_.lock()->getInstrumentFMWindowHeight());
1411 
1412 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::envelopeNumberChanged,
1413 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMEnvelopeNumberChanged);
1414 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::envelopeParameterChanged,
1415 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMEnvelopeParameterChanged);
1416 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::lfoNumberChanged,
1417 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMLFONumberChanged);
1418 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::lfoParameterChanged,
1419 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMLFOParameterChanged);
1420 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::operatorSequenceNumberChanged,
1421 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMOperatorSequenceNumberChanged);
1422 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::operatorSequenceParameterChanged,
1423 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMOperatorSequenceParameterChanged);
1424 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::arpeggioNumberChanged,
1425 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMArpeggioNumberChanged);
1426 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::arpeggioParameterChanged,
1427 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMArpeggioParameterChanged);
1428 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::pitchNumberChanged,
1429 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMPitchNumberChanged);
1430 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::pitchParameterChanged,
1431 							 instForms_.get(), &InstrumentFormManager::onInstrumentFMPitchParameterChanged);
1432 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::jamKeyOnEvent,
1433 							 this, [&](JamKey key) {
1434 				bt_->jamKeyOnForced(key, SoundSource::FM, !config_.lock()->getFixJammingVolume());
1435 			}, Qt::DirectConnection);
1436 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::jamKeyOffEvent,
1437 							 this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::FM); },
1438 			Qt::DirectConnection);
1439 			QObject::connect(fmForm.get(), &InstrumentEditorFMForm::modified,
1440 							 this, &MainWindow::setModifiedTrue);
1441 
1442 			fmForm->installEventFilter(this);
1443 
1444 			instForms_->onInstrumentFMEnvelopeNumberChanged();
1445 			instForms_->onInstrumentFMLFONumberChanged();
1446 			instForms_->onInstrumentFMOperatorSequenceNumberChanged();
1447 			instForms_->onInstrumentFMArpeggioNumberChanged();
1448 			instForms_->onInstrumentFMPitchNumberChanged();
1449 			break;
1450 		}
1451 		case InstrumentType::SSG:
1452 		{
1453 			auto ssgForm = std::make_shared<InstrumentEditorSSGForm>(num);
1454 			form = ssgForm;
1455 			ssgForm->setCore(bt_);
1456 			ssgForm->setConfiguration(config_.lock());
1457 			ssgForm->setColorPalette(palette_);
1458 			ssgForm->resize(config_.lock()->getInstrumentSSGWindowWidth(),
1459 							config_.lock()->getInstrumentSSGWindowHeight());
1460 
1461 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::waveformNumberChanged,
1462 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGWaveformNumberChanged);
1463 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::waveformParameterChanged,
1464 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGWaveformParameterChanged);
1465 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::toneNoiseNumberChanged,
1466 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGToneNoiseNumberChanged);
1467 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::toneNoiseParameterChanged,
1468 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGToneNoiseParameterChanged);
1469 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::envelopeNumberChanged,
1470 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGEnvelopeNumberChanged);
1471 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::envelopeParameterChanged,
1472 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGEnvelopeParameterChanged);
1473 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::arpeggioNumberChanged,
1474 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGArpeggioNumberChanged);
1475 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::arpeggioParameterChanged,
1476 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGArpeggioParameterChanged);
1477 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::pitchNumberChanged,
1478 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGPitchNumberChanged);
1479 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::pitchParameterChanged,
1480 							 instForms_.get(), &InstrumentFormManager::onInstrumentSSGPitchParameterChanged);
1481 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::jamKeyOnEvent,
1482 							 this, [&](JamKey key) {
1483 				bt_->jamKeyOnForced(key, SoundSource::SSG, !config_.lock()->getFixJammingVolume());
1484 			}, Qt::DirectConnection);
1485 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::jamKeyOffEvent,
1486 							 this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::SSG); }
1487 			, Qt::DirectConnection);
1488 			QObject::connect(ssgForm.get(), &InstrumentEditorSSGForm::modified,
1489 							 this, &MainWindow::setModifiedTrue);
1490 
1491 			ssgForm->installEventFilter(this);
1492 
1493 			instForms_->onInstrumentSSGWaveformNumberChanged();
1494 			instForms_->onInstrumentSSGToneNoiseNumberChanged();
1495 			instForms_->onInstrumentSSGEnvelopeNumberChanged();
1496 			instForms_->onInstrumentSSGArpeggioNumberChanged();
1497 			instForms_->onInstrumentSSGPitchNumberChanged();
1498 			break;
1499 		}
1500 		case InstrumentType::ADPCM:
1501 		{
1502 			auto adpcmForm = std::make_shared<InstrumentEditorADPCMForm>(num);
1503 			form = adpcmForm;
1504 			adpcmForm->setCore(bt_);
1505 			adpcmForm->setConfiguration(config_.lock());
1506 			adpcmForm->setColorPalette(palette_);
1507 			adpcmForm->resize(config_.lock()->getInstrumentADPCMWindowWidth(),
1508 							  config_.lock()->getInstrumentADPCMWindowHeight());
1509 
1510 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::sampleNumberChanged,
1511 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleNumberChanged);
1512 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::sampleParameterChanged,
1513 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleParameterChanged);
1514 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::sampleAssignRequested,
1515 							 this, &MainWindow::assignADPCMSamples);
1516 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::sampleMemoryChanged,
1517 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleMemoryUpdated);
1518 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::envelopeNumberChanged,
1519 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMEnvelopeNumberChanged);
1520 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::envelopeParameterChanged,
1521 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMEnvelopeParameterChanged);
1522 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::arpeggioNumberChanged,
1523 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMArpeggioNumberChanged);
1524 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::arpeggioParameterChanged,
1525 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMArpeggioParameterChanged);
1526 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::pitchNumberChanged,
1527 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMPitchNumberChanged);
1528 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::pitchParameterChanged,
1529 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMPitchParameterChanged);
1530 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::jamKeyOnEvent,
1531 							 this, [&](JamKey key) {
1532 				bt_->jamKeyOnForced(key, SoundSource::ADPCM, !config_.lock()->getFixJammingVolume());
1533 			}, Qt::DirectConnection);
1534 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::jamKeyOffEvent,
1535 							 this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::ADPCM); },
1536 			Qt::DirectConnection);
1537 			QObject::connect(adpcmForm.get(), &InstrumentEditorADPCMForm::modified,
1538 							 this, &MainWindow::setModifiedTrue);
1539 
1540 			adpcmForm->installEventFilter(this);
1541 
1542 			instForms_->onInstrumentADPCMSampleNumberChanged();
1543 			instForms_->onInstrumentADPCMEnvelopeNumberChanged();
1544 			instForms_->onInstrumentADPCMArpeggioNumberChanged();
1545 			instForms_->onInstrumentADPCMPitchNumberChanged();
1546 			break;
1547 		}
1548 		case InstrumentType::Drumkit:
1549 		{
1550 			auto kitForm = std::make_shared<InstrumentEditorDrumkitForm>(num);
1551 			form = kitForm;
1552 			kitForm->setCore(bt_);
1553 			kitForm->setConfiguration(config_.lock());
1554 			kitForm->setColorPalette(palette_);
1555 			kitForm->resize(config_.lock()->getInstrumentDrumkitWindowWidth(),
1556 							config_.lock()->getInstrumentDrumkitWindowHeight());
1557 
1558 			QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::sampleNumberChanged,
1559 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleNumberChanged);
1560 			QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::sampleParameterChanged,
1561 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleParameterChanged);
1562 			QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::sampleAssignRequested,
1563 							 this, &MainWindow::assignADPCMSamples);
1564 			QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::sampleMemoryChanged,
1565 							 instForms_.get(), &InstrumentFormManager::onInstrumentADPCMSampleMemoryUpdated);
1566 			QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::jamKeyOnEvent,
1567 							 this, [&](JamKey key) {
1568 				bt_->jamKeyOnForced(key, SoundSource::ADPCM, !config_.lock()->getFixJammingVolume());
1569 			}, Qt::DirectConnection);
1570 			QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::jamKeyOffEvent,
1571 							 this, [&](JamKey key) { bt_->jamKeyOffForced(key, SoundSource::ADPCM); },
1572 			Qt::DirectConnection);
1573 			QObject::connect(kitForm.get(), &InstrumentEditorDrumkitForm::modified,
1574 							 this, &MainWindow::setModifiedTrue);
1575 
1576 			kitForm->installEventFilter(this);
1577 
1578 			instForms_->onInstrumentADPCMSampleNumberChanged();
1579 			break;
1580 		}
1581 		default:
1582 			throw std::invalid_argument("Invalid instrument type");
1583 		}
1584 
1585 		form->addActions({ &octUpSc_, &octDownSc_, &jamVolUpSc_, &jamVolDownSc_ });
1586 
1587 		instForms_->add(num, std::move(form), inst->getSoundSource(), inst->getType());
1588 	}
1589 
1590 	instForms_->showForm(num);
1591 }
1592 
findRowFromInstrumentList(int instNum)1593 int MainWindow::findRowFromInstrumentList(int instNum)
1594 {
1595 	auto& list = ui->instrumentList;
1596 	int row = 0;
1597 	for (; row < list->count(); ++row) {
1598 		auto item = list->item(row);
1599 		if (item->data(Qt::UserRole).toInt() == instNum) break;
1600 	}
1601 	return row;
1602 }
1603 
renameInstrument()1604 void MainWindow::renameInstrument()
1605 {
1606 	auto list = ui->instrumentList;
1607 	auto item = list->currentItem();
1608 	// Finish current edit
1609 	if (item == renamingInstItem_) {
1610 		finishRenamingInstrument();
1611 		return;
1612 	}
1613 	else if (renamingInstItem_) {
1614 		finishRenamingInstrument();
1615 	}
1616 	renamingInstItem_ = item;
1617 
1618 	int num = item->data(Qt::UserRole).toInt();
1619 	renamingInstEdit_ = new QLineEdit(utf8ToQString(bt_->getInstrument(num)->getName()));
1620 	QObject::connect(renamingInstEdit_, &QLineEdit::editingFinished, this, &MainWindow::finishRenamingInstrument);
1621 	renamingInstEdit_->installEventFilter(this);
1622 	ui->instrumentList->setItemWidget(item, renamingInstEdit_);
1623 	renamingInstEdit_->selectAll();
1624 	renamingInstEdit_->setFocus();
1625 }
1626 
finishRenamingInstrument()1627 void MainWindow::finishRenamingInstrument()
1628 {
1629 	if (!renamingInstItem_ || !renamingInstEdit_) return;
1630 	bool hasFocus = renamingInstEdit_->hasFocus();
1631 	auto list = ui->instrumentList;
1632 	int num = renamingInstItem_->data(Qt::UserRole).toInt();
1633 	int row = findRowFromInstrumentList(num);
1634 	auto oldName = utf8ToQString(bt_->getInstrument(num)->getName());
1635 	QString newName = renamingInstEdit_->text();
1636 	list->removeItemWidget(renamingInstItem_);
1637 	if (newName != oldName) {
1638 		bt_->setInstrumentName(num, newName.toUtf8().toStdString());
1639 		comStack_->push(new ChangeInstrumentNameQtCommand(list, num, row, instForms_, oldName, newName));
1640 	}
1641 	renamingInstItem_ = nullptr;
1642 	renamingInstEdit_ = nullptr;
1643 	if (hasFocus) ui->instrumentList->setFocus();
1644 }
1645 
cloneInstrument()1646 void MainWindow::cloneInstrument()
1647 {
1648 	int num = bt_->findFirstFreeInstrumentNumber();
1649 	if (num == -1) return;
1650 
1651 	int refNum = ui->instrumentList->currentItem()->data(Qt::UserRole).toInt();
1652 	// KEEP CODE ORDER //
1653 	bt_->cloneInstrument(num, refNum);
1654 	auto inst = bt_->getInstrument(num);
1655 	comStack_->push(new CloneInstrumentQtCommand(ui->instrumentList, num, inst->getType(),
1656 												 utf8ToQString(inst->getName()), instForms_));
1657 	//----------//
1658 }
1659 
deepCloneInstrument()1660 void MainWindow::deepCloneInstrument()
1661 {
1662 	int num = bt_->findFirstFreeInstrumentNumber();
1663 	if (num == -1) return;
1664 
1665 	int refNum = ui->instrumentList->currentItem()->data(Qt::UserRole).toInt();
1666 	// KEEP CODE ORDER //
1667 	bt_->deepCloneInstrument(num, refNum);
1668 	auto inst = bt_->getInstrument(num);
1669 	comStack_->push(new DeepCloneInstrumentQtCommand(
1670 						ui->instrumentList, num, inst->getType(), utf8ToQString(inst->getName()),
1671 						instForms_, this, config_.lock()->getWriteOnlyUsedSamples()));
1672 	//----------//
1673 }
1674 
loadInstrument()1675 void MainWindow::loadInstrument()
1676 {
1677 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
1678 	QStringList filters {
1679 		tr("BambooTracker instrument (*.bti)"),
1680 				tr("DefleMask preset (*.dmp)"),
1681 				tr("TFM Music Maker instrument (*.tfi)"),
1682 				tr("VGM Music Maker instrument (*.vgi)"),
1683 				tr("WOPN instrument (*.opni)"),
1684 				tr("Gens KMod dump (*.y12)"),
1685 				tr("MVSTracker instrument (*.ins)")
1686 	};
1687 	QString defaultFilter = filters.at(config_.lock()->getInstrumentOpenFormat());
1688 
1689 	QString file = QFileDialog::getOpenFileName(this, tr("Open instrument"), (dir.isEmpty() ? "./" : dir),
1690 												filters.join(";;"), &defaultFilter);
1691 	if (file.isNull()) return;
1692 
1693 	int index = getSelectedFileFilter(file, filters);
1694 	if (index != -1) config_.lock()->setInstrumentOpenFormat(index);
1695 
1696 
1697 	funcLoadInstrument(file);
1698 }
1699 
funcLoadInstrument(QString file)1700 void MainWindow::funcLoadInstrument(QString file)
1701 {
1702 	int n = bt_->findFirstFreeInstrumentNumber();
1703 	if (n == -1) {
1704 		FileIOErrorMessageBox(file, true, FileIO::FileType::Inst,
1705 							  tr("The number of instruments has reached the upper limit."), this).exec();
1706 		return;
1707 	}
1708 
1709 	try {
1710 		QFile fp(file);
1711 		if (!fp.open(QIODevice::ReadOnly)) {
1712 			FileIOErrorMessageBox::openError(file, true, FileIO::FileType::Inst, this);
1713 			return;
1714 		}
1715 		QByteArray array = fp.readAll();
1716 		fp.close();
1717 
1718 		BinaryContainer contaner;
1719 		contaner.appendVector(std::vector<char>(array.begin(), array.end()));
1720 		bt_->loadInstrument(contaner, file.toStdString(), n);
1721 
1722 		auto inst = bt_->getInstrument(n);
1723 		comStack_->push(new AddInstrumentQtCommand(
1724 							ui->instrumentList, n, utf8ToQString(inst->getName()), inst->getType(),
1725 							instForms_, this, config_.lock()->getWriteOnlyUsedSamples()));
1726 		ui->instrumentList->setCurrentRow(n);
1727 		config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
1728 	}
1729 	catch (FileIOError& e) {
1730 		FileIOErrorMessageBox(file, true, e, this).exec();
1731 	}
1732 	catch (std::exception& e) {
1733 		FileIOErrorMessageBox(file, true, FileIO::FileType::Inst, QString(e.what()), this).exec();
1734 	}
1735 }
1736 
saveInstrument()1737 void MainWindow::saveInstrument()
1738 {
1739 	int n = ui->instrumentList->currentItem()->data(Qt::UserRole).toInt();
1740 	QString name = utf8ToQString(bt_->getInstrument(n)->getName());
1741 
1742 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
1743 	QString file = QFileDialog::getSaveFileName(
1744 					   this, tr("Save instrument"),
1745 					   QString("%1/%2.bti").arg(dir.isEmpty() ? "." : dir, name),
1746 					   tr("BambooTracker instrument (*.bti)"));
1747 	if (file.isNull()) return;
1748 	if (!file.endsWith(".bti")) file += ".bti";	// For linux
1749 
1750 	try {
1751 		BinaryContainer container;
1752 		bt_->saveInstrument(container, n);
1753 
1754 		QFile fp(file);
1755 		if (!fp.open(QIODevice::WriteOnly)) {
1756 			FileIOErrorMessageBox::openError(file, false, FileIO::FileType::Inst, this);
1757 			return;
1758 		}
1759 		fp.write(container.getPointer(), container.size());
1760 		fp.close();
1761 
1762 		config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
1763 	}
1764 	catch (FileIOError& e) {
1765 		FileIOErrorMessageBox(file, false, e, this).exec();
1766 	}
1767 	catch (std::exception& e) {
1768 		FileIOErrorMessageBox(file, false, FileIO::FileType::Inst, QString(e.what()), this).exec();
1769 	}
1770 }
1771 
importInstrumentsFromBank()1772 void MainWindow::importInstrumentsFromBank()
1773 {
1774 	stopPlaySong();
1775 
1776 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
1777 	QStringList filters {
1778 		tr("BambooTracker bank (*.btb)"),
1779 				tr("WOPN bank (*.wopn)"),
1780 				tr("PMD FF (*.ff)"),
1781 				tr("PMD PPC (*.ppc)"),
1782 				tr("FMP PVI (*.pvi)"),
1783 				tr("MUCOM88 voice (*.dat)")
1784 	};
1785 	QString defaultFilter = filters.at(config_.lock()->getBankOpenFormat());
1786 
1787 	QString file = QFileDialog::getOpenFileName(this, tr("Open bank"), (dir.isEmpty() ? "./" : dir),
1788 												filters.join(";;"), &defaultFilter);
1789 	if (file.isNull()) {
1790 		return;
1791 	}
1792 	else {
1793 		int index = getSelectedFileFilter(file, filters);
1794 		if (index != -1) config_.lock()->setBankOpenFormat(index);
1795 	}
1796 
1797 	funcImportInstrumentsFromBank(file);
1798 }
1799 
funcImportInstrumentsFromBank(QString file)1800 void MainWindow::funcImportInstrumentsFromBank(QString file)
1801 {
1802 	stopPlaySong();
1803 
1804 	std::unique_ptr<AbstractBank> bank;
1805 	std::shared_ptr<InstrumentsManager> bankMan = std::make_shared<InstrumentsManager>(true);
1806 	try {
1807 		QFile fp(file);
1808 		if (!fp.open(QIODevice::ReadOnly)) {
1809 			FileIOErrorMessageBox::openError(file, true, FileIO::FileType::Bank, this);
1810 			return;
1811 		}
1812 		QByteArray array = fp.readAll();
1813 		fp.close();
1814 
1815 		BinaryContainer container;
1816 		container.appendVector(std::vector<char>(array.begin(), array.end()));
1817 
1818 		bank.reset(BankIO::loadBank(container, file.toStdString()));
1819 		config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
1820 	}
1821 	catch (FileIOError& e) {
1822 		FileIOErrorMessageBox(file, true, e, this).exec();
1823 		return;
1824 	}
1825 	catch (std::exception& e) {
1826 		FileIOErrorMessageBox(file, true, FileIO::FileType::Bank, QString(e.what()), this).exec();
1827 		return;
1828 	}
1829 
1830 	// Change text codec
1831 	if (auto ff = dynamic_cast<FfBank*>(bank.get())) {
1832 		QTextCodec* codec = QTextCodec::codecForName("Shift-JIS");
1833 		for (size_t i = 0; i < ff->getNumInstruments(); ++i) {
1834 			std::string sjis = ff->getInstrumentName(i);
1835 			std::string utf8 = codec->toUnicode(sjis.c_str(), sjis.length()).toStdString();
1836 			ff->setInstrumentName(i, utf8);
1837 		}
1838 	}
1839 	else if (auto mu88 = dynamic_cast<Mucom88Bank*>(bank.get())) {
1840 		QTextCodec* codec = QTextCodec::codecForName("Shift-JIS");
1841 		for (size_t i = 0; i < mu88->getNumInstruments(); ++i) {
1842 			std::string sjis = mu88->getInstrumentName(i);
1843 			std::string utf8 = codec->toUnicode(sjis.c_str(), sjis.length()).toStdString();
1844 			mu88->setInstrumentName(i, utf8);
1845 		}
1846 	}
1847 
1848 	size_t jamId = 128;	// Dummy
1849 	std::shared_ptr<AbstractInstrument> jamInst;
1850 	importBankDiag_ = std::make_unique<InstrumentSelectionDialog>(*bank, tr("Select instruments to load:"), config_, this);
1851 	auto updateInst = [&] (size_t id) {
1852 		if (id != jamId) {
1853 			bankJamMidiCtrl_.store(true);
1854 			jamId = id;
1855 			bankMan->clearAll();
1856 			jamInst.reset(bank->loadInstrument(id, bankMan, 0));
1857 			jamInst->setNumber(128);	// Special number
1858 			std::vector<std::vector<size_t>> addrs = bt_->assignADPCMBeforeForcedJamKeyOn(jamInst);
1859 			for (size_t i = 0; i < addrs.size(); ++i) {
1860 				bankMan->setSampleADPCMStartAddress(i, addrs[i][0]);
1861 				bankMan->setSampleADPCMStopAddress(i, addrs[i][1]);
1862 			}
1863 			bankJamMidiCtrl_.store(false);
1864 		}
1865 	};
1866 	QObject::connect(importBankDiag_.get(), &InstrumentSelectionDialog::jamKeyOnEvent,
1867 					 this, [&](size_t id, JamKey key) {
1868 		updateInst(id);
1869 		bt_->jamKeyOnForced(key, jamInst->getSoundSource(),
1870 							!config_.lock()->getFixJammingVolume(), jamInst);
1871 	},
1872 	Qt::DirectConnection);
1873 	QObject::connect(importBankDiag_.get(), &InstrumentSelectionDialog::jamKeyOnMidiEvent,
1874 					 this, [&](size_t id, int key) {
1875 		updateInst(id);
1876 		bt_->jamKeyOnForced(key, jamInst->getSoundSource(),
1877 							!config_.lock()->getFixJammingVolume(), jamInst);
1878 	},
1879 	Qt::DirectConnection);
1880 	QObject::connect(importBankDiag_.get(), &InstrumentSelectionDialog::jamKeyOffEvent,
1881 					 this, [&](JamKey key) { bt_->jamKeyOffForced(key, jamInst->getSoundSource()); },
1882 	Qt::DirectConnection);
1883 	QObject::connect(importBankDiag_.get(), &InstrumentSelectionDialog::jamKeyOffMidiEvent,
1884 					 this, [&](int key) { if (jamInst) bt_->jamKeyOffForced(key, jamInst->getSoundSource()); },
1885 	Qt::DirectConnection);
1886 	importBankDiag_->addActions({ &octUpSc_, &octDownSc_ });
1887 
1888 	if (importBankDiag_->exec() != QDialog::Accepted) {
1889 		assignADPCMSamples();	// Restore
1890 		importBankDiag_.reset();
1891 		return;
1892 	}
1893 
1894 	QVector<size_t> selection = importBankDiag_->currentInstrumentSelection();
1895 	importBankDiag_.reset();
1896 	if (selection.empty()) return;
1897 
1898 	try {
1899 		bool sampleRestoreRequested = false;
1900 		int lastNum = ui->instrumentList->currentRow();
1901 		for (const size_t& index : selection) {
1902 			int n = bt_->findFirstFreeInstrumentNumber();
1903 			if (n == -1){
1904 				FileIOErrorMessageBox(file, true, FileIO::FileType::Inst,
1905 									  tr("The number of instruments has reached the upper limit."), this).exec();
1906 				ui->instrumentList->setCurrentRow(lastNum);
1907 				return;
1908 			}
1909 
1910 			bt_->importInstrument(*bank, index, n);
1911 
1912 			auto inst = bt_->getInstrument(n);
1913 			comStack_->push(new AddInstrumentQtCommand(
1914 								ui->instrumentList, n, utf8ToQString(inst->getName()),
1915 								inst->getType(), instForms_, this, config_.lock()->getWriteOnlyUsedSamples(), true));
1916 			lastNum = n;
1917 
1918 			sampleRestoreRequested |= (inst->getSoundSource() == SoundSource::ADPCM);
1919 		}
1920 		ui->instrumentList->setCurrentRow(lastNum);
1921 
1922 		if (sampleRestoreRequested) assignADPCMSamples();	// Store only once
1923 	}
1924 	catch (FileIOError& e) {
1925 		FileIOErrorMessageBox(file, true, e, this).exec();
1926 	}
1927 	catch (std::exception& e) {
1928 		FileIOErrorMessageBox(file, true, FileIO::FileType::Bank, QString(e.what()), this).exec();
1929 	}
1930 }
1931 
exportInstrumentsToBank()1932 void MainWindow::exportInstrumentsToBank()
1933 {
1934 	std::vector<int> ids = bt_->getInstrumentIndices();
1935 	std::shared_ptr<BtBank> bank(std::make_shared<BtBank>(ids, bt_->getInstrumentNames()));
1936 
1937 	InstrumentSelectionDialog dlg(*bank, tr("Select instruments to save:"), config_, this);
1938 
1939 	if (dlg.exec() != QDialog::Accepted)
1940 		return;
1941 
1942 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
1943 	QString file = QFileDialog::getSaveFileName(this, tr("Save bank"), (dir.isEmpty() ? "./" : dir),
1944 												tr("BambooTracker bank (*.btb)"));
1945 	if (file.isNull()) return;
1946 
1947 	QVector<size_t> selection = dlg.currentInstrumentSelection();
1948 	if (selection.empty()) return;
1949 	std::vector<int> sel;
1950 	std::transform(selection.begin(), selection.end(), std::back_inserter(sel),
1951 				   [&ids](size_t i) { return ids.at(i); });
1952 	std::sort(sel.begin(), sel.end());
1953 
1954 	try {
1955 		BinaryContainer container;
1956 		bt_->exportInstruments(container, sel);
1957 
1958 		QFile fp(file);
1959 		if (!fp.open(QIODevice::WriteOnly)) {
1960 			FileIOErrorMessageBox::openError(file, false, FileIO::FileType::Bank, this);
1961 			return;
1962 		}
1963 		fp.write(container.getPointer(), container.size());
1964 		fp.close();
1965 
1966 		config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
1967 	}
1968 	catch (FileIOError& e) {
1969 		FileIOErrorMessageBox(file, false, e, this).exec();
1970 	}
1971 	catch (std::exception& e) {
1972 		FileIOErrorMessageBox(file, false, FileIO::FileType::Bank, QString(e.what()), this).exec();
1973 	}
1974 }
1975 
swapInstruments(int row1,int row2)1976 void MainWindow::swapInstruments(int row1, int row2)
1977 {
1978 	if (row1 == row2) return;
1979 
1980 	// KEEP CODE ORDER //
1981 	int num1 = ui->instrumentList->item(row1)->data(Qt::UserRole).toInt();
1982 	int num2 = ui->instrumentList->item(row2)->data(Qt::UserRole).toInt();
1983 	QString name1 = utf8ToQString(bt_->getInstrument(num1)->getName());
1984 	QString name2 = utf8ToQString(bt_->getInstrument(num2)->getName());
1985 
1986 	bt_->swapInstruments(num1, num2, config_.lock()->getReflectInstrumentNumberChange());
1987 	comStack_->push(new SwapInstrumentsQtCommand(
1988 						ui->instrumentList, row1, row2, name1, name2, instForms_, ui->patternEditor));
1989 	//----------//
1990 }
1991 
1992 /********** Undo-Redo **********/
undo()1993 void MainWindow::undo()
1994 {
1995 	bt_->undo();
1996 	comStack_->undo();
1997 }
1998 
redo()1999 void MainWindow::redo()
2000 {
2001 	bt_->redo();
2002 	comStack_->redo();
2003 }
2004 
2005 /********** Load data **********/
loadModule()2006 void MainWindow::loadModule()
2007 {
2008 	instForms_->clearAll();
2009 	ui->instrumentList->clear();
2010 	on_instrumentList_itemSelectionChanged();
2011 
2012 	ui->modTitleLineEdit->setText(utf8ToQString(bt_->getModuleTitle()));
2013 	ui->modTitleLineEdit->setCursorPosition(0);
2014 	ui->authorLineEdit->setText(utf8ToQString(bt_->getModuleAuthor()));
2015 	ui->authorLineEdit->setCursorPosition(0);
2016 	ui->copyrightLineEdit->setText(utf8ToQString(bt_->getModuleCopyright()));
2017 	ui->copyrightLineEdit->setCursorPosition(0);
2018 	{
2019 		QSignalBlocker blocker(ui->songComboBox);	// Prevent duplicated call "loadSong"
2020 		ui->songComboBox->clear();
2021 		for (size_t i = 0; i < bt_->getSongCount(); ++i) {
2022 			QString title = utf8ToQString(bt_->getSongTitle(static_cast<int>(i)));
2023 			if (title.isEmpty()) title = tr("Untitled");
2024 			ui->songComboBox->addItem(QString("#%1 %2").arg(i).arg(title));
2025 		}
2026 	}
2027 	highlight1_->setValue(static_cast<int>(bt_->getModuleStepHighlight1Distance()));
2028 	highlight2_->setValue(static_cast<int>(bt_->getModuleStepHighlight2Distance()));
2029 
2030 	for (auto& idx : bt_->getInstrumentIndices()) {
2031 		auto inst = bt_->getInstrument(idx);
2032 		comStack_->push(new AddInstrumentQtCommand(
2033 							ui->instrumentList, idx, utf8ToQString(inst->getName()), inst->getType(),
2034 							instForms_, this, config_.lock()->getWriteOnlyUsedSamples(), true));
2035 	}
2036 
2037 	isSavedModBefore_ = false;
2038 
2039 	loadSong();
2040 
2041 	// Set tick frequency
2042 	stream_->setInterruption(bt_->getModuleTickFrequency());
2043 	if (timer_) timer_->setInterval(1000000 / bt_->getModuleTickFrequency());
2044 	statusIntr_->setText(QString::number(bt_->getModuleTickFrequency()) + QString("Hz"));
2045 
2046 	// Set mixer
2047 	QString text;
2048 	switch (bt_->getModuleMixerType()) {
2049 	case MixerType::UNSPECIFIED:
2050 		bt_->setMasterVolumeFM(config_.lock()->getMixerVolumeFM());
2051 		bt_->setMasterVolumeSSG(config_.lock()->getMixerVolumeSSG());
2052 		text = tr("-");
2053 		break;
2054 	case MixerType::CUSTOM:
2055 		bt_->setMasterVolumeFM(bt_->getModuleCustomMixerFMLevel());
2056 		bt_->setMasterVolumeSSG(bt_->getModuleCustomMixerSSGLevel());
2057 		text = tr("Custom");
2058 		break;
2059 	case MixerType::PC_9821_PC_9801_86:
2060 		bt_->setMasterVolumeFM(0);
2061 		bt_->setMasterVolumeSSG(-5.5);
2062 		text = tr("PC-9821 with PC-9801-86");
2063 		break;
2064 	case MixerType::PC_9821_SPEAK_BOARD:
2065 		bt_->setMasterVolumeFM(0);
2066 		bt_->setMasterVolumeSSG(-3.0);
2067 		text = tr("PC-9821 with Speak Board");
2068 		break;
2069 	case MixerType::PC_8801_VA2:
2070 		bt_->setMasterVolumeFM(0);
2071 		bt_->setMasterVolumeSSG(1.5);
2072 		text = tr("PC-88VA2");
2073 		break;
2074 	case MixerType::PC_8801_MKII_SR:
2075 		bt_->setMasterVolumeFM(0);
2076 		bt_->setMasterVolumeSSG(2.5);
2077 		text = tr("NEC PC-8801mkIISR");
2078 		break;
2079 	}
2080 	statusMixer_->setText(text);
2081 
2082 	// Set comment
2083 	if (commentDiag_) commentDiag_->setComment(utf8ToQString(bt_->getModuleComment()));
2084 
2085 	// Clear records
2086 	QApplication::clipboard()->clear();
2087 	comStack_->clear();
2088 	bt_->clearCommandHistory();
2089 }
2090 
openModule(QString file)2091 void MainWindow::openModule(QString file)
2092 {
2093 	try {
2094 		freezeViews();
2095 		if (timer_) timer_->stop();
2096 		else stream_->stop();
2097 
2098 		BinaryContainer container;
2099 		QFile fp(file);
2100 		if (!fp.open(QIODevice::ReadOnly)) {
2101 			FileIOErrorMessageBox::openError(file, true, FileIO::FileType::Mod, this);
2102 			return;
2103 		}
2104 		QByteArray array = fp.readAll();
2105 		fp.close();
2106 
2107 		container.appendVector(std::vector<char>(array.begin(), array.end()));
2108 		bt_->loadModule(container);
2109 		bt_->setModulePath(file.toStdString());
2110 
2111 		loadModule();
2112 
2113 		config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
2114 		changeFileHistory(file);
2115 	}
2116 	catch (std::exception& e) {
2117 		if (auto ef = dynamic_cast<FileIOError*>(&e)) {
2118 			FileIOErrorMessageBox(file, true, *ef, this).exec();
2119 		}
2120 		else {
2121 			FileIOErrorMessageBox(file, true, FileIO::FileType::Mod, QString(e.what()), this).exec();
2122 		}
2123 		// Init module
2124 		freezeViews();
2125 		bt_->makeNewModule();
2126 		loadModule();
2127 	}
2128 
2129 	isModifiedForNotCommand_ = false;
2130 	setWindowModified(false);
2131 	if (timer_) timer_->start();
2132 	else stream_->start();
2133 	setInitialSelectedInstrument();
2134 	assignADPCMSamples();
2135 }
2136 
loadSong()2137 void MainWindow::loadSong()
2138 {
2139 	// Init position
2140 	int songCnt = static_cast<int>(bt_->getSongCount());
2141 	if (ui->songComboBox->currentIndex() >= songCnt)
2142 		bt_->setCurrentSongNumber(songCnt - 1);
2143 	else
2144 		bt_->setCurrentSongNumber(bt_->getCurrentSongNumber());
2145 	bt_->setCurrentOrderNumber(0);
2146 	bt_->setCurrentTrack(0);
2147 	bt_->setCurrentStepNumber(0);
2148 
2149 	// Init ui
2150 	ui->orderList->onSongLoaded();
2151 	setOrderListGroupMaximumWidth();
2152 	ui->patternEditor->onSongLoaded();
2153 	unfreezeViews();
2154 
2155 	int curSong = bt_->getCurrentSongNumber();
2156 	ui->songComboBox->setCurrentIndex(curSong);
2157 	ui->tempoSpinBox->setValue(bt_->getSongTempo(curSong));
2158 	ui->speedSpinBox->setValue(bt_->getSongSpeed(curSong));
2159 	ui->patternSizeSpinBox->setValue(static_cast<int>(bt_->getDefaultPatternSize(curSong)));
2160 	ui->grooveSpinBox->setValue(bt_->getSongGroove(curSong));
2161 	ui->grooveSpinBox->setMaximum(static_cast<int>(bt_->getGrooveCount()) - 1);
2162 	if (bt_->isUsedTempoInSong(curSong)) {
2163 		ui->tempoSpinBox->setEnabled(true);
2164 		ui->speedSpinBox->setEnabled(true);
2165 		ui->grooveCheckBox->setChecked(false);
2166 		ui->grooveSpinBox->setEnabled(false);
2167 	}
2168 	else {
2169 		ui->tempoSpinBox->setEnabled(false);
2170 		ui->speedSpinBox->setEnabled(false);
2171 		ui->grooveCheckBox->setChecked(true);
2172 		ui->grooveSpinBox->setEnabled(true);
2173 	}
2174 	onCurrentTrackChanged();
2175 
2176 	setWindowTitle();
2177 	switch (bt_->getSongStyle(bt_->getCurrentSongNumber()).type) {
2178 	case SongType::Standard:		statusStyle_->setText(tr("Standard"));			break;
2179 	case SongType::FM3chExpanded:	statusStyle_->setText(tr("FM3ch expanded"));	break;
2180 	}
2181 	statusPlayPos_->setText(config_.lock()->getShowRowNumberInHex() ? "00/00" : "000/000");
2182 
2183 	bmManForm_->onCurrentSongNumberChanged();
2184 }
2185 
assignADPCMSamples()2186 void MainWindow::assignADPCMSamples()
2187 {
2188 	bt_->stopPlaySong();
2189 	lockWidgets(false);
2190 	if (timer_) timer_->stop();
2191 	else stream_->stop();
2192 	bt_->assignSampleADPCMRawSamples();	// Mutex register
2193 	instForms_->onInstrumentADPCMSampleMemoryUpdated();
2194 	if (timer_) timer_->start();
2195 	else stream_->start();
2196 }
2197 
2198 /********** Play song **********/
startPlaySong()2199 void MainWindow::startPlaySong()
2200 {
2201 	bt_->startPlaySong();
2202 	lockWidgets(true);
2203 	firstViewUpdateRequest_ = true;
2204 }
2205 
startPlayFromStart()2206 void MainWindow::startPlayFromStart()
2207 {
2208 	bt_->startPlayFromStart();
2209 	lockWidgets(true);
2210 	firstViewUpdateRequest_ = true;
2211 }
2212 
startPlayPattern()2213 void MainWindow::startPlayPattern()
2214 {
2215 	bt_->startPlayPattern();
2216 	lockWidgets(true);
2217 	firstViewUpdateRequest_ = true;
2218 }
2219 
startPlayFromCurrentStep()2220 void MainWindow::startPlayFromCurrentStep()
2221 {
2222 	bt_->startPlayFromCurrentStep();
2223 	lockWidgets(true);
2224 	firstViewUpdateRequest_ = true;
2225 }
2226 
startPlayFromMarker()2227 void MainWindow::startPlayFromMarker()
2228 {
2229 	if (bt_->startPlayFromMarker()) {
2230 		lockWidgets(true);
2231 		firstViewUpdateRequest_ = true;
2232 	}
2233 }
2234 
playStep()2235 void MainWindow::playStep()
2236 {
2237 	if (!bt_->isPlaySong()) {
2238 		bt_->playStep();
2239 		firstViewUpdateRequest_ = true;
2240 		ui->patternEditor->onPlayStepPressed();
2241 	}
2242 }
2243 
stopPlaySong()2244 void MainWindow::stopPlaySong()
2245 {
2246 	bt_->stopPlaySong();
2247 	lockWidgets(false);
2248 	ui->patternEditor->onStoppedPlaySong();
2249 	ui->orderList->onStoppedPlaySong();
2250 }
2251 
lockWidgets(bool isLock)2252 void MainWindow::lockWidgets(bool isLock)
2253 {
2254 	hasLockedWigets_ = isLock;
2255 	ui->songComboBox->setEnabled(!isLock);
2256 }
2257 
2258 /********** Octave change **********/
changeOctave(bool upFlag)2259 void MainWindow::changeOctave(bool upFlag)
2260 {
2261 	if (upFlag) octave_->stepUp();
2262 	else octave_->stepDown();
2263 
2264 	statusOctave_->setText(tr("Octave: %1").arg(bt_->getCurrentOctave()));
2265 }
2266 
2267 /********** Configuration change **********/
changeConfiguration()2268 void MainWindow::changeConfiguration()
2269 {
2270 	// Real chip interface
2271 	bool streamState = false;
2272 	RealChipInterface intf = config_.lock()->getRealChipInterface();
2273 	if (intf == RealChipInterface::NONE) {
2274 		timer_.reset();
2275 		bt_->useSCCI(nullptr);
2276 		bt_->useC86CTL(nullptr);
2277 		QString streamErr;
2278 		streamState = stream_->initialize(
2279 							   config_.lock()->getSampleRate(),
2280 							   config_.lock()->getBufferLength(),
2281 							   bt_->getModuleTickFrequency(),
2282 							   utf8ToQString(config_.lock()->getSoundAPI()),
2283 							   utf8ToQString(config_.lock()->getSoundDevice()),
2284 							   &streamErr);
2285 		if (!streamState) showStreamFailedDialog(streamErr);
2286 		stream_->start();
2287 	}
2288 	else {
2289 		stream_->stop();
2290 		if (timer_) {
2291 			timer_->stop();
2292 		}
2293 		else {
2294 			timer_ = std::make_unique<Timer>();
2295 			timer_->setInterval(1000000 / bt_->getModuleTickFrequency());
2296 			tickEventMethod_ = metaObject()->indexOfSlot("onNewTickSignaledRealChip()");
2297 			Q_ASSERT(tickEventMethod_ != -1);
2298 			timer_->setFunction([&]{
2299 				QMetaMethod method = this->metaObject()->method(this->tickEventMethod_);
2300 				method.invoke(this, Qt::QueuedConnection);
2301 			});
2302 		}
2303 
2304 		setRealChipInterface(intf);
2305 
2306 		timer_->start();
2307 	}
2308 
2309 	setMidiConfiguration();
2310 	updateFonts();
2311 	ui->orderList->setHorizontalScrollMode(config_.lock()->getMoveCursorByHorizontalScroll());
2312 	ui->patternEditor->setHorizontalScrollMode(config_.lock()->getMoveCursorByHorizontalScroll());
2313 	instForms_->updateByConfiguration();
2314 
2315 	bt_->changeConfiguration(config_);
2316 
2317 	if (streamState) {
2318 		uint32_t sr = stream_->getStreamRate();
2319 		if (config_.lock()->getSampleRate() != sr) {
2320 			showStreamRateWarningDialog(sr);
2321 			bt_->setStreamRate(sr);
2322 		}
2323 	}
2324 
2325 	setShortcuts();
2326 	updateInstrumentListColors();
2327 	bmManForm_->onConfigurationChanged(config_.lock()->getShowRowNumberInHex());
2328 
2329 	visualTimer_->stop();
2330 	visualTimer_->start(static_cast<int>(std::round(1000. / config_.lock()->getWaveViewFrameRate())));
2331 
2332 	update();
2333 }
2334 
setRealChipInterface(RealChipInterface intf)2335 void MainWindow::setRealChipInterface(RealChipInterface intf)
2336 {
2337 	if (intf == bt_->getRealChipinterface()) return;
2338 
2339 	if (isWindowModified()
2340 			&& QMessageBox::warning(this, tr("Warning"),
2341 									tr("The module has been changed. Do you want to save it?"),
2342 									QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
2343 		on_actionSave_As_triggered();
2344 	}
2345 
2346 	switch (intf) {
2347 	case RealChipInterface::SCCI:
2348 		bt_->useC86CTL(nullptr);
2349 		scciDll_->load();
2350 		if (scciDll_->isLoaded()) {
2351 			auto getManager = reinterpret_cast<scci::SCCIFUNC>(
2352 								  scciDll_->resolve("getSoundInterfaceManager"));
2353 			bt_->useSCCI(getManager ? getManager() : nullptr);
2354 		}
2355 		else {
2356 			bt_->useSCCI(nullptr);
2357 		}
2358 		break;
2359 	case RealChipInterface::C86CTL:
2360 		bt_->useSCCI(nullptr);
2361 		c86ctlDll_->load();
2362 		if (c86ctlDll_->isLoaded()) {
2363 			bt_->useC86CTL(new C86ctlBase(c86ctlDll_->resolve("CreateInstance")));
2364 		}
2365 		else {
2366 			bt_->useC86CTL(nullptr);
2367 		}
2368 		break;
2369 	default:
2370 		break;
2371 	}
2372 
2373 	bt_->assignSampleADPCMRawSamples();	// Mutex register
2374 	instForms_->onInstrumentADPCMSampleMemoryUpdated();
2375 }
2376 
setMidiConfiguration()2377 void MainWindow::setMidiConfiguration()
2378 {
2379 	MidiInterface &midiIntf = MidiInterface::instance();
2380 	std::string midiApi = config_.lock()->getMidiAPI();
2381 	std::string midiInPortName = config_.lock()->getMidiInputPort();
2382 
2383 	std::string errDetail;
2384 	if (!midiIntf.switchApi(midiApi, &errDetail))
2385 		showMidiFailedDialog(QString::fromStdString(errDetail));
2386 
2387 	bool res = true;
2388 	if (!midiInPortName.empty())
2389 		res = midiIntf.openInputPortByName(midiInPortName, &errDetail);
2390 	else if (midiIntf.supportsVirtualPort())
2391 		res = midiIntf.openInputPort(~0u, &errDetail);
2392 	if (!res) showMidiFailedDialog(QString::fromStdString(errDetail));
2393 }
2394 
updateFonts()2395 void MainWindow::updateFonts()
2396 {
2397 	ui->patternEditor->setFonts(
2398 				utf8ToQString(config_.lock()->getPatternEditorHeaderFont()),
2399 				config_.lock()->getPatternEditorHeaderFontSize(),
2400 				utf8ToQString(config_.lock()->getPatternEditorRowsFont()),
2401 				config_.lock()->getPatternEditorRowsFontSize());
2402 	ui->orderList->setFonts(
2403 				utf8ToQString(config_.lock()->getOrderListHeaderFont()),
2404 				config_.lock()->getOrderListHeaderFontSize(),
2405 				utf8ToQString(config_.lock()->getOrderListRowsFont()),
2406 				config_.lock()->getOrderListRowsFontSize());
2407 }
2408 
2409 /********** History change **********/
changeFileHistory(QString file)2410 void MainWindow::changeFileHistory(QString file)
2411 {
2412 	fileHistory_->addFile(file);
2413 	for (int i = ui->menu_Recent_Files->actions().count() - 1; 1 < i; --i)
2414 		ui->menu_Recent_Files->removeAction(ui->menu_Recent_Files->actions().at(i));
2415 	for (size_t i = 0; i < fileHistory_->size(); ++i) {
2416 		// Leave Before Qt5.7.0 style due to windows xp
2417 		QAction* action = ui->menu_Recent_Files->addAction(QString("&%1 %2").arg(i + 1).arg(fileHistory_->at(i)));
2418 		action->setData(fileHistory_->at(i));
2419 	}
2420 }
2421 
2422 /********** Backup **********/
backupModule(QString srcFile)2423 bool MainWindow::backupModule(QString srcFile)
2424 {
2425 	if (!isSavedModBefore_ && config_.lock()->getBackupModules()) {
2426 		bool err = false;
2427 		QString backup = srcFile + ".bak";
2428 		if (QFile::exists(backup)) err = !QFile::remove(backup);
2429 		if (err || !QFile::copy(srcFile, backup)) {
2430 			QMessageBox::critical(this, tr("Error"), tr("Failed to backup module."));
2431 			return false;
2432 		}
2433 	}
2434 	return true;
2435 }
2436 
2437 /******************************/
setWindowTitle()2438 void MainWindow::setWindowTitle()
2439 {
2440 	int n = bt_->getCurrentSongNumber();
2441 	QString filePath = QString::fromStdString(bt_->getModulePath());
2442 	QString fileName = filePath.isEmpty() ? tr("Untitled") : QFileInfo(filePath).fileName();
2443 	QString songTitle = utf8ToQString(bt_->getSongTitle(n));
2444 	if (songTitle.isEmpty()) songTitle = tr("Untitled");
2445 	QMainWindow::setWindowTitle(QString("%1[*] [#%2 %3] - BambooTracker")
2446 								.arg(fileName, QString::number(n), songTitle));
2447 }
2448 
setModifiedTrue()2449 void MainWindow::setModifiedTrue()
2450 {
2451 	isModifiedForNotCommand_ = true;
2452 	setWindowModified(true);
2453 }
2454 
setInitialSelectedInstrument()2455 void MainWindow::setInitialSelectedInstrument()
2456 {
2457 	if (bt_->getInstrumentIndices().empty()) {
2458 		bt_->setCurrentInstrument(-1);
2459 		statusInst_->setText(tr("No instrument"));
2460 	}
2461 	else {
2462 		ui->instrumentList->setCurrentRow(0);
2463 	}
2464 }
2465 
getModuleFileBaseName() const2466 QString MainWindow::getModuleFileBaseName() const
2467 {
2468 	auto filePathStd = bt_->getModulePath();
2469 	QString filePath = QString::fromStdString(filePathStd);
2470 	return (filePath.isEmpty() ? tr("Untitled") : QFileInfo(filePath).completeBaseName());
2471 }
2472 
getSelectedFileFilter(QString & file,QStringList & filters) const2473 int MainWindow::getSelectedFileFilter(QString& file, QStringList& filters) const
2474 {
2475 	QRegularExpression re(R"(\(\*\.(.+)\))");
2476 	QString ex = QFileInfo(file).suffix();
2477 	for (int i = 0; i < filters.size(); ++i)
2478 		if (ex == re.match(filters[i]).captured(1)) return i;
2479 	return -1;
2480 }
2481 
2482 /******************************/
2483 /********** Instrument list events **********/
on_instrumentList_customContextMenuRequested(const QPoint & pos)2484 void MainWindow::on_instrumentList_customContextMenuRequested(const QPoint &pos)
2485 {
2486 	auto& list = ui->instrumentList;
2487 	QPoint globalPos = list->mapToGlobal(pos);
2488 	QMenu menu;
2489 
2490 	// Leave Before Qt5.7.0 style due to windows xp
2491 	menu.addActions({ ui->actionNew_Instrument, ui->actionNew_Drumki_t, ui->actionRemove_Instrument });
2492 	menu.addSeparator();
2493 	menu.addAction(ui->actionRename_Instrument);
2494 	menu.addSeparator();
2495 	menu.addActions({ ui->actionClone_Instrument, ui->actionDeep_Clone_Instrument });
2496 	menu.addSeparator();
2497 	menu.addActions({ ui->actionLoad_From_File, ui->actionSave_To_File });
2498 	menu.addSeparator();
2499 	menu.addActions({ ui->actionImport_From_Bank_File, ui->actionExport_To_Bank_File });
2500 	menu.addSeparator();
2501 	menu.addAction(ui->actionEdit);
2502 
2503 	menu.exec(globalPos);
2504 }
2505 
on_instrumentList_itemDoubleClicked(QListWidgetItem * item)2506 void MainWindow::on_instrumentList_itemDoubleClicked(QListWidgetItem *item)
2507 {
2508 	Q_UNUSED(item)
2509 	openInstrumentEditor();
2510 }
2511 
on_instrumentList_itemSelectionChanged()2512 void MainWindow::on_instrumentList_itemSelectionChanged()
2513 {
2514 	int num = (ui->instrumentList->currentRow() == -1)
2515 			  ? -1
2516 			  : ui->instrumentList->currentItem()->data(Qt::UserRole).toInt();
2517 	bt_->setCurrentInstrument(num);
2518 
2519 	if (num == -1) statusInst_->setText(tr("No instrument"));
2520 	else statusInst_->setText(
2521 				tr("Instrument: %1").arg(QString("%1").arg(num, 2, 16, QChar('0')).toUpper()));
2522 
2523 	bool canAdd = (bt_->findFirstFreeInstrumentNumber() != -1);
2524 	ui->actionLoad_From_File->setEnabled(canAdd);
2525 	ui->actionImport_From_Bank_File->setEnabled(canAdd);
2526 
2527 	bool isSelected = (num != -1);
2528 	ui->actionRemove_Instrument->setEnabled(isSelected);
2529 	ui->actionClone_Instrument->setEnabled(isSelected);
2530 	ui->actionDeep_Clone_Instrument->setEnabled(isSelected);
2531 	ui->actionSave_To_File->setEnabled(isSelected);
2532 	ui->actionExport_To_Bank_File->setEnabled(isSelected);
2533 	ui->actionRename_Instrument->setEnabled(isSelected);
2534 	ui->actionEdit->setEnabled(isSelected);
2535 }
2536 
on_grooveCheckBox_stateChanged(int arg1)2537 void MainWindow::on_grooveCheckBox_stateChanged(int arg1)
2538 {
2539 	if (arg1 == Qt::Checked) {
2540 		ui->tempoSpinBox->setEnabled(false);
2541 		ui->speedSpinBox->setEnabled(false);
2542 		ui->grooveSpinBox->setEnabled(true);
2543 		bt_->toggleTempoOrGrooveInSong(bt_->getCurrentSongNumber(), false);
2544 	}
2545 	else {
2546 		ui->tempoSpinBox->setEnabled(true);
2547 		ui->speedSpinBox->setEnabled(true);
2548 		ui->grooveSpinBox->setEnabled(false);
2549 		bt_->toggleTempoOrGrooveInSong(bt_->getCurrentSongNumber(), true);
2550 	}
2551 
2552 	setModifiedTrue();
2553 }
2554 
on_actionExit_triggered()2555 void MainWindow::on_actionExit_triggered()
2556 {
2557 	close();
2558 }
2559 
on_actionUndo_triggered()2560 void MainWindow::on_actionUndo_triggered()
2561 {
2562 	undo();
2563 }
2564 
on_actionRedo_triggered()2565 void MainWindow::on_actionRedo_triggered()
2566 {
2567 	redo();
2568 }
2569 
on_actionCut_triggered()2570 void MainWindow::on_actionCut_triggered()
2571 {
2572 	if (isEditedPattern_) ui->patternEditor->cutSelectedCells();
2573 }
2574 
on_actionCopy_triggered()2575 void MainWindow::on_actionCopy_triggered()
2576 {
2577 	if (isEditedPattern_) ui->patternEditor->copySelectedCells();
2578 	else if (isEditedOrder_) ui->orderList->copySelectedCells();
2579 }
2580 
on_actionPaste_triggered()2581 void MainWindow::on_actionPaste_triggered()
2582 {
2583 	if (isEditedPattern_) ui->patternEditor->onPastePressed();
2584 	else if (isEditedOrder_) ui->orderList->onPastePressed();
2585 }
2586 
on_actionDelete_triggered()2587 void MainWindow::on_actionDelete_triggered()
2588 {
2589 	if (isEditedPattern_) ui->patternEditor->onDeletePressed();
2590 	else if (isEditedOrder_) ui->orderList->deleteOrder();
2591 	else if (isEditedInstList_) on_actionRemove_Instrument_triggered();
2592 }
2593 
updateMenuByPattern()2594 void MainWindow::updateMenuByPattern()
2595 {
2596 	isEditedPattern_ = true;
2597 	isEditedOrder_ = false;
2598 	isEditedInstList_ = false;
2599 
2600 	if (bt_->isJamMode()) {
2601 		// Edit
2602 		ui->actionPaste->setEnabled(false);
2603 		ui->actionMix->setEnabled(false);
2604 		ui->actionOverwrite->setEnabled(false);
2605 		ui->action_Insert->setEnabled(false);
2606 		ui->actionDelete->setEnabled(false);
2607 		// Pattern
2608 		ui->actionInterpolate->setEnabled(false);
2609 		ui->actionReverse->setEnabled(false);
2610 		ui->actionReplace_Instrument->setEnabled(false);
2611 		ui->actionExpand->setEnabled(false);
2612 		ui->actionShrink->setEnabled(false);
2613 		ui->actionDecrease_Note->setEnabled(false);
2614 		ui->actionIncrease_Note->setEnabled(false);
2615 		ui->actionDecrease_Octave->setEnabled(false);
2616 		ui->actionIncrease_Octave->setEnabled(false);
2617 		ui->actionFine_Decrease_Values->setEnabled(false);
2618 		ui->actionFine_Increase_Values->setEnabled(false);
2619 		ui->actionCoarse_D_ecrease_Values->setEnabled(false);
2620 		ui->actionCoarse_I_ncrease_Values->setEnabled(false);
2621 	}
2622 	else {
2623 		// Edit
2624 		bool enabled = QApplication::clipboard()->text().startsWith("PATTERN_");
2625 		ui->actionPaste->setEnabled(enabled);
2626 		ui->actionMix->setEnabled(enabled);
2627 		ui->actionOverwrite->setEnabled(enabled);
2628 		ui->action_Insert->setEnabled(enabled);
2629 		ui->actionDelete->setEnabled(true);
2630 		// Pattern
2631 		ui->actionInterpolate->setEnabled(isSelectedPattern_);
2632 		ui->actionReverse->setEnabled(isSelectedPattern_);
2633 		ui->actionReplace_Instrument->setEnabled(
2634 					isSelectedPattern_ && ui->instrumentList->currentRow() != -1);
2635 		ui->actionExpand->setEnabled(isSelectedPattern_);
2636 		ui->actionShrink->setEnabled(isSelectedPattern_);
2637 		ui->actionDecrease_Note->setEnabled(true);
2638 		ui->actionIncrease_Note->setEnabled(true);
2639 		ui->actionDecrease_Octave->setEnabled(true);
2640 		ui->actionIncrease_Octave->setEnabled(true);
2641 		ui->actionFine_Decrease_Values->setEnabled(true);
2642 		ui->actionFine_Increase_Values->setEnabled(true);
2643 		ui->actionCoarse_D_ecrease_Values->setEnabled(true);
2644 		ui->actionCoarse_I_ncrease_Values->setEnabled(true);
2645 	}
2646 
2647 	updateMenuByPatternSelection(isSelectedPattern_);
2648 }
2649 
updateMenuByOrder()2650 void MainWindow::updateMenuByOrder()
2651 {
2652 	isEditedPattern_ = false;
2653 	isEditedOrder_ = true;
2654 	isEditedInstList_ = false;
2655 
2656 	// Edit
2657 	bool enabled = QApplication::clipboard()->text().startsWith("ORDER_");
2658 	ui->actionCut->setEnabled(false);
2659 	ui->actionPaste->setEnabled(enabled);
2660 	ui->actionMix->setEnabled(false);
2661 	ui->actionOverwrite->setEnabled(false);
2662 	ui->action_Insert->setEnabled(false);
2663 	ui->actionDelete->setEnabled(true);
2664 	// Song
2665 	bool canAdd = bt_->canAddNewOrder(bt_->getCurrentSongNumber());
2666 	ui->actionInsert_Order->setEnabled(canAdd);
2667 	//ui->actionRemove_Order->setEnabled(true);
2668 	ui->actionDuplicate_Order->setEnabled(canAdd);
2669 	//ui->actionMove_Order_Up->setEnabled(true);
2670 	//ui->actionMove_Order_Down->setEnabled(true);
2671 	ui->actionClone_Patterns->setEnabled(canAdd);
2672 	ui->actionClone_Order->setEnabled(canAdd);
2673 	// Pattern
2674 	ui->actionInterpolate->setEnabled(false);
2675 	ui->actionReverse->setEnabled(false);
2676 	ui->actionReplace_Instrument->setEnabled(false);
2677 	ui->actionExpand->setEnabled(false);
2678 	ui->actionShrink->setEnabled(false);
2679 	ui->actionDecrease_Note->setEnabled(false);
2680 	ui->actionIncrease_Note->setEnabled(false);
2681 	ui->actionDecrease_Octave->setEnabled(false);
2682 	ui->actionIncrease_Octave->setEnabled(false);
2683 	ui->actionFine_Decrease_Values->setEnabled(false);
2684 	ui->actionFine_Increase_Values->setEnabled(false);
2685 	ui->actionCoarse_D_ecrease_Values->setEnabled(false);
2686 	ui->actionCoarse_I_ncrease_Values->setEnabled(false);
2687 
2688 	updateMenuByOrderSelection(isSelectedOrder_);
2689 }
2690 
onCurrentTrackChanged()2691 void MainWindow::onCurrentTrackChanged()
2692 {
2693 	SoundSource src = bt_->getCurrentTrackAttribute().source;
2694 	bool space = (bt_->findFirstFreeInstrumentNumber() != -1);
2695 	ui->actionNew_Instrument->setEnabled((src != SoundSource::RHYTHM) && space);
2696 	ui->actionNew_Drumki_t->setEnabled((src == SoundSource::ADPCM) && space);
2697 }
2698 
updateMenuByInstrumentList()2699 void MainWindow::updateMenuByInstrumentList()
2700 {
2701 	isEditedPattern_ = false;
2702 	isEditedOrder_ = false;
2703 	isEditedInstList_ = true;
2704 
2705 	// Edit
2706 	ui->actionPaste->setEnabled(false);
2707 	ui->actionMix->setEnabled(false);
2708 	ui->actionOverwrite->setEnabled(false);
2709 	ui->action_Insert->setEnabled(false);
2710 	ui->actionDelete->setEnabled(true);
2711 
2712 	// Pattern
2713 	ui->actionInterpolate->setEnabled(false);
2714 	ui->actionReverse->setEnabled(false);
2715 	ui->actionReplace_Instrument->setEnabled(false);
2716 	ui->actionExpand->setEnabled(false);
2717 	ui->actionShrink->setEnabled(false);
2718 	ui->actionDecrease_Note->setEnabled(false);
2719 	ui->actionIncrease_Note->setEnabled(false);
2720 	ui->actionDecrease_Octave->setEnabled(false);
2721 	ui->actionIncrease_Octave->setEnabled(false);
2722 	ui->actionFine_Decrease_Values->setEnabled(false);
2723 	ui->actionFine_Increase_Values->setEnabled(false);
2724 	ui->actionCoarse_D_ecrease_Values->setEnabled(false);
2725 	ui->actionCoarse_I_ncrease_Values->setEnabled(false);
2726 }
2727 
updateMenuByPatternSelection(bool isSelected)2728 void MainWindow::updateMenuByPatternSelection(bool isSelected)
2729 {
2730 	isSelectedPattern_ = isSelected;
2731 
2732 	if (bt_->isJamMode()) {
2733 		// Edit
2734 		ui->actionCopy->setEnabled(false);
2735 		ui->actionCut->setEnabled(false);
2736 		// Pattern
2737 		ui->actionInterpolate->setEnabled(false);
2738 		ui->actionReverse->setEnabled(false);
2739 		ui->actionReplace_Instrument->setEnabled(false);
2740 		ui->actionExpand->setEnabled(false);
2741 		ui->actionShrink->setEnabled(false);
2742 	}
2743 	else {
2744 		// Edit
2745 		ui->actionCopy->setEnabled(isSelected);
2746 		ui->actionCut->setEnabled(isEditedPattern_ ? isSelected : false);
2747 		// Pattern
2748 		bool enabled = (isEditedPattern_ && isEditedPattern_) ? isSelected : false;
2749 		ui->actionInterpolate->setEnabled(enabled);
2750 		ui->actionReverse->setEnabled(enabled);
2751 		ui->actionReplace_Instrument->setEnabled(
2752 					enabled && ui->instrumentList->currentRow() != -1);
2753 		ui->actionExpand->setEnabled(enabled);
2754 		ui->actionShrink->setEnabled(enabled);
2755 	}
2756 }
2757 
updateMenuByOrderSelection(bool isSelected)2758 void MainWindow::updateMenuByOrderSelection(bool isSelected)
2759 {
2760 	isSelectedOrder_ = isSelected;
2761 
2762 	// Edit
2763 	ui->actionCopy->setEnabled(isSelected);
2764 }
2765 
on_actionAll_triggered()2766 void MainWindow::on_actionAll_triggered()
2767 {
2768 	if (isEditedPattern_) ui->patternEditor->onSelectPressed(1);
2769 	else if (isEditedOrder_) ui->orderList->onSelectPressed(1);
2770 }
2771 
on_actionNone_triggered()2772 void MainWindow::on_actionNone_triggered()
2773 {
2774 	if (isEditedPattern_) ui->patternEditor->onSelectPressed(0);
2775 	else if (isEditedOrder_) ui->orderList->onSelectPressed(0);
2776 }
2777 
on_actionDecrease_Note_triggered()2778 void MainWindow::on_actionDecrease_Note_triggered()
2779 {
2780 	if (isEditedPattern_) ui->patternEditor->onTransposePressed(false, false);
2781 }
2782 
on_actionIncrease_Note_triggered()2783 void MainWindow::on_actionIncrease_Note_triggered()
2784 {
2785 	if (isEditedPattern_) ui->patternEditor->onTransposePressed(false, true);
2786 }
2787 
on_actionDecrease_Octave_triggered()2788 void MainWindow::on_actionDecrease_Octave_triggered()
2789 {
2790 	if (isEditedPattern_) ui->patternEditor->onTransposePressed(true, false);
2791 }
2792 
on_actionIncrease_Octave_triggered()2793 void MainWindow::on_actionIncrease_Octave_triggered()
2794 {
2795 	if (isEditedPattern_) ui->patternEditor->onTransposePressed(true, true);
2796 }
2797 
on_actionInsert_Order_triggered()2798 void MainWindow::on_actionInsert_Order_triggered()
2799 {
2800 	ui->orderList->insertOrderBelow();
2801 }
2802 
on_actionRemove_Order_triggered()2803 void MainWindow::on_actionRemove_Order_triggered()
2804 {
2805 	ui->orderList->deleteOrder();
2806 }
2807 
on_actionModule_Properties_triggered()2808 void MainWindow::on_actionModule_Properties_triggered()
2809 {
2810 	ModulePropertiesDialog dialog(bt_, config_.lock()->getMixerVolumeFM(), config_.lock()->getMixerVolumeSSG());
2811 	if (dialog.exec() == QDialog::Accepted
2812 			&& showUndoResetWarningDialog(tr("Do you want to change song properties?"))) {
2813 		int instRow = ui->instrumentList->currentRow();
2814 		bt_->stopPlaySong();
2815 		lockWidgets(false);
2816 		dialog.onAccepted();
2817 		freezeViews();
2818 		if (!timer_) stream_->stop();
2819 		loadModule();
2820 		setModifiedTrue();
2821 		setWindowTitle();
2822 		ui->instrumentList->setCurrentRow(instRow);
2823 		if (!timer_) stream_->start();
2824 		assignADPCMSamples();
2825 	}
2826 }
2827 
on_actionNew_Instrument_triggered()2828 void MainWindow::on_actionNew_Instrument_triggered()
2829 {
2830 	addInstrument();
2831 }
2832 
on_actionRemove_Instrument_triggered()2833 void MainWindow::on_actionRemove_Instrument_triggered()
2834 {
2835 	removeInstrument(ui->instrumentList->currentRow());
2836 }
2837 
on_actionClone_Instrument_triggered()2838 void MainWindow::on_actionClone_Instrument_triggered()
2839 {
2840 	cloneInstrument();
2841 }
2842 
on_actionDeep_Clone_Instrument_triggered()2843 void MainWindow::on_actionDeep_Clone_Instrument_triggered()
2844 {
2845 	deepCloneInstrument();
2846 }
2847 
on_actionEdit_triggered()2848 void MainWindow::on_actionEdit_triggered()
2849 {
2850 	openInstrumentEditor();
2851 }
2852 
on_actionPlay_triggered()2853 void MainWindow::on_actionPlay_triggered()
2854 {
2855 	startPlaySong();
2856 }
2857 
on_actionPlay_Pattern_triggered()2858 void MainWindow::on_actionPlay_Pattern_triggered()
2859 {
2860 	startPlayPattern();
2861 }
2862 
on_actionPlay_From_Start_triggered()2863 void MainWindow::on_actionPlay_From_Start_triggered()
2864 {
2865 	startPlayFromStart();
2866 }
2867 
on_actionPlay_From_Cursor_triggered()2868 void MainWindow::on_actionPlay_From_Cursor_triggered()
2869 {
2870 	startPlayFromCurrentStep();
2871 }
2872 
on_actionStop_triggered()2873 void MainWindow::on_actionStop_triggered()
2874 {
2875 	stopPlaySong();
2876 }
2877 
on_actionEdit_Mode_triggered()2878 void MainWindow::on_actionEdit_Mode_triggered()
2879 {
2880 	bt_->toggleJamMode();
2881 	ui->orderList->changeEditable();
2882 	ui->patternEditor->changeEditable();
2883 
2884 	if (isEditedOrder_) updateMenuByOrder();
2885 	else if (isEditedPattern_) updateMenuByPattern();
2886 
2887 	bool editable = !bt_->isJamMode();
2888 	statusDetail_->setText(editable ? tr("Change to edit mode") : tr("Change to jam mode"));
2889 	ui->action_Toggle_Bookmark->setEnabled(editable);
2890 }
2891 
on_actionToggle_Track_triggered()2892 void MainWindow::on_actionToggle_Track_triggered()
2893 {
2894 	ui->patternEditor->onToggleTrackPressed();
2895 }
2896 
on_actionSolo_Track_triggered()2897 void MainWindow::on_actionSolo_Track_triggered()
2898 {
2899 	ui->patternEditor->onSoloTrackPressed();
2900 }
2901 
on_actionKill_Sound_triggered()2902 void MainWindow::on_actionKill_Sound_triggered()
2903 {
2904 	bt_->killSound();
2905 }
2906 
on_actionAbout_triggered()2907 void MainWindow::on_actionAbout_triggered()
2908 {
2909 	QMessageBox box(QMessageBox::NoIcon,
2910 					tr("About"),
2911 					QString("<h2>BambooTracker v%1</h2>").arg(
2912 						QString::fromStdString(Version::ofApplicationInString()))
2913 					+ tr("<b>YM2608 (OPNA) Music Tracker<br>"
2914 						 "Copyright (C) 2018-2020 Rerrah</b><br>"
2915 						 "<hr>"
2916 						 "Libraries:<br>"
2917 						 "- C86CTL by (C) honet (BSD 3-Clause)<br>"
2918 						 "- libOPNMIDI by (C) Vitaly Novichkov (MIT License part)<br>"
2919 						 "- MAME (MAME License)<br>"
2920 						 "- Nuked OPN-MOD by (C) Alexey Khokholov (Nuke.YKT)<br>"
2921 						 "and (C) Jean Pierre Cimalando (LGPL v2.1)<br>"
2922 						 "- RtAudio by (C) Gary P. Scavone (RtAudio License)<br>"
2923 						 "- RtMidi by (C) Gary P. Scavone (RtMidi License)<br>"
2924 						 "- SCCI by (C) gasshi (SCCI License)<br>"
2925 						 "- Silk icons by (C) Mark James (CC BY 2.5 or 3.0)<br>"
2926 						 "- Qt (GPL v2+ or LGPL v3)<br>"
2927 						 "- VGMPlay by (C) Valley Bell (GPL v2)<br>"
2928 						 "<br>"
2929 						 "Also see changelog which lists contributors."),
2930 					QMessageBox::Ok,
2931 					this);
2932 	box.setIconPixmap(QIcon(":/icon/app_icon").pixmap(QSize(44, 44)));
2933 	box.exec();
2934 }
2935 
on_actionFollow_Mode_triggered()2936 void MainWindow::on_actionFollow_Mode_triggered()
2937 {
2938 	bt_->setFollowPlay(ui->actionFollow_Mode->isChecked());
2939 	config_.lock()->setFollowMode(ui->actionFollow_Mode->isChecked());
2940 
2941 	ui->orderList->onFollowModeChanged();
2942 	ui->patternEditor->onFollowModeChanged();
2943 }
2944 
on_actionGroove_Settings_triggered()2945 void MainWindow::on_actionGroove_Settings_triggered()
2946 {
2947 	std::vector<std::vector<int>> seqs(bt_->getGrooveCount());
2948 	std::generate(seqs.begin(), seqs.end(), [&, i = 0]() mutable { return bt_->getGroove(i++); });
2949 
2950 	GrooveSettingsDialog diag;
2951 	diag.setGrooveSquences(seqs);
2952 	if (diag.exec() == QDialog::Accepted) {
2953 		bt_->stopPlaySong();
2954 		lockWidgets(false);
2955 		bt_->setGrooves(diag.getGrooveSequences());
2956 		ui->grooveSpinBox->setMaximum(static_cast<int>(bt_->getGrooveCount()) - 1);
2957 		setModifiedTrue();
2958 	}
2959 }
2960 
on_actionConfiguration_triggered()2961 void MainWindow::on_actionConfiguration_triggered()
2962 {
2963 	ConfigurationDialog diag(config_.lock(), palette_, stream_);
2964 	QObject::connect(&diag, &ConfigurationDialog::applyPressed, this, &MainWindow::changeConfiguration);
2965 
2966 	if (diag.exec() == QDialog::Accepted) {
2967 		bt_->stopPlaySong();
2968 		changeConfiguration();
2969 		ConfigurationHandler::saveConfiguration(config_.lock());
2970 		ColorPaletteHandler::savePalette(palette_.get());
2971 		lockWidgets(false);
2972 	}
2973 }
2974 
on_actionExpand_triggered()2975 void MainWindow::on_actionExpand_triggered()
2976 {
2977 	ui->patternEditor->onExpandPressed();
2978 }
2979 
on_actionShrink_triggered()2980 void MainWindow::on_actionShrink_triggered()
2981 {
2982 	ui->patternEditor->onShrinkPressed();
2983 }
2984 
on_actionDuplicate_Order_triggered()2985 void MainWindow::on_actionDuplicate_Order_triggered()
2986 {
2987 	ui->orderList->onDuplicatePressed();
2988 }
2989 
on_actionMove_Order_Up_triggered()2990 void MainWindow::on_actionMove_Order_Up_triggered()
2991 {
2992 	ui->orderList->onMoveOrderPressed(true);
2993 }
2994 
on_actionMove_Order_Down_triggered()2995 void MainWindow::on_actionMove_Order_Down_triggered()
2996 {
2997 	ui->orderList->onMoveOrderPressed(false);
2998 }
2999 
on_actionClone_Patterns_triggered()3000 void MainWindow::on_actionClone_Patterns_triggered()
3001 {
3002 	ui->orderList->onClonePatternsPressed();
3003 }
3004 
on_actionClone_Order_triggered()3005 void MainWindow::on_actionClone_Order_triggered()
3006 {
3007 	ui->orderList->onCloneOrderPressed();
3008 }
3009 
on_actionNew_triggered()3010 void MainWindow::on_actionNew_triggered()
3011 {
3012 	if (isWindowModified()) {
3013 		QString modTitle = utf8ToQString(bt_->getModuleTitle());
3014 		if (modTitle.isEmpty()) modTitle = tr("Untitled");
3015 		QMessageBox dialog(QMessageBox::Warning,
3016 						   "BambooTracker",
3017 						   tr("Save changes to %1?").arg(modTitle),
3018 						   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
3019 		switch (dialog.exec()) {
3020 		case QMessageBox::Yes:
3021 			if (!on_actionSave_triggered()) return;
3022 			break;
3023 		case QMessageBox::No:
3024 			break;
3025 		case QMessageBox::Cancel:
3026 			return;
3027 		default:
3028 			break;
3029 		}
3030 	}
3031 
3032 	bt_->stopPlaySong();
3033 	lockWidgets(false);
3034 	freezeViews();
3035 	if (!timer_) stream_->stop();
3036 	bt_->makeNewModule();
3037 	loadModule();
3038 	setInitialSelectedInstrument();
3039 	isModifiedForNotCommand_ = false;
3040 	setWindowModified(false);
3041 	if (!timer_) stream_->start();
3042 	assignADPCMSamples();
3043 }
3044 
on_actionComments_triggered()3045 void MainWindow::on_actionComments_triggered()
3046 {
3047 	if (commentDiag_) {
3048 		if (commentDiag_->isVisible()) commentDiag_->activateWindow();
3049 		else commentDiag_->show();
3050 	}
3051 	else {
3052 		commentDiag_ = std::make_unique<CommentEditDialog>(utf8ToQString(bt_->getModuleComment()));
3053 		commentDiag_->show();
3054 		QObject::connect(commentDiag_.get(), &CommentEditDialog::commentChanged,
3055 						 this, [&](const QString text) {
3056 			bt_->setModuleComment(text.toUtf8().toStdString());
3057 			setModifiedTrue();
3058 		});
3059 	}
3060 }
3061 
on_actionSave_triggered()3062 bool MainWindow::on_actionSave_triggered()
3063 {
3064 	auto path = QString::fromStdString(bt_->getModulePath());
3065 	if (!path.isEmpty() && QFileInfo::exists(path) && QFileInfo(path).isFile()) {
3066 		if (!backupModule(path)) return false;
3067 
3068 		try {
3069 			BinaryContainer container;
3070 			bt_->saveModule(container);
3071 
3072 			QFile fp(path);
3073 			if (!fp.open(QIODevice::WriteOnly)) {
3074 				FileIOErrorMessageBox::openError(path, false, FileIO::FileType::Mod, this);
3075 				return false;
3076 			}
3077 			fp.write(container.getPointer(), container.size());
3078 			fp.close();
3079 
3080 			isModifiedForNotCommand_ = false;
3081 			isSavedModBefore_ = true;
3082 			setWindowModified(false);
3083 			setWindowTitle();
3084 			return true;
3085 		}
3086 		catch (FileIOError& e) {
3087 			FileIOErrorMessageBox(path, false, e, this).exec();
3088 			return false;
3089 		}
3090 		catch (std::exception& e) {
3091 			FileIOErrorMessageBox(path, false, FileIO::FileType::Mod, QString(e.what()), this).exec();
3092 			return false;
3093 		}
3094 	}
3095 	else {
3096 		return on_actionSave_As_triggered();
3097 	}
3098 }
3099 
on_actionSave_As_triggered()3100 bool MainWindow::on_actionSave_As_triggered()
3101 {
3102 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3103 	QString file = QFileDialog::getSaveFileName(
3104 					   this, tr("Save module"),
3105 					   QString("%1/%2.btm").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()),
3106 					   tr("BambooTracker module (*.btm)"));
3107 	if (file.isNull()) return false;
3108 	if (!file.endsWith(".btm")) file += ".btm";	// For linux
3109 
3110 	if (QFile::exists(file)) {	// Backup if the module already exists
3111 		if (!backupModule(file)) return false;
3112 	}
3113 
3114 	bt_->setModulePath(file.toStdString());
3115 	try {
3116 		BinaryContainer container;
3117 		bt_->saveModule(container);
3118 
3119 		QFile fp(file);
3120 		if (!fp.open(QIODevice::WriteOnly)) {
3121 			FileIOErrorMessageBox::openError(file, false, FileIO::FileType::Mod, this);
3122 			return false;
3123 		}
3124 		fp.write(container.getPointer(), container.size());
3125 		fp.close();
3126 
3127 		isModifiedForNotCommand_ = false;
3128 		isSavedModBefore_ = true;
3129 		setWindowModified(false);
3130 		setWindowTitle();
3131 		config_.lock()->setWorkingDirectory(QFileInfo(file).dir().path().toStdString());
3132 		changeFileHistory(file);
3133 		return true;
3134 	}
3135 	catch (FileIOError& e) {
3136 		FileIOErrorMessageBox(file, false, e, this).exec();
3137 		return false;
3138 	}
3139 	catch (std::exception& e) {
3140 		FileIOErrorMessageBox(file, false, FileIO::FileType::Mod, QString(e.what()), this).exec();
3141 		return false;
3142 	}
3143 }
3144 
on_actionOpen_triggered()3145 void MainWindow::on_actionOpen_triggered()
3146 {
3147 	if (isWindowModified()) {
3148 		QString modTitle = utf8ToQString(bt_->getModuleTitle());
3149 		if (modTitle.isEmpty()) modTitle = tr("Untitled");
3150 		QMessageBox dialog(QMessageBox::Warning,
3151 						   "BambooTracker",
3152 						   tr("Save changes to %1?").arg(modTitle),
3153 						   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
3154 		switch (dialog.exec()) {
3155 		case QMessageBox::Yes:
3156 			if (!on_actionSave_triggered()) return;
3157 			break;
3158 		case QMessageBox::No:
3159 			break;
3160 		case QMessageBox::Cancel:
3161 			return;
3162 		default:
3163 			break;
3164 		}
3165 	}
3166 
3167 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3168 	QString file = QFileDialog::getOpenFileName(this, tr("Open module"), (dir.isEmpty() ? "./" : dir),
3169 												tr("BambooTracker module (*.btm)"));
3170 	if (file.isNull()) return;
3171 
3172 	bt_->stopPlaySong();
3173 	lockWidgets(false);
3174 
3175 	openModule(file);
3176 }
3177 
on_actionLoad_From_File_triggered()3178 void MainWindow::on_actionLoad_From_File_triggered()
3179 {
3180 	loadInstrument();
3181 }
3182 
on_actionSave_To_File_triggered()3183 void MainWindow::on_actionSave_To_File_triggered()
3184 {
3185 	saveInstrument();
3186 }
3187 
on_actionImport_From_Bank_File_triggered()3188 void MainWindow::on_actionImport_From_Bank_File_triggered()
3189 {
3190 	importInstrumentsFromBank();
3191 }
3192 
on_actionInterpolate_triggered()3193 void MainWindow::on_actionInterpolate_triggered()
3194 {
3195 	ui->patternEditor->onInterpolatePressed();
3196 }
3197 
on_actionReverse_triggered()3198 void MainWindow::on_actionReverse_triggered()
3199 {
3200 	ui->patternEditor->onReversePressed();
3201 }
3202 
on_actionReplace_Instrument_triggered()3203 void MainWindow::on_actionReplace_Instrument_triggered()
3204 {
3205 	ui->patternEditor->onReplaceInstrumentPressed();
3206 }
3207 
on_actionRow_triggered()3208 void MainWindow::on_actionRow_triggered()
3209 {
3210 	if (isEditedPattern_) ui->patternEditor->onSelectPressed(2);
3211 	else if (isEditedOrder_) ui->orderList->onSelectPressed(2);
3212 }
3213 
on_actionColumn_triggered()3214 void MainWindow::on_actionColumn_triggered()
3215 {
3216 	if (isEditedPattern_) ui->patternEditor->onSelectPressed(3);
3217 	else if (isEditedOrder_) ui->orderList->onSelectPressed(3);
3218 }
3219 
on_actionPattern_triggered()3220 void MainWindow::on_actionPattern_triggered()
3221 {
3222 	if (isEditedPattern_) ui->patternEditor->onSelectPressed(4);
3223 	else if (isEditedOrder_) ui->orderList->onSelectPressed(4);
3224 }
3225 
on_actionOrder_triggered()3226 void MainWindow::on_actionOrder_triggered()
3227 {
3228 	if (isEditedPattern_) ui->patternEditor->onSelectPressed(5);
3229 	else if (isEditedOrder_) ui->orderList->onSelectPressed(5);
3230 }
3231 
on_actionRemove_Unused_Instruments_triggered()3232 void MainWindow::on_actionRemove_Unused_Instruments_triggered()
3233 {
3234 	if (showUndoResetWarningDialog(tr("Do you want to remove all unused instruments?"))) {
3235 		bt_->stopPlaySong();
3236 		lockWidgets(false);
3237 
3238 		auto list = ui->instrumentList;
3239 		for (auto& n : bt_->getUnusedInstrumentIndices()) {
3240 			for (int i = 0; i < list->count(); ++i) {
3241 				if (list->item(i)->data(Qt::UserRole).toInt() == n) {
3242 					removeInstrument(i);
3243 				}
3244 			}
3245 		}
3246 		bt_->clearUnusedInstrumentProperties();
3247 		bt_->clearCommandHistory();
3248 		comStack_->clear();
3249 		setModifiedTrue();
3250 	}
3251 }
3252 
on_actionRemove_Unused_Patterns_triggered()3253 void MainWindow::on_actionRemove_Unused_Patterns_triggered()
3254 {
3255 	if (showUndoResetWarningDialog(tr("Do you want to remove all unused patterns?"))) {
3256 		bt_->stopPlaySong();
3257 		lockWidgets(false);
3258 
3259 		bt_->clearUnusedPatterns();
3260 		bt_->clearCommandHistory();
3261 		comStack_->clear();
3262 		setModifiedTrue();
3263 	}
3264 }
3265 
on_actionWAV_triggered()3266 void MainWindow::on_actionWAV_triggered()
3267 {
3268 	WaveExportSettingsDialog diag;
3269 	if (diag.exec() != QDialog::Accepted) return;
3270 
3271 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3272 	QString path = QFileDialog::getSaveFileName(
3273 					   this, tr("Export to WAV"),
3274 					   QString("%1/%2.wav").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()),
3275 					   tr("WAV signed 16-bit PCM (*.wav)"));
3276 	if (path.isNull()) return;
3277 	if (!path.endsWith(".wav")) path += ".wav";	// For linux
3278 
3279 	int max = static_cast<int>(bt_->getAllStepCount(
3280 								   bt_->getCurrentSongNumber(), static_cast<size_t>(diag.getLoopCount()))) + 3;
3281 	QProgressDialog progress(tr("Export to WAV"), tr("Cancel"), 0, max);
3282 	progress.setValue(0);
3283 	progress.setWindowFlags(progress.windowFlags()
3284 							& ~Qt::WindowContextHelpButtonHint
3285 							& ~Qt::WindowCloseButtonHint);
3286 	progress.show();
3287 
3288 	bt_->stopPlaySong();
3289 	lockWidgets(false);
3290 	stream_->stop();
3291 
3292 	try {
3293 		WavContainer container(0, static_cast<uint32_t>(diag.getSampleRate()));
3294 		auto bar = [&progress]() -> bool {
3295 				   QApplication::processEvents();
3296 				   progress.setValue(progress.value() + 1);
3297 				   return progress.wasCanceled();
3298 	};
3299 
3300 		bool res = bt_->exportToWav(container, diag.getLoopCount(), bar);
3301 		if (res) {
3302 			QFile fp(path);
3303 			if (!fp.open(QIODevice::WriteOnly)) {
3304 				FileIOErrorMessageBox::openError(path, false, FileIO::FileType::WAV, this);
3305 			}
3306 			else {
3307 				BinaryContainer bc = container.createWavBinary();
3308 				fp.write(bc.getPointer(), bc.size());
3309 				fp.close();
3310 				bar();
3311 
3312 				config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString());
3313 			}
3314 		}
3315 	}
3316 	catch (FileIOError& e) {
3317 		FileIOErrorMessageBox(path, false, e, this).exec();
3318 	}
3319 	catch (std::exception& e) {
3320 		FileIOErrorMessageBox(path, false, FileIO::FileType::WAV, QString(e.what()), this).exec();
3321 	}
3322 
3323 	stream_->start();
3324 }
3325 
on_actionVGM_triggered()3326 void MainWindow::on_actionVGM_triggered()
3327 {
3328 	VgmExportSettingsDialog diag;
3329 	if (diag.exec() != QDialog::Accepted) return;
3330 	GD3Tag tag = diag.getGD3Tag();
3331 
3332 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3333 	QString path = QFileDialog::getSaveFileName(
3334 					   this, tr("Export to VGM"),
3335 					   QString("%1/%2.vgm").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()),
3336 					   tr("VGM file (*.vgm)"));
3337 	if (path.isNull()) return;
3338 	if (!path.endsWith(".vgm")) path += ".vgm";	// For linux
3339 
3340 	int max = static_cast<int>(bt_->getAllStepCount(bt_->getCurrentSongNumber(), 1)) + 3;
3341 	QProgressDialog progress(tr("Export to VGM"), tr("Cancel"), 0, max);
3342 	progress.setValue(0);
3343 	progress.setWindowFlags(progress.windowFlags()
3344 							& ~Qt::WindowContextHelpButtonHint
3345 							& ~Qt::WindowCloseButtonHint);
3346 	progress.show();
3347 
3348 	bt_->stopPlaySong();
3349 	lockWidgets(false);
3350 	stream_->stop();
3351 
3352 	try {
3353 		BinaryContainer container;
3354 		auto bar = [&progress]() -> bool {
3355 				   QApplication::processEvents();
3356 				   progress.setValue(progress.value() + 1);
3357 				   return progress.wasCanceled();
3358 	};
3359 
3360 		bool res = bt_->exportToVgm(container, diag.getExportTarget(), diag.enabledGD3(), tag, bar);
3361 		if (res) {
3362 			QFile fp(path);
3363 			if (!fp.open(QIODevice::WriteOnly)) {
3364 				FileIOErrorMessageBox::openError(path, false, FileIO::FileType::VGM, this);
3365 			}
3366 			else {
3367 				fp.write(container.getPointer(), container.size());
3368 				fp.close();
3369 				bar();
3370 
3371 				config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString());
3372 			}
3373 		}
3374 	}
3375 	catch (FileIOError& e) {
3376 		FileIOErrorMessageBox(path, false, e, this).exec();
3377 	}
3378 	catch (std::exception& e) {
3379 		FileIOErrorMessageBox(path, false, FileIO::FileType::VGM, QString(e.what()), this).exec();
3380 	}
3381 
3382 	stream_->start();
3383 }
3384 
on_actionS98_triggered()3385 void MainWindow::on_actionS98_triggered()
3386 {
3387 	S98ExportSettingsDialog diag;
3388 	if (diag.exec() != QDialog::Accepted) return;
3389 	S98Tag tag = diag.getS98Tag();
3390 
3391 	QString dir = QString::fromStdString(config_.lock()->getWorkingDirectory());
3392 	QString path = QFileDialog::getSaveFileName(
3393 					   this, tr("Export to S98"),
3394 					   QString("%1/%2.s98").arg(dir.isEmpty() ? "." : dir, getModuleFileBaseName()),
3395 					   tr("S98 file (*.s98)"));
3396 	if (path.isNull()) return;
3397 	if (!path.endsWith(".s98")) path += ".s98";	// For linux
3398 
3399 	int max = static_cast<int>(bt_->getAllStepCount(bt_->getCurrentSongNumber(), 1)) + 3;
3400 	QProgressDialog progress(tr("Export to S98"), tr("Cancel"), 0, max);
3401 	progress.setValue(0);
3402 	progress.setWindowFlags(progress.windowFlags()
3403 							& ~Qt::WindowContextHelpButtonHint
3404 							& ~Qt::WindowCloseButtonHint);
3405 	progress.show();
3406 
3407 	bt_->stopPlaySong();
3408 	lockWidgets(false);
3409 	stream_->stop();
3410 
3411 	try {
3412 		BinaryContainer container;
3413 		auto bar = [&progress]() -> bool {
3414 				   QApplication::processEvents();
3415 				   progress.setValue(progress.value() + 1);
3416 				   return progress.wasCanceled();
3417 	};
3418 
3419 		bool res = bt_->exportToS98(container, diag.getExportTarget(), diag.enabledTag(),
3420 									tag, diag.getResolution(), bar);
3421 		if (res) {
3422 			QFile fp(path);
3423 			if (!fp.open(QIODevice::WriteOnly)) {
3424 				FileIOErrorMessageBox::openError(path, false, FileIO::FileType::S98, this);
3425 			}
3426 			else {
3427 				fp.write(container.getPointer(), container.size());
3428 				fp.close();
3429 				bar();
3430 
3431 				config_.lock()->setWorkingDirectory(QFileInfo(path).dir().path().toStdString());
3432 			}
3433 		}
3434 	}
3435 	catch (FileIOError& e) {
3436 		FileIOErrorMessageBox(path, false, e, this).exec();
3437 	}
3438 	catch (std::exception& e) {
3439 		FileIOErrorMessageBox(path, false, FileIO::FileType::S98, QString(e.what()), this).exec();
3440 	}
3441 
3442 	stream_->start();
3443 }
3444 
on_actionMix_triggered()3445 void MainWindow::on_actionMix_triggered()
3446 {
3447 	if (isEditedPattern_) ui->patternEditor->onPasteMixPressed();
3448 }
3449 
on_actionOverwrite_triggered()3450 void MainWindow::on_actionOverwrite_triggered()
3451 {
3452 	if (isEditedPattern_) ui->patternEditor->onPasteOverwritePressed();
3453 }
3454 
onNewTickSignaledRealChip()3455 void MainWindow::onNewTickSignaledRealChip()
3456 {
3457 	onNewTickSignaled(bt_->streamCountUp());
3458 }
3459 
onNewTickSignaled(int state)3460 void MainWindow::onNewTickSignaled(int state)
3461 {
3462 	if (!state) {	// New step
3463 		int order = bt_->getPlayingOrderNumber();
3464 		if (order > -1) {	// Playing
3465 			if (isVisible() && !isMinimized()) {
3466 				ui->orderList->updatePositionByOrderUpdate(firstViewUpdateRequest_);
3467 				ui->patternEditor->updatePositionByStepUpdate(firstViewUpdateRequest_);
3468 				firstViewUpdateRequest_ = false;
3469 			}
3470 
3471 			int width, base;
3472 			if (config_.lock()->getShowRowNumberInHex()) {
3473 				width = 2;
3474 				base = 16;
3475 			}
3476 			else {
3477 				width = 3;
3478 				base = 10;
3479 			}
3480 			statusPlayPos_->setText(
3481 						QString("%1/%2")
3482 						.arg(order, width, base, QChar('0'))
3483 						.arg(bt_->getPlayingStepNumber(), width, base, QChar('0')).toUpper());
3484 		}
3485 	}
3486 	else if (state == -1) {
3487 		if (hasLockedWigets_) lockWidgets(false);
3488 	}
3489 
3490 	// Update BPM status
3491 	if (bt_->getStreamGrooveEnabled()) {
3492 		statusBpm_->setText(tr("Groove"));
3493 	}
3494 	else {
3495 		// BPM = tempo * 6 / speed * 4 / 1st highlight
3496 		double bpm = 24.0 * bt_->getStreamTempo() / bt_->getStreamSpeed() / highlight1_->value();
3497 		statusBpm_->setText(QString::number(bpm, 'f', 2) + QString(" BPM"));
3498 	}
3499 }
3500 
on_actionClear_triggered()3501 void MainWindow::on_actionClear_triggered()
3502 {
3503 	fileHistory_->clearHistory();
3504 	for (int i = ui->menu_Recent_Files->actions().count() - 1; 1 < i; --i)
3505 		ui->menu_Recent_Files->removeAction(ui->menu_Recent_Files->actions().at(i));
3506 }
3507 
on_keyRepeatCheckBox_stateChanged(int arg1)3508 void MainWindow::on_keyRepeatCheckBox_stateChanged(int arg1)
3509 {
3510 	config_.lock()->setKeyRepetition(arg1 == Qt::Checked);
3511 }
3512 
updateVisuals()3513 void MainWindow::updateVisuals()
3514 {
3515 	int16_t wave[2 * OPNAController::OUTPUT_HISTORY_SIZE];
3516 	bt_->getOutputHistory(wave);
3517 
3518 	ui->waveVisual->setStereoSamples(wave, OPNAController::OUTPUT_HISTORY_SIZE);
3519 }
3520 
on_action_Effect_List_triggered()3521 void MainWindow::on_action_Effect_List_triggered()
3522 {
3523 	if (effListDiag_) {
3524 		if (effListDiag_->isVisible()) effListDiag_->activateWindow();
3525 		else effListDiag_->show();
3526 	}
3527 	else {
3528 		effListDiag_ = std::make_unique<EffectListDialog>();
3529 		effListDiag_->show();
3530 	}
3531 }
3532 
on_actionShortcuts_triggered()3533 void MainWindow::on_actionShortcuts_triggered()
3534 {
3535 	if (shortcutsDiag_) {
3536 		if (shortcutsDiag_->isVisible()) shortcutsDiag_->activateWindow();
3537 		else shortcutsDiag_->show();
3538 	}
3539 	else {
3540 		shortcutsDiag_ = std::make_unique<KeyboardShortcutListDialog>();
3541 		shortcutsDiag_->show();
3542 	}
3543 }
3544 
on_actionExport_To_Bank_File_triggered()3545 void MainWindow::on_actionExport_To_Bank_File_triggered()
3546 {
3547 	exportInstrumentsToBank();
3548 }
3549 
on_actionRemove_Duplicate_Instruments_triggered()3550 void MainWindow::on_actionRemove_Duplicate_Instruments_triggered()
3551 {
3552 	if (showUndoResetWarningDialog(tr("Do you want to remove all duplicate instruments?"))) {
3553 		bt_->stopPlaySong();
3554 		lockWidgets(false);
3555 
3556 		std::vector<std::vector<int>> duplicates = bt_->checkDuplicateInstruments();
3557 		auto list = ui->instrumentList;
3558 		for (auto& group : duplicates) {
3559 			for (size_t i = 1; i < group.size(); ++i) {
3560 				for (int j = 0; j < list->count(); ++j) {
3561 					if (list->item(j)->data(Qt::UserRole).toInt() == group[i])
3562 						removeInstrument(j);
3563 				}
3564 			}
3565 		}
3566 		bt_->replaceDuplicateInstrumentsInPatterns(duplicates);
3567 		bt_->clearUnusedInstrumentProperties();
3568 		bt_->clearCommandHistory();
3569 		comStack_->clear();
3570 		ui->patternEditor->onDuplicateInstrumentsRemoved();
3571 		setModifiedTrue();
3572 	}
3573 }
3574 
on_actionRename_Instrument_triggered()3575 void MainWindow::on_actionRename_Instrument_triggered()
3576 {
3577 	renameInstrument();
3578 }
3579 
on_action_Bookmark_Manager_triggered()3580 void MainWindow::on_action_Bookmark_Manager_triggered()
3581 {
3582 	if (bmManForm_->isVisible()) bmManForm_->activateWindow();
3583 	else bmManForm_->show();
3584 }
3585 
on_actionFine_Decrease_Values_triggered()3586 void MainWindow::on_actionFine_Decrease_Values_triggered()
3587 {
3588 	if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(false, false);
3589 }
3590 
on_actionFine_Increase_Values_triggered()3591 void MainWindow::on_actionFine_Increase_Values_triggered()
3592 {
3593 	if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(false, true);
3594 }
3595 
on_actionCoarse_D_ecrease_Values_triggered()3596 void MainWindow::on_actionCoarse_D_ecrease_Values_triggered()
3597 {
3598 	if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(true, false);
3599 }
3600 
on_actionCoarse_I_ncrease_Values_triggered()3601 void MainWindow::on_actionCoarse_I_ncrease_Values_triggered()
3602 {
3603 	if (isEditedPattern_) ui->patternEditor->onChangeValuesPressed(true, true);
3604 }
3605 
on_action_Toggle_Bookmark_triggered()3606 void MainWindow::on_action_Toggle_Bookmark_triggered()
3607 {
3608 	bmManForm_->onBookmarkToggleRequested(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
3609 }
3610 
on_action_Next_Bookmark_triggered()3611 void MainWindow::on_action_Next_Bookmark_triggered()
3612 {
3613 	bmManForm_->onBookmarkJumpRequested(true, bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
3614 }
3615 
on_action_Previous_Bookmark_triggered()3616 void MainWindow::on_action_Previous_Bookmark_triggered()
3617 {
3618 	bmManForm_->onBookmarkJumpRequested(false, bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
3619 }
3620 
on_action_Instrument_Mask_triggered()3621 void MainWindow::on_action_Instrument_Mask_triggered()
3622 {
3623 	config_.lock()->setInstrumentMask(ui->action_Instrument_Mask->isChecked());
3624 }
3625 
on_action_Volume_Mask_triggered()3626 void MainWindow::on_action_Volume_Mask_triggered()
3627 {
3628 	config_.lock()->setVolumeMask(ui->action_Volume_Mask->isChecked());
3629 }
3630 
on_actionSet_Ro_w_Marker_triggered()3631 void MainWindow::on_actionSet_Ro_w_Marker_triggered()
3632 {
3633 	bt_->setMarker(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
3634 	ui->patternEditor->changeMarker();
3635 }
3636 
on_actionPlay_From_Marker_triggered()3637 void MainWindow::on_actionPlay_From_Marker_triggered()
3638 {
3639 	startPlayFromMarker();
3640 }
3641 
on_action_Go_To_triggered()3642 void MainWindow::on_action_Go_To_triggered()
3643 {
3644 	GoToDialog diag(bt_);
3645 	if (diag.exec() == QDialog::Accepted) {
3646 		if (!bt_->isPlaySong() || !bt_->isFollowPlay()) {
3647 			bt_->setCurrentOrderNumber(diag.getOrder());
3648 			bt_->setCurrentStepNumber(diag.getStep());
3649 			bt_->setCurrentTrack(diag.getTrack());
3650 			ui->orderList->updatePositionByPositionJump(true);
3651 			ui->patternEditor->updatepositionByPositionJump(true);
3652 		}
3653 	}
3654 }
3655 
on_actionRemove_Unused_ADPCM_Samples_triggered()3656 void MainWindow::on_actionRemove_Unused_ADPCM_Samples_triggered()
3657 {
3658 	if (showUndoResetWarningDialog(tr("Do you want to remove all unused ADPCM samples?"))) {
3659 		bt_->stopPlaySong();
3660 		lockWidgets(false);
3661 
3662 		bt_->clearUnusedADPCMSamples();
3663 		assignADPCMSamples();
3664 		bt_->clearCommandHistory();
3665 		comStack_->clear();
3666 		setModifiedTrue();
3667 	}
3668 }
3669 
on_action_Status_Bar_triggered()3670 void MainWindow::on_action_Status_Bar_triggered()
3671 {
3672 	ui->statusBar->setVisible(ui->action_Status_Bar->isChecked());
3673 }
3674 
on_action_Toolbar_triggered()3675 void MainWindow::on_action_Toolbar_triggered()
3676 {
3677 	bool visible = ui->action_Toolbar->isChecked();
3678 	ui->mainToolBar->setVisible(visible);
3679 	ui->subToolBar->setVisible(visible);
3680 }
3681 
on_actionNew_Drumki_t_triggered()3682 void MainWindow::on_actionNew_Drumki_t_triggered()
3683 {
3684 	addDrumkit();
3685 }
3686 
on_action_Wave_View_triggered(bool checked)3687 void MainWindow::on_action_Wave_View_triggered(bool checked)
3688 {
3689 	config_.lock()->setVisibleWaveView(checked);
3690 	ui->waveVisual->setVisible(checked);
3691 	if (checked)
3692 		visualTimer_->start(static_cast<int>(std::round(1000. / config_.lock()->getWaveViewFrameRate())));
3693 	else
3694 		visualTimer_->stop();
3695 }
3696 
on_action_Transpose_Song_triggered()3697 void MainWindow::on_action_Transpose_Song_triggered()
3698 {
3699 	TransposeSongDialog diag;
3700 	if (diag.exec() == QDialog::Accepted) {
3701 		if (showUndoResetWarningDialog(tr("Do you want to transpose a song?"))) {
3702 			bt_->stopPlaySong();
3703 			lockWidgets(false);
3704 
3705 			bt_->transposeSong(bt_->getCurrentSongNumber(),
3706 							   diag.getTransposeSeminotes(), diag.getExcludeInstruments());
3707 			ui->patternEditor->onPatternDataGlobalChanged();
3708 
3709 			bt_->clearCommandHistory();
3710 			comStack_->clear();
3711 			setModifiedTrue();
3712 		}
3713 	}
3714 }
3715 
on_action_Swap_Tracks_triggered()3716 void MainWindow::on_action_Swap_Tracks_triggered()
3717 {
3718 	SwapTracksDialog diag(bt_->getSongStyle(bt_->getCurrentSongNumber()));
3719 	if (diag.exec() == QDialog::Accepted) {
3720 		int track1 = diag.getTrack1();
3721 		int track2 = diag.getTrack2();
3722 		if ((track1 != track2) && showUndoResetWarningDialog(tr("Do you want to swap tracks?"))) {
3723 			bt_->stopPlaySong();
3724 			lockWidgets(false);
3725 
3726 			bt_->swapTracks(bt_->getCurrentSongNumber(), track1, track2);
3727 			ui->orderList->onOrderDataGlobalChanged();
3728 			ui->patternEditor->onPatternDataGlobalChanged();
3729 
3730 			bt_->clearCommandHistory();
3731 			comStack_->clear();
3732 			setModifiedTrue();
3733 		}
3734 	}
3735 }
3736 
on_action_Insert_triggered()3737 void MainWindow::on_action_Insert_triggered()
3738 {
3739 	if (isEditedPattern_) ui->patternEditor->onPasteInsertPressed();
3740 }
3741 
on_action_Hide_Tracks_triggered()3742 void MainWindow::on_action_Hide_Tracks_triggered()
3743 {
3744 	HideTracksDialog diag(bt_->getSongStyle(bt_->getCurrentSongNumber()),
3745 						  ui->patternEditor->getVisibleTracks());
3746 	if (diag.exec() == QDialog::Accepted) {
3747 		setTrackVisibility(diag.getVisibleTracks());
3748 	}
3749 }
3750 
on_action_Estimate_Song_Length_triggered()3751 void MainWindow::on_action_Estimate_Song_Length_triggered()
3752 {
3753 	double time = bt_->calculateSongLength(bt_->getCurrentSongNumber());
3754 	int seconds = static_cast<int>(std::round(time));
3755 	QMessageBox box;
3756 	box.setIcon(QMessageBox::Information);
3757 	box.setText(tr("Approximate song length: %1m%2s")
3758 				.arg(seconds / 60).arg(seconds % 60, 2, 10, QChar('0')));
3759 	box.exec();
3760 }
3761