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 "pattern_editor_panel.hpp"
27 #include <algorithm>
28 #include <vector>
29 #include <utility>
30 #include <stdexcept>
31 #include <thread>
32 #include <unordered_map>
33 #include <numeric>
34 #include <QPainter>
35 #include <QFontMetrics>
36 #include <QFontInfo>
37 #include <QPoint>
38 #include <QApplication>
39 #include <QClipboard>
40 #include <QMenu>
41 #include <QAction>
42 #include <QRegularExpression>
43 #include <QRegularExpressionMatch>
44 #include <QMetaMethod>
45 #include <QIcon>
46 #include "gui/event_guard.hpp"
47 #include "gui/command/pattern/pattern_commands_qt.hpp"
48 #include "midi/midi.hpp"
49 #include "jam_manager.hpp"
50 #include "gui/effect_description.hpp"
51 #include "gui/jam_layout.hpp"
52 #include "gui/gui_util.hpp"
53 
PatternEditorPanel(QWidget * parent)54 PatternEditorPanel::PatternEditorPanel(QWidget *parent)
55 	: QWidget(parent),
56 	  config_(std::make_shared<Configuration>()),	// Dummy
57 	  stepFontWidth_(0),
58 	  stepFontHeight_(0),
59 	  stepFontAscent_(0),
60 	  stepFontLeading_(0),
61 	  headerFontAscent_(0),
62 	  widthSpace_(0),
63 	  widthSpaceDbl_(0),
64 	  stepNumWidthCnt_(0),
65 	  stepNumWidth_(0),
66 	  stepNumBase_(0),
67 	  baseTrackWidth_(0),
68 	  toneNameWidth_(0),
69 	  instWidth_(0),
70 	  volWidth_(0),
71 	  effWidth_(0),
72 	  effIDWidth_(0),
73 	  effValWidth_(0),
74 	  tracksWidthFromLeftToEnd_(0),
75 	  hdMuteToggleWidth_(0),
76 	  hdEffCompandButtonWidth_(0),
77 	  headerHeight_(0),
78 	  hdPlusY_(0),
79 	  hdMinusY_(0),
80 	  curRowBaselineY_(0),
81 	  curRowY_(0),
82 	  visTracks_(1),	// Dummy
83 	  rightEffn_(16),	// Dummy
84 	  leftTrackVisIdx_(0),
85 	  curSongNum_(0),
86 	  curPos_{ 0, 0, 0, 0, },
87 	  hovPos_{ -1, -1, -1, -1 },
88 	  mousePressPos_{ -1, -1, -1, -1 },
89 	  mouseReleasePos_{ -1, -1, -1, -1 },
90 	  selLeftAbovePos_{ -1, -1, -1, -1 },
91 	  selRightBelowPos_{ -1, -1, -1, -1 },
92 	  shiftPressedPos_{ -1, -1, -1, -1 },
93 	  doubleClickPos_{ -1, -1, -1, -1 },
94 	  markerPos_{ -1, -1, -1, -1 },
95 	  isIgnoreToSlider_(false),
96 	  isIgnoreToOrder_(false),
97 	  isPressedPlus_(false),
98 	  isPressedMinus_(false),
99 	  entryCnt_(0),
100 	  selectAllState_(-1),
101 	  isMuteElse_(false),
102 	  hl1Cnt_(4),
103 	  hl2Cnt_(16),
104 	  editableStepCnt_(1),
105 	  viewedRowCnt_(1),
106 	  viewedRowsHeight_(0),
107 	  viewedRowOffset_(0),
108 	  viewedCenterY_(0),
109 	  viewedCenterBaseY_(0),
110 	  backChanged_(false),
111 	  textChanged_(false),
112 	  foreChanged_(false),
113 	  headerChanged_(false),
114 	  focusChanged_(false),
115 	  followModeChanged_(false),
116 	  hasFocussedBefore_(false),
117 	  stepDownCount_(0),
118 	  repaintable_(true),
119 	  repaintingCnt_(0),
120 	  isInitedFirstMod_(false),
121 	  upSc_(Qt::Key_Up, this, nullptr, nullptr, Qt::WidgetShortcut),
122 	  upWSSc_(Qt::SHIFT + Qt::Key_Up, this, nullptr, nullptr, Qt::WidgetShortcut),
123 	  dnSc_(Qt::Key_Down, this, nullptr, nullptr, Qt::WidgetShortcut),
124 	  dnWSSc_(Qt::SHIFT + Qt::Key_Down, this, nullptr, nullptr, Qt::WidgetShortcut),
125 	  pgUpSc_(Qt::Key_PageUp, this, nullptr, nullptr, Qt::WidgetShortcut),
126 	  pgUpWSSc_(Qt::SHIFT + Qt::Key_PageUp, this, nullptr, nullptr, Qt::WidgetShortcut),
127 	  pgDnSc_(Qt::Key_PageDown, this, nullptr, nullptr, Qt::WidgetShortcut),
128 	  pgDnWSSc_(Qt::SHIFT + Qt::Key_PageDown, this, nullptr, nullptr, Qt::WidgetShortcut),
129 	  homeSc_(Qt::Key_Home, this, nullptr, nullptr, Qt::WidgetShortcut),
130 	  homeWSSc_(Qt::SHIFT + Qt::Key_Home, this, nullptr, nullptr, Qt::WidgetShortcut),
131 	  endSc_(Qt::Key_End, this, nullptr, nullptr, Qt::WidgetShortcut),
132 	  endWSSc_(Qt::SHIFT + Qt::Key_End, this, nullptr, nullptr, Qt::WidgetShortcut),
133 	  hlUpSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut),
134 	  hlUpWSSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut),
135 	  hlDnSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut),
136 	  hlDnWSSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut),
137 	  ltSc_(Qt::Key_Left, this, nullptr, nullptr, Qt::WidgetShortcut),
138 	  ltWSSc_(Qt::SHIFT + Qt::Key_Left, this, nullptr, nullptr, Qt::WidgetShortcut),
139 	  rtSc_(Qt::Key_Right, this, nullptr, nullptr, Qt::WidgetShortcut),
140 	  rtWSSc_(Qt::SHIFT + Qt::Key_Right, this, nullptr, nullptr, Qt::WidgetShortcut),
141 	  keyOffSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut),
142 	  echoBufSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut),
143 	  stepMvUpSc_(Qt::ALT + Qt::Key_Up, this, nullptr, nullptr, Qt::WidgetShortcut),
144 	  stepMvDnSc_(Qt::ALT + Qt::Key_Down, this, nullptr, nullptr, Qt::WidgetShortcut),
145 	  expandColSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut),
146 	  shrinkColSc_(QKeySequence(), this, nullptr, nullptr, Qt::WidgetShortcut)
147 {
148 	setAttribute(Qt::WA_Hover);
149 	setFocusPolicy(Qt::ClickFocus);
150 	setContextMenuPolicy(Qt::CustomContextMenu);
151 
152 	// Initialize font
153 	headerFont_ = QApplication::font();
154 	headerFont_.setPointSize(10);
155 	stepFont_ = QFont("Monospace", 10);
156 	stepFont_.setStyleHint(QFont::TypeWriter);
157 	stepFont_.setStyleStrategy(QFont::ForceIntegerMetrics);
158 
159 	updateSizes();
160 
161 	// Track visibility
162 	songStyle_.type = SongType::Standard;	// Dummy
163 	songStyle_.trackAttribs.push_back({ 0, SoundSource::FM, 0 });	// Dummy
164 	std::iota(visTracks_.begin(), visTracks_.end(), 0);
165 
166 	// Shortcuts
__anon5de16e990102(bool isShift, auto getFunc) 167 	auto vMoveLam = [&] (bool isShift, auto getFunc) {	// For lazy evaluation
168 		if (bt_->isPlaySong() && bt_->isFollowPlay()) return;
169 		moveCursorToDown(getFunc());
170 		checkSelectionByCursorMove(isShift);
171 	};
__anon5de16e990202null172 	auto upLam = [&] { return (editableStepCnt_ ? -editableStepCnt_ : -1); };
__anon5de16e990302null173 	QObject::connect(&upSc_, &QShortcut::activated, this, [vMoveLam, upLam] { vMoveLam(false, upLam); });
__anon5de16e990402null174 	QObject::connect(&upWSSc_, &QShortcut::activated, this, [vMoveLam, upLam] { vMoveLam(true, upLam); });
__anon5de16e990502null175 	auto dnLam = [&] { return (editableStepCnt_ ? editableStepCnt_ : 1); };
__anon5de16e990602null176 	QObject::connect(&dnSc_, &QShortcut::activated, this, [vMoveLam, dnLam] { vMoveLam(false, dnLam); });
__anon5de16e990702null177 	QObject::connect(&dnWSSc_, &QShortcut::activated, this, [vMoveLam, dnLam] { vMoveLam(true, dnLam); });
__anon5de16e990802null178 	auto pgUpLam = [&] { return -static_cast<int>(config_->getPageJumpLength()); };
__anon5de16e990902null179 	QObject::connect(&pgUpSc_, &QShortcut::activated, this, [vMoveLam, pgUpLam] { vMoveLam(false, pgUpLam); });
__anon5de16e990a02null180 	QObject::connect(&pgUpWSSc_, &QShortcut::activated, this, [vMoveLam, pgUpLam] { vMoveLam(true, pgUpLam); });
__anon5de16e990b02null181 	auto pgDnLam = [&] { return static_cast<int>(config_->getPageJumpLength()); };
__anon5de16e990c02null182 	QObject::connect(&pgDnSc_, &QShortcut::activated, this, [vMoveLam, pgDnLam] { vMoveLam(false, pgDnLam); });
__anon5de16e990d02null183 	QObject::connect(&pgDnWSSc_, &QShortcut::activated, this, [vMoveLam, pgDnLam] { vMoveLam(true, pgDnLam); });
__anon5de16e990e02null184 	auto homeLam = [&] { return -curPos_.step; };
__anon5de16e990f02null185 	QObject::connect(&homeSc_, &QShortcut::activated, this, [vMoveLam, homeLam] { vMoveLam(false, homeLam); });
__anon5de16e991002null186 	QObject::connect(&homeWSSc_, &QShortcut::activated, this, [vMoveLam, homeLam] { vMoveLam(true, homeLam); });
__anon5de16e991102null187 	auto endLam = [&] {
188 		return (static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - curPos_.step - 1);
189 	};
__anon5de16e991202null190 	QObject::connect(&endSc_, &QShortcut::activated, this, [vMoveLam, endLam] { vMoveLam(false, endLam); });
__anon5de16e991302null191 	QObject::connect(&endWSSc_, &QShortcut::activated, this, [vMoveLam, endLam] { vMoveLam(true, endLam); });
__anon5de16e991402null192 	auto hlUpLam = [&] {
193 		int base;
194 		if (curPos_.step) {
195 			base = curPos_.step;
196 		}
197 		else {
198 			base = static_cast<int>(bt_->getPatternSizeFromOrderNumber(
199 										curSongNum_,
200 										(curPos_.order) ? (curPos_.order - 1)
201 														: (static_cast<int>(bt_->getOrderSize(curSongNum_)) - 1)));
202 		}
203 		return ((base - 1) / hl1Cnt_ * hl1Cnt_ - base);
204 	};
__anon5de16e991502null205 	QObject::connect(&hlUpSc_, &QShortcut::activated, this, [vMoveLam, hlUpLam] { vMoveLam(false, hlUpLam); });
__anon5de16e991602null206 	QObject::connect(&hlUpWSSc_, &QShortcut::activated, this, [vMoveLam, hlUpLam] { vMoveLam(true, hlUpLam); });
__anon5de16e991702null207 	auto hlDnLam = [&] {
208 		int next = std::min((curPos_.step / hl1Cnt_ + 1) * hl1Cnt_,
209 							static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)));
210 		return (next - curPos_.step);
211 	};
__anon5de16e991802null212 	QObject::connect(&hlDnSc_, &QShortcut::activated, this, [vMoveLam, hlDnLam] { vMoveLam(false, hlDnLam); });
__anon5de16e991902null213 	QObject::connect(&hlDnWSSc_, &QShortcut::activated, this, [vMoveLam, hlDnLam] { vMoveLam(true, hlDnLam); });
__anon5de16e991a02(bool isLeft, bool isShift) 214 	auto ltRtLam = [&] (bool isLeft, bool isShift) {
215 		moveCursorToRight(isLeft ? -1 : 1);
216 		checkSelectionByCursorMove(isShift);
217 	};
__anon5de16e991b02null218 	QObject::connect(&ltSc_, &QShortcut::activated, this, [ltRtLam] { ltRtLam(true, false); });
__anon5de16e991c02null219 	QObject::connect(&ltWSSc_, &QShortcut::activated, this, [ltRtLam] { ltRtLam(true, true); });
__anon5de16e991d02null220 	QObject::connect(&rtSc_, &QShortcut::activated, this, [ltRtLam] { ltRtLam(false, false); });
__anon5de16e991e02null221 	QObject::connect(&rtWSSc_, &QShortcut::activated, this, [ltRtLam] { ltRtLam(false, true); });
__anon5de16e991f02null222 	QObject::connect(&keyOffSc_, &QShortcut::activated, this, [&] {
223 		if (!bt_->isJamMode() && curPos_.colInTrack == 0) {
224 			bt_->setStepKeyOff(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step);
225 			comStack_.lock()->push(new SetKeyOffToStepQtCommand(this));
226 			if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_);
227 		}
228 	});
__anon5de16e992002null229 	QObject::connect(&echoBufSc_, &QShortcut::activated, this, [&] {
230 		if (!bt_->isJamMode() && curPos_.colInTrack == 0) {
231 			int n = bt_->getCurrentOctave();
232 			if (n > 3) n = 3;
233 			bt_->setEchoBufferAccess(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step, n);
234 			comStack_.lock()->push(new SetEchoBufferAccessQtCommand(this));
235 			if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_);
236 		}
237 	});
__anon5de16e992102null238 	QObject::connect(&stepMvUpSc_, &QShortcut::activated, this, [&] {
239 		if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !bt_->isJamMode())
240 			deletePreviousStep();
241 	});
__anon5de16e992202null242 	QObject::connect(&stepMvDnSc_, &QShortcut::activated, this, [&] {
243 		if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !bt_->isJamMode()) {
244 			insertStep();
245 			moveCursorToDown(1);
246 		}
247 	});
__anon5de16e992302null248 	QObject::connect(&expandColSc_, &QShortcut::activated, this, [&] {
249 		onExpandEffectColumnPressed(curPos_.trackVisIdx);
250 	});
__anon5de16e992402null251 	QObject::connect(&shrinkColSc_, &QShortcut::activated, this, [&] {
252 		onShrinkEffectColumnPressed(curPos_.trackVisIdx);
253 	});
254 	onShortcutUpdated();
255 
256 	// MIDI
257 	midiKeyEventMethod_ = metaObject()->indexOfSlot("midiKeyEvent(uchar,uchar,uchar)");
258 	Q_ASSERT(midiKeyEventMethod_ != -1);
259 	MidiInterface::instance().installInputHandler(&midiThreadReceivedEvent, this);
260 }
261 
~PatternEditorPanel()262 PatternEditorPanel::~PatternEditorPanel()
263 {
264 	MidiInterface::instance().uninstallInputHandler(&midiThreadReceivedEvent, this);
265 }
266 
funcResize()267 void PatternEditorPanel::funcResize()
268 {
269 	// Recalculate center row position
270 	curRowY_ = (geometry().height() + headerHeight_ - stepFontHeight_) >> 1;
271 	curRowBaselineY_ = curRowY_ + stepFontAscent_ - (stepFontLeading_ >> 1);
272 
273 	initDisplay();
274 }
275 
updateSizes()276 void PatternEditorPanel::updateSizes()
277 {
278 	QFontMetrics metrics(stepFont_);
279 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
280 	stepFontWidth_ = metrics.horizontalAdvance('0');
281 #else
282 	stepFontWidth_ = metrics.width('0');
283 #endif
284 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
285 	stepFontAscent_ = metrics.capHeight();
286 #else
287 	stepFontAscent_ = metrics.boundingRect('X').height();
288 #endif
289 	stepFontLeading_ = metrics.ascent() - stepFontAscent_ + metrics.descent() / 2;
290 	stepFontHeight_ = stepFontAscent_ + stepFontLeading_;
291 
292 	QFontMetrics m(headerFont_);
293 	headerFontAscent_ = m.ascent() + 2;
294 
295 	/* Width & height */
296 	widthSpace_ = stepFontWidth_ / 5 * 2;
297 	widthSpaceDbl_ = widthSpace_ * 2;
298 	stepNumWidthCnt_ = config_->getShowRowNumberInHex() ? 2 : 3;
299 	if (config_->getShowRowNumberInHex()) {
300 		stepNumWidthCnt_ = 2;
301 		stepNumBase_ = 16;
302 	}
303 	else {
304 		stepNumWidthCnt_ = 3;
305 		stepNumBase_ = 10;
306 	}
307 	stepNumWidth_ = stepFontWidth_ * stepNumWidthCnt_ + widthSpace_;
308 	toneNameWidth_ = stepFontWidth_ * 3;
309 	instWidth_ = stepFontWidth_ * 2;
310 	volWidth_ = stepFontWidth_ * 2;
311 	effIDWidth_ = stepFontWidth_ * 2;
312 	effValWidth_ = stepFontWidth_ * 2;
313 	effWidth_ = effIDWidth_ + effValWidth_ + widthSpaceDbl_;
314 	baseTrackWidth_ = toneNameWidth_ + instWidth_ + volWidth_
315 					  + effIDWidth_ + effValWidth_ + widthSpaceDbl_ * 4;
316 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
317 	hdEffCompandButtonWidth_ = m.horizontalAdvance("+");
318 #else
319 	hdEffCompandButtonWidth_ = m.width("+");
320 #endif
321 	hdMuteToggleWidth_ = baseTrackWidth_ - hdEffCompandButtonWidth_ - stepFontWidth_ / 2 * 3;
322 	headerHeight_ = m.height() * 2 + 1;
323 	hdPlusY_ = headerHeight_ / 4 + m.lineSpacing() / 2 - m.leading() / 2 - m.descent();
324 	hdMinusY_ = headerHeight_ / 2 + hdPlusY_;
325 
326 	initDisplay();
327 }
328 
initDisplay()329 void PatternEditorPanel::initDisplay()
330 {
331 	int width = geometry().width();
332 
333 	// Recalculate pixmap sizes
334 	viewedRegionHeight_ = std::max((geometry().height() - headerHeight_), stepFontHeight_);
335 	int cnt = viewedRegionHeight_ / stepFontHeight_;
336 	viewedRowCnt_ = (cnt % 2) ? (cnt + 2) : (cnt + 1);
337 	viewedRowsHeight_ = viewedRowCnt_ * stepFontHeight_;
338 
339 	viewedRowOffset_ = (viewedRowsHeight_ - viewedRegionHeight_) >> 1;
340 	viewedCenterY_ = (viewedRowsHeight_ - stepFontHeight_) >> 1;
341 	viewedCenterBaseY_ = viewedCenterY_ + stepFontAscent_ + (stepFontLeading_ >> 1);
342 
343 	completePixmap_ = QPixmap(geometry().size());
344 	backPixmap_ = QPixmap(width, viewedRowsHeight_);
345 	textPixmap_ = QPixmap(width, viewedRowsHeight_);
346 	forePixmap_ = QPixmap(width, viewedRowsHeight_);
347 	headerPixmap_ = QPixmap(width, headerHeight_);
348 }
349 
setCore(std::shared_ptr<BambooTracker> core)350 void PatternEditorPanel::setCore(std::shared_ptr<BambooTracker> core)
351 {
352 	bt_ = core;
353 }
354 
isReadyCore() const355 bool PatternEditorPanel::isReadyCore() const
356 {
357 	return (bt_ != nullptr);
358 }
359 
setCommandStack(std::weak_ptr<QUndoStack> stack)360 void PatternEditorPanel::setCommandStack(std::weak_ptr<QUndoStack> stack)
361 {
362 	comStack_ = stack;
363 }
364 
setConfiguration(std::shared_ptr<Configuration> config)365 void PatternEditorPanel::setConfiguration(std::shared_ptr<Configuration> config)
366 {
367 	config_ = config;
368 }
369 
setColorPallete(std::shared_ptr<ColorPalette> palette)370 void PatternEditorPanel::setColorPallete(std::shared_ptr<ColorPalette> palette)
371 {
372 	palette_ = palette;
373 }
374 
waitPaintFinish()375 void PatternEditorPanel::waitPaintFinish()
376 {
377 	while (true) {
378 		if (repaintingCnt_.load())
379 			std::this_thread::sleep_for(std::chrono::milliseconds(10));
380 		else {
381 			curPos_ = { 0, 0, 0, 0 };	// Init
382 			return;
383 		}
384 	}
385 }
386 
getHeaderFont() const387 QString PatternEditorPanel::getHeaderFont() const
388 {
389 	return QFontInfo(headerFont_).family();
390 }
391 
getHeaderFontSize() const392 int PatternEditorPanel::getHeaderFontSize() const
393 {
394 	return QFontInfo(headerFont_).pointSize();
395 }
396 
getRowsFont() const397 QString PatternEditorPanel::getRowsFont() const
398 {
399 	return QFontInfo(stepFont_).family();
400 }
401 
getRowsFontSize() const402 int PatternEditorPanel::getRowsFontSize() const
403 {
404 	return QFontInfo(stepFont_).pointSize();
405 }
406 
setFonts(QString headerFont,int headerSize,QString rowsFont,int rowsSize)407 void PatternEditorPanel::setFonts(QString headerFont, int headerSize, QString rowsFont, int rowsSize)
408 {
409 	headerFont_ = QFont(headerFont, headerSize);
410 	stepFont_ = QFont(rowsFont, rowsSize);
411 
412 	updateSizes();
413 	updateTracksWidthFromLeftToEnd();
414 
415 	redrawAll();
416 }
417 
setVisibleTracks(std::vector<int> tracks)418 void PatternEditorPanel::setVisibleTracks(std::vector<int> tracks)
419 {
420 	visTracks_ = tracks;
421 	rightEffn_.resize(visTracks_.size());
422 	std::transform(visTracks_.begin(), visTracks_.end(), rightEffn_.begin(), [&](int t) {
423 		return static_cast<int>(bt_->getEffectDisplayWidth(curSongNum_, t));
424 	});
425 	int max = static_cast<int>(tracks.size());
426 	bool cond = (max <= curPos_.trackVisIdx);
427 	if (cond) curPos_.trackVisIdx = max;
428 	leftTrackVisIdx_ = std::min(leftTrackVisIdx_, curPos_.trackVisIdx);
429 	updateTracksWidthFromLeftToEnd();
430 
431 	bt_->setCurrentTrack(visTracks_.at(curPos_.trackVisIdx));
432 	emit currentTrackChanged(curPos_.trackVisIdx);
433 
434 	if (cond) {
435 		if (config_->getMoveCursorByHorizontalScroll())
436 			emit hScrollBarChangeRequested(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack));
437 		else
438 			emit hScrollBarChangeRequested(leftTrackVisIdx_);
439 	}
440 
441 	redrawAll();
442 }
443 
getVisibleTracks() const444 std::vector<int> PatternEditorPanel::getVisibleTracks() const
445 {
446 	return visTracks_;
447 }
448 
redrawByPatternChanged(bool patternSizeChanged)449 void PatternEditorPanel::redrawByPatternChanged(bool patternSizeChanged)
450 {
451 	textChanged_ = true;
452 
453 	// When pattern size is changed, redraw all area
454 	if (patternSizeChanged) {
455 		backChanged_ = true;
456 		foreChanged_ = true;
457 	}
458 
459 	repaint();
460 }
461 
redrawByFocusChanged()462 void PatternEditorPanel::redrawByFocusChanged()
463 {
464 	if (hasFocussedBefore_) {
465 		focusChanged_ = true;
466 		repaint();
467 	}
468 	else {
469 		redrawAll();
470 		hasFocussedBefore_ = true;
471 	}
472 }
473 
redrawByHoverChanged()474 void PatternEditorPanel::redrawByHoverChanged()
475 {
476 	headerChanged_ = true;
477 	backChanged_ = true;
478 	repaint();
479 }
480 
redrawByMaskChanged()481 void PatternEditorPanel::redrawByMaskChanged()
482 {
483 	foreChanged_ = true;
484 	headerChanged_ = true;
485 	repaint();
486 }
487 
redrawPatterns()488 void PatternEditorPanel::redrawPatterns()
489 {
490 	backChanged_ = true;
491 	textChanged_ = true;
492 	foreChanged_ = true;
493 	repaint();
494 }
495 
redrawAll()496 void PatternEditorPanel::redrawAll()
497 {
498 	headerChanged_ = true;
499 	redrawPatterns();
500 }
501 
resetEntryCount()502 void PatternEditorPanel::resetEntryCount()
503 {
504 	entryCnt_ = 0;
505 }
506 
drawPattern(const QRect & rect)507 void PatternEditorPanel::drawPattern(const QRect &rect)
508 {
509 	if (repaintable_.load()) {
510 		repaintable_.store(false);
511 		++repaintingCnt_;	// Use module data after this line
512 
513 		if (rect.size() != completePixmap_.size()) {	// Prevent resize event was failed
514 			funcResize();
515 			headerChanged_ = true;
516 			backChanged_ = true;
517 			textChanged_ = true;
518 			foreChanged_ = true;
519 		}
520 
521 		if (backChanged_ || textChanged_ || foreChanged_ || headerChanged_ || focusChanged_ || stepDownCount_ || followModeChanged_) {
522 
523 			int maxWidth = std::min(rect.width(), tracksWidthFromLeftToEnd_);
524 			completePixmap_.fill(palette_->ptnBackColor);
525 
526 			if (!focusChanged_) {
527 				if (stepDownCount_ && !followModeChanged_) {
528 					quickDrawRows(maxWidth);
529 				}
530 				else {
531 					backPixmap_.fill(Qt::transparent);
532 					if (textChanged_) textPixmap_.fill(Qt::transparent);
533 					if (foreChanged_) forePixmap_.fill(Qt::transparent);
534 					drawRows(maxWidth);
535 				}
536 				drawBorders(maxWidth);
537 
538 				if (headerChanged_) {
539 					// headerPixmap_->fill(Qt::transparent);
540 					drawHeaders(maxWidth);
541 				}
542 			}
543 
544 			{
545 				QPainter mergePainter(&completePixmap_);
546 				QRect rowsRect(0, viewedRowOffset_, maxWidth, viewedRegionHeight_);
547 				QRect inViewRect(0, headerHeight_, maxWidth, viewedRegionHeight_);
548 				mergePainter.drawPixmap(inViewRect, backPixmap_, rowsRect);
549 				mergePainter.drawPixmap(inViewRect, textPixmap_, rowsRect);
550 				mergePainter.drawPixmap(inViewRect, forePixmap_, rowsRect);
551 				mergePainter.drawPixmap(headerPixmap_.rect(), headerPixmap_);
552 			}
553 
554 			if (!hasFocus()) drawShadow();
555 
556 			backChanged_ = false;
557 			textChanged_ = false;
558 			foreChanged_ = false;
559 			headerChanged_ = false;
560 			focusChanged_ = false;
561 			followModeChanged_ = false;
562 			stepDownCount_ = 0;
563 		}
564 
565 		--repaintingCnt_;	// Used module data until this line
566 		repaintable_.store(true);
567 	}
568 
569 	QPainter completePainter(this);
570 	completePainter.drawPixmap(rect, completePixmap_);
571 }
572 
drawRows(int maxWidth)573 void PatternEditorPanel::drawRows(int maxWidth)
574 {
575 	QPainter forePainter(&forePixmap_);
576 	QPainter textPainter(&textPixmap_);
577 	QPainter backPainter(&backPixmap_);
578 	textPainter.setFont(stepFont_);
579 
580 	/* Current row */
581 	// Fill row
582 	backPainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_,
583 						 bt_->isJamMode() ? palette_->ptnCurStepColor : palette_->ptnCurEditStepColor);
584 	// Step number
585 	if (markerPos_.isEqualRows(curPos_))
586 		backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor);	// Paint marker
587 	if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(curPos_))
588 		backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor);	// Paint hover
589 	if (textChanged_) {
590 		if (curPos_.step % hl2Cnt_) {
591 			textPainter.setPen(!(curPos_.step % hl2Cnt_) ? palette_->ptnHl1StepNumColor
592 														 : !(curPos_.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor
593 																					 : palette_->ptnDefStepNumColor);
594 		}
595 		else {
596 			textPainter.setPen(palette_->ptnHl2StepNumColor);
597 		}
598 		textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg(curPos_.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper());
599 	}
600 	// Step data
601 	for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) {
602 		x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, curPos_.order, curPos_.step, x, viewedCenterBaseY_, viewedCenterY_);
603 	}
604 	viewedCenterPos_ = curPos_;
605 
606 	int stepNum, odrNum;
607 	int rowY, baseY;
608 	int playOdrNum = bt_->getPlayingOrderNumber();
609 	int playStepNum = bt_->getPlayingStepNumber();
610 
611 	/* Previous rows */
612 	viewedFirstPos_ = curPos_;
613 	for (rowY = viewedCenterY_ - stepFontHeight_, baseY = viewedCenterBaseY_ - stepFontHeight_,
614 		 stepNum = curPos_.step - 1, odrNum = curPos_.order;
615 		 rowY >= 0;
616 		 rowY -= stepFontHeight_, baseY -= stepFontHeight_, --stepNum) {
617 		if (stepNum == -1) {
618 			if (odrNum == 0) {
619 				break;
620 			}
621 			else if (config_->getShowPreviousNextOrders()) {
622 				--odrNum;
623 				stepNum = static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, odrNum)) - 1;
624 			}
625 			else {
626 				break;
627 			}
628 		}
629 
630 		QColor rowColor;
631 		if (!config_->getFollowMode() && odrNum == playOdrNum && stepNum == playStepNum) {
632 			rowColor = palette_->ptnPlayStepColor;
633 		}
634 		else {
635 			rowColor = !(stepNum % hl2Cnt_) ? palette_->ptnHl2StepColor
636 											: !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepColor
637 																   : palette_->ptnDefStepColor;
638 		}
639 
640 		// Fill row
641 		backPainter.fillRect(0, rowY, maxWidth, stepFontHeight_, rowColor);
642 		// Step number
643 		if (markerPos_.isEqualRows(odrNum, stepNum))
644 			backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor);	// Paint marker
645 		if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(odrNum, stepNum))
646 			backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor);	// Paint hover
647 		if (textChanged_) {
648 			textPainter.setPen(!(stepNum % hl2Cnt_) ? palette_->ptnHl2StepNumColor
649 													: !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepNumColor
650 																		   : palette_->ptnDefStepNumColor);
651 			textPainter.drawText(1, baseY, QString("%1").arg(stepNum, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper());
652 		}
653 		// Step data
654 		for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) {
655 			x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, odrNum, stepNum, x, baseY, rowY);
656 		}
657 		if (foreChanged_) {
658 			if (odrNum != curPos_.order)	// Mask
659 				forePainter.fillRect(0, rowY, maxWidth, stepFontHeight_, palette_->ptnMaskColor);
660 		}
661 		viewedFirstPos_.setRows(odrNum, stepNum);
662 	}
663 
664 	int stepEnd = static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order));
665 
666 	/* Next rows */
667 	viewedLastPos_ = curPos_;
668 	for (rowY = viewedCenterY_ + stepFontHeight_, baseY = viewedCenterBaseY_ + stepFontHeight_,
669 		 stepNum = curPos_.step + 1, odrNum = curPos_.order;
670 		 rowY < viewedRowsHeight_;
671 		 rowY += stepFontHeight_, baseY += stepFontHeight_, ++stepNum) {
672 		if (stepNum == stepEnd) {
673 			if (odrNum == static_cast<int>(bt_->getOrderSize(curSongNum_)) - 1) {
674 				break;
675 			}
676 			else if (config_->getShowPreviousNextOrders()) {
677 				++odrNum;
678 				stepNum = 0;
679 				stepEnd = static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, odrNum));
680 			}
681 			else {
682 				break;
683 			}
684 		}
685 
686 		QColor rowColor;
687 		if (!config_->getFollowMode() && odrNum == playOdrNum && stepNum == playStepNum) {
688 			rowColor = palette_->ptnPlayStepColor;
689 		}
690 		else {
691 			rowColor = !(stepNum % hl2Cnt_) ? palette_->ptnHl2StepColor
692 											: !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepColor
693 																   : palette_->ptnDefStepColor;
694 		}
695 
696 		// Fill row
697 		backPainter.fillRect(0, rowY, maxWidth, stepFontHeight_, rowColor);
698 		// Step number
699 		if (markerPos_.isEqualRows(odrNum, stepNum))
700 			backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor);	// Paint marker
701 		if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(odrNum, stepNum))
702 			backPainter.fillRect(0, rowY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor);	// Paint hover
703 		if (textChanged_) {
704 			textPainter.setPen(!(stepNum % hl2Cnt_) ? palette_->ptnHl2StepNumColor
705 													: !(stepNum % hl1Cnt_) ? palette_->ptnHl1StepNumColor
706 																		   : palette_->ptnDefStepNumColor);
707 			textPainter.drawText(1, baseY, QString("%1").arg(stepNum, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper());
708 		}
709 		// Step data
710 		for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) {
711 			x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, odrNum, stepNum, x, baseY, rowY);
712 		}
713 		if (foreChanged_) {
714 			if (odrNum != curPos_.order)	// Mask
715 				forePainter.fillRect(0, rowY, maxWidth, stepFontHeight_, palette_->ptnMaskColor);
716 		}
717 		viewedLastPos_.setRows(odrNum, stepNum);
718 	}
719 }
720 
quickDrawRows(int maxWidth)721 void PatternEditorPanel::quickDrawRows(int maxWidth)
722 {
723 	int halfRowsCnt = viewedRowCnt_ >> 1;
724 	bool repaintForeAll = (curPos_.step - stepDownCount_ < 0);
725 	int shift = stepFontHeight_ * stepDownCount_;
726 
727 	/* Move up */
728 	QRect srcRect(0, 0, maxWidth, viewedRowsHeight_);
729 	if (!repaintForeAll) forePixmap_.scroll(0, -shift, srcRect);
730 	textPixmap_.scroll(0, -shift, srcRect);
731 	backPixmap_.scroll(0, -shift, srcRect);
732 	{
733 		PatternPosition fpos = calculatePositionFrom(viewedCenterPos_.order, viewedCenterPos_.step, stepDownCount_ - halfRowsCnt);
734 		if (fpos.order != -1) viewedFirstPos_ = std::move(fpos);
735 	}
736 
737 	QPainter forePainter(&forePixmap_);
738 	QPainter textPainter(&textPixmap_);
739 	QPainter backPainter(&backPixmap_);
740 	textPainter.setFont(stepFont_);
741 
742 	/* Clear previous cursor row, current cursor row and last rows text and foreground */
743 	int prevY = viewedCenterY_ - shift;
744 	int lastY = viewedRowsHeight_ - shift;
745 	textPainter.setCompositionMode(QPainter::CompositionMode_Source);
746 	textPainter.fillRect(0, prevY, maxWidth, stepFontHeight_, Qt::transparent);
747 	textPainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, Qt::transparent);
748 	textPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent);
749 	textPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
750 	if (!repaintForeAll) {
751 		forePainter.setCompositionMode(QPainter::CompositionMode_Source);
752 		forePainter.fillRect(0, prevY, maxWidth, stepFontHeight_, Qt::transparent);
753 		forePainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_, Qt::transparent);
754 		forePainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent);
755 		forePainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
756 	}
757 
758 	/* Redraw previous cursor step */
759 	{
760 		int baseY = viewedCenterBaseY_ - shift;
761 		QColor rowColor = !(viewedCenterPos_.step % hl2Cnt_) ? palette_->ptnHl2StepColor
762 															 : !(viewedCenterPos_.step % hl1Cnt_) ? palette_->ptnHl1StepColor
763 																								  : palette_->ptnDefStepColor;
764 		// Fill row
765 		backPainter.fillRect(0, prevY, maxWidth, stepFontHeight_, rowColor);
766 		// Step number
767 		if (markerPos_.isEqualRows(viewedCenterPos_))
768 			backPainter.fillRect(0, prevY, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor);	// Paint marker
769 		if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(viewedCenterPos_))
770 			backPainter.fillRect(0, prevY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor);	// Paint hover
771 		textPainter.setPen(!(viewedCenterPos_.step % hl2Cnt_) ? palette_->ptnHl2StepNumColor
772 															  : !(viewedCenterPos_.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor
773 																								   : palette_->ptnDefStepNumColor);
774 		textPainter.drawText(1, baseY, QString("%1").arg(viewedCenterPos_.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper());
775 		// Step data
776 		for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) {
777 			x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, viewedCenterPos_.order, viewedCenterPos_.step, x, baseY, prevY);
778 		}
779 	}
780 
781 	/* Redraw current cursor step */
782 	// Fill row
783 	backPainter.fillRect(0, viewedCenterY_, maxWidth, stepFontHeight_,
784 						 bt_->isJamMode() ? palette_->ptnCurStepColor : palette_->ptnCurEditStepColor);
785 	// Step number
786 	if (markerPos_.isEqualRows(curPos_))
787 		backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor);	// Paint marker
788 	if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(curPos_))
789 		backPainter.fillRect(0, viewedCenterY_, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor);	// Paint hover
790 	if (curPos_.step % hl2Cnt_) {
791 		textPainter.setPen(!(curPos_.step % hl2Cnt_) ? palette_->ptnHl1StepNumColor
792 													 : !(curPos_.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor
793 																				 : palette_->ptnDefStepNumColor);
794 	}
795 	else {
796 		textPainter.setPen(palette_->ptnHl2StepNumColor);
797 	}
798 	textPainter.drawText(1, viewedCenterBaseY_, QString("%1").arg(curPos_.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper());
799 	// Step data
800 	for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) {
801 		x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, curPos_.order, curPos_.step, x, viewedCenterBaseY_, viewedCenterY_);
802 	}
803 	viewedCenterPos_ = curPos_;
804 
805 	/* Draw new step at last if necessary */
806 	{
807 		PatternPosition bpos = calculatePositionFrom(viewedCenterPos_.order, viewedCenterPos_.step, halfRowsCnt);
808 		if (!config_->getShowPreviousNextOrders() && viewedCenterPos_.order != bpos.order) {
809 			// Clear row
810 			backPainter.setCompositionMode(QPainter::CompositionMode_Source);
811 			backPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent);
812 		}
813 		else {
814 			int baseY = lastY + (viewedCenterBaseY_ - viewedCenterY_);
815 			bpos = std::exchange(viewedLastPos_, bpos);
816 			while (true) {
817 				if (bpos.compareRows(viewedLastPos_) == 0) break;
818 				PatternPosition tmpBpos = calculatePositionFrom(bpos.order, bpos.step, 1);
819 				if (tmpBpos.order == -1) {	// when viewedLastPos_.row == -1 (viewedlastPos_.row < viewedCenterPos_.row + halRowsCnt)
820 					viewedLastPos_ = bpos;
821 					// Clear row
822 					backPainter.setCompositionMode(QPainter::CompositionMode_Source);
823 					backPainter.fillRect(0, lastY, maxWidth, shift, Qt::transparent);
824 					break;
825 				}
826 				else {
827 					bpos = tmpBpos;
828 				}
829 
830 				QColor rowColor = !(bpos.step % hl2Cnt_) ? palette_->ptnHl2StepColor
831 														 : !(bpos.step % hl1Cnt_) ? palette_->ptnHl1StepColor
832 																				  : palette_->ptnDefStepColor;
833 				// Fill row
834 				backPainter.fillRect(0, lastY, maxWidth, stepFontHeight_, rowColor);
835 				// Step number
836 				if (markerPos_.isEqualRows(bpos))
837 					backPainter.fillRect(0, lastY, stepNumWidth_, stepFontHeight_, palette_->ptnMarkerColor);	// Paint marker
838 				if (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(bpos))
839 					backPainter.fillRect(0, lastY, stepNumWidth_, stepFontHeight_, palette_->ptnHovCellColor);	// Paint hover
840 				textPainter.setPen(!(bpos.step % hl2Cnt_) ? palette_->ptnHl2StepNumColor
841 														  : !(bpos.step % hl1Cnt_) ? palette_->ptnHl1StepNumColor
842 																				   : palette_->ptnDefStepNumColor);
843 				textPainter.drawText(1, baseY, QString("%1").arg(bpos.step, stepNumWidthCnt_, stepNumBase_, QChar('0')).toUpper());
844 				// Step data
845 				for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) {
846 					x += drawStep(forePainter, textPainter, backPainter, trackVisIdx, bpos.order, bpos.step, x, baseY, lastY);
847 				}
848 				if (bpos.order != curPos_.order)	// Mask
849 					forePainter.fillRect(0, lastY, maxWidth, stepFontHeight_, palette_->ptnMaskColor);
850 
851 				baseY += stepFontHeight_;
852 				lastY += stepFontHeight_;
853 			}
854 		}
855 	}
856 
857 	/* Redraw foreground all area if new order */
858 	if (repaintForeAll) {
859 		forePixmap_.fill(Qt::transparent);
860 		int y = viewedCenterY_ - viewedCenterPos_.step * stepFontHeight_;
861 		if (y > 0) forePainter.fillRect(0, 0, maxWidth, y, palette_->ptnMaskColor);
862 		if (viewedLastPos_.order != viewedCenterPos_.order) {
863 			y += static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, viewedCenterPos_.order)) * stepFontHeight_;
864 			forePainter.fillRect(0, y, maxWidth, viewedRowsHeight_ - y, palette_->ptnMaskColor);
865 		}
866 		for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) {
867 			int w = baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast<size_t>(trackVisIdx));
868 			if (foreChanged_ && bt_->isMute(visTracks_.at(trackVisIdx)))	// Paint mute mask
869 				forePainter.fillRect(x, 0, w, viewedRowsHeight_, palette_->ptnMaskColor);
870 			x += w;
871 		}
872 	}
873 }
874 
drawStep(QPainter & forePainter,QPainter & textPainter,QPainter & backPainter,int trackVisIdx,int orderNum,int stepNum,int x,int baseY,int rowY)875 int PatternEditorPanel::drawStep(QPainter &forePainter, QPainter &textPainter, QPainter& backPainter, int trackVisIdx, int orderNum, int stepNum, int x, int baseY, int rowY)
876 {
877 	int trackNum = visTracks_.at(trackVisIdx);
878 	int offset = x + widthSpace_;
879 	PatternPosition pos{ trackVisIdx, 0, orderNum, stepNum };
880 	QColor textColor = curPos_.isEqualRows(orderNum, stepNum) ? palette_->ptnCurTextColor : palette_->ptnDefTextColor;
881 	bool isHovTrack = (hovPos_.order == -2 && hovPos_.trackVisIdx == trackVisIdx);
882 	bool isHovStep = (hovPos_.trackVisIdx == -2 && hovPos_.isEqualRows(orderNum, stepNum));
883 	SoundSource src = songStyle_.trackAttribs[static_cast<size_t>(trackNum)].source;
884 
885 
886 	/* Tone name */
887 	if (pos == curPos_)	// Paint current cell
888 		backPainter.fillRect(offset - widthSpace_, rowY, toneNameWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnCurCellColor);
889 	if (pos == hovPos_ || isHovTrack || isHovStep)	// Paint hover
890 		backPainter.fillRect(offset - widthSpace_, rowY, toneNameWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnHovCellColor);
891 	if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0)
892 			&& isSelectedCell(trackVisIdx, 0, orderNum, stepNum))	// Paint selected
893 		backPainter.fillRect(offset - widthSpace_, rowY, toneNameWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnSelCellColor);
894 	if (textChanged_) {
895 		int noteNum = bt_->getStepNoteNumber(curSongNum_, trackNum, orderNum, stepNum);
896 		switch (noteNum) {
897 		case -1:	// None
898 			textPainter.setPen(textColor);
899 			textPainter.drawText(offset, baseY, "---");
900 			break;
901 		case -2:	// Key off
902 			textPainter.fillRect(offset, rowY + stepFontHeight_ * 2 / 5,
903 								 toneNameWidth_, stepFontHeight_ / 5, palette_->ptnNoteColor);
904 			break;
905 		case -3:	// Echo 0
906 			textPainter.setPen(palette_->ptnNoteColor);
907 			textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^0");
908 			break;
909 		case -4:	// Echo 1
910 			textPainter.setPen(palette_->ptnNoteColor);
911 			textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^1");
912 			break;
913 		case -5:	// Echo 2
914 			textPainter.setPen(palette_->ptnNoteColor);
915 			textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^2");
916 			break;
917 		case -6:	// Echo 3
918 			textPainter.setPen(palette_->ptnNoteColor);
919 			textPainter.drawText(offset + stepFontWidth_ / 2, baseY, "^3");
920 			break;
921 		default:	// Convert tone name
922 		{
923 			QString toneStr;
924 			switch (noteNum % 12) {
925 			case 0:		toneStr = "C-";	break;
926 			case 1:		toneStr = "C#";	break;
927 			case 2:		toneStr = "D-";	break;
928 			case 3:		toneStr = "D#";	break;
929 			case 4:		toneStr = "E-";	break;
930 			case 5:		toneStr = "F-";	break;
931 			case 6:		toneStr = "F#";	break;
932 			case 7:		toneStr = "G-";	break;
933 			case 8:		toneStr = "G#";	break;
934 			case 9:		toneStr = "A-";	break;
935 			case 10:	toneStr = "A#";	break;
936 			case 11:	toneStr = "B-";	break;
937 			}
938 			textPainter.setPen(palette_->ptnNoteColor);
939 			textPainter.drawText(offset, baseY, toneStr + QString::number(noteNum / 12));
940 			break;
941 		}
942 		}
943 	}
944 	offset += toneNameWidth_ +  widthSpaceDbl_;
945 	pos.colInTrack = 1;
946 
947 	/* Instrument */
948 	if (pos == curPos_)	// Paint current cell
949 		backPainter.fillRect(offset - widthSpace_, rowY, instWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnCurCellColor);
950 	if (pos == hovPos_ || isHovTrack || isHovStep)	// Paint hover
951 		backPainter.fillRect(offset - widthSpace_, rowY, instWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnHovCellColor);
952 	if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0)
953 			&& isSelectedCell(trackVisIdx, 1, orderNum, stepNum))	// Paint selected
954 		backPainter.fillRect(offset - widthSpace_, rowY, instWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnSelCellColor);
955 	if (textChanged_) {
956 		int instNum = bt_->getStepInstrument(curSongNum_, trackNum, orderNum, stepNum);
957 		if (instNum == -1) {
958 			textPainter.setPen(textColor);
959 			textPainter.drawText(offset, baseY, "--");
960 		}
961 		else {
962 			std::unique_ptr<AbstractInstrument> inst = bt_->getInstrument(instNum);
963 			textPainter.setPen((inst != nullptr && src == inst->getSoundSource())
964 							   ? palette_->ptnInstColor
965 							   : palette_->ptnErrorColor);
966 			textPainter.drawText(offset, baseY, QString("%1").arg(instNum, 2, 16, QChar('0')).toUpper());
967 		}
968 	}
969 	offset += instWidth_ +  widthSpaceDbl_;
970 	pos.colInTrack = 2;
971 
972 	/* Volume */
973 	if (pos == curPos_)	// Paint current cell
974 		backPainter.fillRect(offset - widthSpace_, rowY, volWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnCurCellColor);
975 	if (pos == hovPos_ || isHovTrack || isHovStep)	// Paint hover
976 		backPainter.fillRect(offset - widthSpace_, rowY, volWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnHovCellColor);
977 	if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0)
978 			&& isSelectedCell(trackVisIdx, 2, orderNum, stepNum))	// Paint selected
979 		backPainter.fillRect(offset - widthSpace_, rowY, volWidth_ + widthSpaceDbl_, stepFontHeight_, palette_->ptnSelCellColor);
980 	if (textChanged_) {
981 		int vol = bt_->getStepVolume(curSongNum_, trackNum, orderNum, stepNum);
982 		if (vol == -1) {
983 			textPainter.setPen(textColor);
984 			textPainter.drawText(offset, baseY, "--");
985 		}
986 		else {
987 			int volLim = 0;	// Dummy set
988 			switch (src) {
989 			case SoundSource::FM:		volLim = 0x80;	break;
990 			case SoundSource::SSG:		volLim = 0x10;	break;
991 			case SoundSource::RHYTHM:		volLim = 0x20;	break;
992 			case SoundSource::ADPCM:	volLim = 0x100;	break;
993 			}
994 			textPainter.setPen((vol < volLim) ? palette_->ptnVolColor : palette_->ptnErrorColor);
995 			if (src == SoundSource::FM && vol < volLim && config_->getReverseFMVolumeOrder()) {
996 				vol = volLim - vol - 1;
997 			}
998 			textPainter.drawText(offset, baseY, QString("%1").arg(vol, 2, 16, QChar('0')).toUpper());
999 		}
1000 	}
1001 	offset += volWidth_ +  widthSpaceDbl_;
1002 	pos.colInTrack = 3;
1003 
1004 	/* Effect */
1005 	for (int i = 0; i <= rightEffn_.at(static_cast<size_t>(trackVisIdx)); ++i) {
1006 		/* Effect ID */
1007 		if (pos == curPos_)	// Paint current cell
1008 			backPainter.fillRect(offset - widthSpace_, rowY, effIDWidth_ + widthSpace_, stepFontHeight_, palette_->ptnCurCellColor);
1009 		if (pos == hovPos_ || isHovTrack || isHovStep)	// Paint hover
1010 			backPainter.fillRect(offset - widthSpace_, rowY, effIDWidth_ + widthSpace_, stepFontHeight_, palette_->ptnHovCellColor);
1011 		if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0)
1012 				&& isSelectedCell(trackVisIdx, pos.colInTrack, orderNum, stepNum))	// Paint selected
1013 			backPainter.fillRect(offset - widthSpace_, rowY, effIDWidth_ + widthSpace_, stepFontHeight_, palette_->ptnSelCellColor);
1014 		std::string effId;
1015 		QString effStr;
1016 		if (textChanged_) {
1017 			effId = bt_->getStepEffectID(curSongNum_, trackNum, orderNum, stepNum, i);
1018 			effStr = QString::fromStdString(effId);
1019 			if (effStr == "--") {
1020 				textPainter.setPen(textColor);
1021 				textPainter.drawText(offset, baseY, effStr);
1022 			}
1023 			else {
1024 				textPainter.setPen(palette_->ptnEffColor);
1025 				textPainter.drawText(offset, baseY, effStr);
1026 			}
1027 		}
1028 		offset += effIDWidth_;
1029 		++pos.colInTrack;
1030 
1031 		/* Effect Value */
1032 		if (pos == curPos_)	// Paint current cell
1033 			backPainter.fillRect(offset, rowY, effValWidth_ + widthSpace_, stepFontHeight_, palette_->ptnCurCellColor);
1034 		if (pos == hovPos_ || isHovTrack || isHovStep)	// Paint hover
1035 			backPainter.fillRect(offset, rowY, effValWidth_ + widthSpace_, stepFontHeight_, palette_->ptnHovCellColor);
1036 		if ((selLeftAbovePos_.trackVisIdx >= 0 && selLeftAbovePos_.order >= 0)
1037 				&& isSelectedCell(trackVisIdx, pos.colInTrack, orderNum, stepNum))	// Paint selected
1038 			backPainter.fillRect(offset, rowY, effValWidth_ + widthSpace_, stepFontHeight_, palette_->ptnSelCellColor);
1039 		if (textChanged_) {
1040 			int effVal = bt_->getStepEffectValue(curSongNum_, trackNum, orderNum, stepNum, i);
1041 			if (effVal == -1) {
1042 				textPainter.setPen(textColor);
1043 				textPainter.drawText(offset, baseY, "--");
1044 			}
1045 			else {
1046 				textPainter.setPen(palette_->ptnEffColor);
1047 				switch (Effect::toEffectType(src, effId)) {
1048 				case EffectType::VolumeDelay:
1049 					if (src == SoundSource::FM && config_->getReverseFMVolumeOrder() && effVal < 0x80)
1050 						effVal = 0x7f - effVal;
1051 					break;
1052 				case EffectType::Brightness:
1053 					if (config_->getReverseFMVolumeOrder() && effVal > 0)
1054 						effVal = 0xff - effVal + 1;
1055 					break;
1056 				default:
1057 					break;
1058 				}
1059 				textPainter.drawText(offset, baseY, QString("%1").arg(effVal, 2, 16, QChar('0')).toUpper());
1060 			}
1061 		}
1062 		offset += effValWidth_ + widthSpaceDbl_;
1063 		++pos.colInTrack;
1064 	}
1065 
1066 	if (foreChanged_ && bt_->isMute(trackNum))	// Paint mute mask
1067 		forePainter.fillRect(x, rowY, offset - x - 1, stepFontHeight_, palette_->ptnMaskColor);
1068 
1069 	return baseTrackWidth_ + effWidth_ * rightEffn_[static_cast<size_t>(trackVisIdx)];
1070 }
1071 
drawHeaders(int maxWidth)1072 void PatternEditorPanel::drawHeaders(int maxWidth)
1073 {
1074 	QPainter painter(&headerPixmap_);
1075 	painter.setFont(headerFont_);
1076 
1077 	painter.fillRect(0, 0, geometry().width(), headerHeight_, palette_->ptnHeaderRowColor);
1078 	painter.setPen(palette_->ptnHeaderBorderColor);
1079 	int bottomLineY = headerHeight_ - 1;
1080 	painter.drawLine(0, bottomLineY, geometry().width(), bottomLineY);
1081 	int lspace = stepFontWidth_ / 2;
1082 	for (int x = stepNumWidth_, trackVisIdx = leftTrackVisIdx_; x < maxWidth; ++trackVisIdx) {
1083 		painter.setPen(palette_->ptnHeaderBorderColor);
1084 		painter.drawLine(x, 0, x, headerHeight_);
1085 		int trackNum = visTracks_.at(trackVisIdx);
1086 		int tw = baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast<size_t>(trackVisIdx));
1087 		if (hovPos_.order == -2 && hovPos_.trackVisIdx == trackVisIdx)
1088 			painter.fillRect(x, 0, tw, headerHeight_, palette_->ptnHovCellColor);
1089 		int left = x + lspace;
1090 		painter.setPen(palette_->ptnHeaderTextColor);
1091 		const TrackAttribute& attrib = songStyle_.trackAttribs[static_cast<size_t>(trackNum)];
1092 		painter.drawText(left, headerFontAscent_,
1093 						 getTrackName(songStyle_.type, attrib.source, attrib.channelInSource));
1094 
1095 		painter.fillRect(left, headerHeight_ - 4, hdMuteToggleWidth_, 2,
1096 						 bt_->isMute(trackNum) ? palette_->ptnMuteColor : palette_->ptnUnmuteColor);
1097 
1098 		painter.drawText(left + hdMuteToggleWidth_ + lspace, hdPlusY_, "+");
1099 		painter.drawText(left + hdMuteToggleWidth_ + lspace, hdMinusY_, "-");
1100 
1101 		x += tw;
1102 	}
1103 }
1104 
drawBorders(int maxWidth)1105 void PatternEditorPanel::drawBorders(int maxWidth)
1106 {
1107 	QPainter painter(&backPixmap_);
1108 	painter.setPen(palette_->ptnBorderColor);
1109 	painter.drawLine(stepNumWidth_, 0, stepNumWidth_, backPixmap_.height());
1110 	size_t trackVisIdx = static_cast<size_t>(leftTrackVisIdx_);
1111 	for (int x = stepNumWidth_; trackVisIdx < rightEffn_.size(); ) {
1112 		x += (baseTrackWidth_ + effWidth_ * rightEffn_.at(trackVisIdx));
1113 		if (x > maxWidth) break;
1114 		painter.drawLine(x, 0, x, backPixmap_.height());
1115 		++trackVisIdx;
1116 	}
1117 }
1118 
drawShadow()1119 void PatternEditorPanel::drawShadow()
1120 {
1121 	QPainter painter(&completePixmap_);
1122 	painter.fillRect(0, 0, geometry().width(), geometry().height(), palette_->ptnUnfocusedShadowColor);
1123 }
1124 
1125 // NOTE: end >= -1
calculateTracksWidthWithRowNum(int beginIdx,int endIdx) const1126 int PatternEditorPanel::calculateTracksWidthWithRowNum(int beginIdx, int endIdx) const
1127 {
1128 	int width = stepNumWidth_;
1129 	for (int i = beginIdx; i <= endIdx; ++i) {
1130 		width += (baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast<size_t>(i)));
1131 	}
1132 	return width;
1133 }
1134 
calculateColNumInRow(int trackVisIdx,int colNumInTrack,bool isExpanded) const1135 int PatternEditorPanel::calculateColNumInRow(int trackVisIdx, int colNumInTrack, bool isExpanded) const
1136 {
1137 	if (isExpanded) {
1138 		return trackVisIdx * 11 + colNumInTrack;
1139 	}
1140 	else {
1141 		trackVisIdx = std::min(trackVisIdx, static_cast<int>(rightEffn_.size()));
1142 		return std::accumulate(rightEffn_.begin(), rightEffn_.begin() + trackVisIdx, colNumInTrack,
1143 							   [](int acc, int v) { return acc + 5 + 2 * v; });
1144 	}
1145 }
1146 
moveCursorToRight(int n)1147 void PatternEditorPanel::moveCursorToRight(int n)
1148 {
1149 	int oldTrackIdx = curPos_.trackVisIdx;
1150 	bool oldLeftTrackIdx = leftTrackVisIdx_;
1151 
1152 	curPos_.colInTrack += n;
1153 	if (n > 0) {
1154 		while (true) {
1155 			int lim = 5 + 2 * rightEffn_.at(static_cast<size_t>(curPos_.trackVisIdx));
1156 			if (curPos_.colInTrack < lim) {
1157 				break;
1158 			}
1159 			else {
1160 				if (curPos_.trackVisIdx == static_cast<int>(visTracks_.size()) - 1) {
1161 					if (config_->getWarpCursor()) {
1162 						curPos_.trackVisIdx = 0;
1163 					}
1164 					else {
1165 						curPos_.colInTrack = lim - 1;
1166 						break;
1167 					}
1168 				}
1169 				else {
1170 					++curPos_.trackVisIdx;
1171 				}
1172 				curPos_.colInTrack -= lim;
1173 			}
1174 		}
1175 	}
1176 	else {
1177 		while (true) {
1178 			if (curPos_.colInTrack >= 0) {
1179 				break;
1180 			}
1181 			else {
1182 				if (!curPos_.trackVisIdx) {
1183 					if (config_->getWarpCursor()) {
1184 						curPos_.trackVisIdx = static_cast<int>(visTracks_.size()) - 1;
1185 					}
1186 					else {
1187 						curPos_.colInTrack = 0;
1188 						break;
1189 					}
1190 				}
1191 				else {
1192 					--curPos_.trackVisIdx;
1193 				}
1194 				curPos_.colInTrack += (5 + 2 * rightEffn_.at(static_cast<size_t>(curPos_.trackVisIdx)));
1195 			}
1196 		}
1197 	}
1198 	if (oldTrackIdx < curPos_.trackVisIdx) {
1199 		while (calculateTracksWidthWithRowNum(leftTrackVisIdx_, curPos_.trackVisIdx) > geometry().width())
1200 			++leftTrackVisIdx_;
1201 	}
1202 	else {
1203 		if (curPos_.trackVisIdx < leftTrackVisIdx_) leftTrackVisIdx_ = curPos_.trackVisIdx;
1204 	}
1205 
1206 	updateTracksWidthFromLeftToEnd();
1207 	entryCnt_ = 0;
1208 
1209 	if (curPos_.trackVisIdx != oldTrackIdx)
1210 		bt_->setCurrentTrack(visTracks_.at(curPos_.trackVisIdx));
1211 
1212 	if (!isIgnoreToSlider_) {
1213 		if (config_->getMoveCursorByHorizontalScroll()) {
1214 			emit hScrollBarChangeRequested(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack));
1215 		}
1216 		else if (curPos_.trackVisIdx != oldTrackIdx) {
1217 			emit hScrollBarChangeRequested(leftTrackVisIdx_);
1218 		}
1219 	}
1220 
1221 	if (!isIgnoreToOrder_ && curPos_.trackVisIdx != oldTrackIdx)	// Send to order list
1222 		emit currentTrackChanged(curPos_.trackVisIdx);
1223 
1224 	// Request fore-background repaint if leftmost track is changed else request only background repaint
1225 	if (leftTrackVisIdx_ != oldLeftTrackIdx) {
1226 		headerChanged_ = true;
1227 		foreChanged_ = true;
1228 		textChanged_ = true;
1229 	}
1230 	backChanged_ = true;
1231 	repaint();
1232 }
1233 
moveViewToRight(int n)1234 void PatternEditorPanel::moveViewToRight(int n)
1235 {
1236 	leftTrackVisIdx_ += n;
1237 	updateTracksWidthFromLeftToEnd();
1238 
1239 	// Calculate cursor position
1240 	int trackIdx = curPos_.trackVisIdx + n;
1241 	int col = std::min(curPos_.colInTrack,
1242 					   4 + 2 * rightEffn_.at(static_cast<size_t>(trackIdx)));
1243 
1244 	// Check visible
1245 	int width = stepNumWidth_;
1246 	for (int i = leftTrackVisIdx_; i <= trackIdx; ++i) {
1247 		width += (baseTrackWidth_ + effWidth_ * rightEffn_.at(static_cast<size_t>(i)));
1248 		if (geometry().width() < width) {
1249 			trackIdx = i - 1;
1250 			col = 4 + 2 * rightEffn_.at(static_cast<size_t>(trackIdx));
1251 			break;
1252 		}
1253 	}
1254 
1255 	// Move cursor and repaint all
1256 	headerChanged_ = true;
1257 	foreChanged_ = true;
1258 	textChanged_ = true;
1259 	moveCursorToRight(calculateColumnDistance(curPos_.trackVisIdx, curPos_.colInTrack, trackIdx, col));
1260 }
1261 
moveCursorToDown(int n)1262 void PatternEditorPanel::moveCursorToDown(int n)
1263 {
1264 	int oldOdr = curPos_.order;
1265 	int tmp = curPos_.step + n;
1266 
1267 	if (n > 0) {
1268 		while (true) {
1269 			int dif = tmp - static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order));
1270 			if (dif < 0) {
1271 				curPos_.step = tmp;
1272 				break;
1273 			}
1274 			else {
1275 				if (config_->getWarpAcrossOrders()) {
1276 					if (curPos_.order == static_cast<int>(bt_->getOrderSize(curSongNum_)) - 1) {
1277 						curPos_.order = 0;
1278 					}
1279 					else {
1280 						++curPos_.order;
1281 					}
1282 				}
1283 				tmp = dif;
1284 			}
1285 		}
1286 	}
1287 	else {
1288 		while (true) {
1289 			if (tmp < 0) {
1290 				if (config_->getWarpAcrossOrders()) {
1291 					if (curPos_.order == 0) {
1292 						curPos_.order = static_cast<int>(bt_->getOrderSize(curSongNum_)) - 1;
1293 					}
1294 					else {
1295 						--curPos_.order;
1296 					}
1297 				}
1298 				tmp += bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order);
1299 			}
1300 			else {
1301 				curPos_.step = tmp;
1302 				break;
1303 			}
1304 		}
1305 	}
1306 
1307 	if (curPos_.order != oldOdr)
1308 		bt_->setCurrentOrderNumber(curPos_.order);
1309 	bt_->setCurrentStepNumber(curPos_.step);
1310 
1311 	entryCnt_ = 0;
1312 
1313 	if (!isIgnoreToSlider_)
1314 		emit vScrollBarChangeRequested(
1315 				curPos_.step, static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1);
1316 
1317 	if (!isIgnoreToOrder_ && curPos_.order != oldOdr)	// Send to order list
1318 		emit currentOrderChanged(
1319 				curPos_.order, static_cast<int>(bt_->getOrderSize(curSongNum_)) - 1);
1320 
1321 	backChanged_ = true;
1322 	textChanged_ = true;
1323 	foreChanged_ = true;
1324 	repaint();
1325 }
1326 
calculateColumnDistance(int beginTrackIdx,int beginColumn,int endTrackIdx,int endColumn,bool isExpanded) const1327 int PatternEditorPanel::calculateColumnDistance(int beginTrackIdx, int beginColumn, int endTrackIdx, int endColumn, bool isExpanded) const
1328 {
1329 	return (calculateColNumInRow(endTrackIdx, endColumn, isExpanded)
1330 			- calculateColNumInRow(beginTrackIdx, beginColumn, isExpanded));
1331 }
1332 
calculateStepDistance(int beginOrder,int beginStep,int endOrder,int endStep) const1333 int PatternEditorPanel::calculateStepDistance(int beginOrder, int beginStep, int endOrder, int endStep) const
1334 {
1335 	int d = 0;
1336 	int startOrder, startStep, stopOrder, stopStep;
1337 	bool flag;
1338 
1339 	if (endOrder >= beginOrder) {
1340 		startOrder = endOrder;
1341 		startStep = endStep;
1342 		stopOrder = beginOrder;
1343 		stopStep = beginStep;
1344 		flag = true;
1345 	}
1346 	else {
1347 		startOrder = beginOrder;
1348 		startStep = beginStep;
1349 		stopOrder = endOrder;
1350 		stopStep = endStep;
1351 		flag = false;
1352 	}
1353 
1354 	while (true) {
1355 		if (startOrder == stopOrder) {
1356 			d += (startStep - stopStep);
1357 			break;
1358 		}
1359 		else {
1360 			d += startStep;
1361 			startStep = static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, --startOrder));
1362 		}
1363 	}
1364 
1365 	return flag ? d : -d;
1366 }
1367 
calculatePositionFrom(int order,int step,int by) const1368 PatternPosition PatternEditorPanel::calculatePositionFrom(int order, int step, int by) const
1369 {
1370 	PatternPosition pos{ -1, -1, order, step + by };
1371 
1372 	if (by > 0) {
1373 		while (true) {
1374 			int dif = pos.step - static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, pos.order));
1375 			if (dif < 0) {
1376 				break;
1377 			}
1378 			else {
1379 				if (pos.order == static_cast<int>(bt_->getOrderSize(curSongNum_)) - 1) {
1380 					return { -1, -1, -1, -1 };
1381 				}
1382 				else {
1383 					++pos.order;
1384 				}
1385 				pos.step = dif;
1386 			}
1387 		}
1388 	}
1389 	else {
1390 		while (true) {
1391 			if (pos.step < 0) {
1392 				if (pos.order == 0) {
1393 					return { -1, -1, -1, -1 };
1394 				}
1395 				else {
1396 					--pos.order;
1397 				}
1398 				pos.step += bt_->getPatternSizeFromOrderNumber(curSongNum_, pos.order);
1399 			}
1400 			else {
1401 				break;
1402 			}
1403 		}
1404 	}
1405 	return pos;
1406 }
1407 
calculateCurrentCursorPosition() const1408 QPoint PatternEditorPanel::calculateCurrentCursorPosition() const
1409 {
1410 	int w = calculateTracksWidthWithRowNum(leftTrackVisIdx_, curPos_.trackVisIdx - 1);
1411 	if (curPos_.colInTrack > 0) {
1412 		w = w + toneNameWidth_ + widthSpaceDbl_;
1413 		if (curPos_.colInTrack > 1) {
1414 			w = w + instWidth_ + widthSpaceDbl_;
1415 			if (curPos_.colInTrack > 2) {
1416 				w = w + volWidth_ + widthSpaceDbl_;
1417 				for (int i = 3; i < 11; ++i) {
1418 					if (curPos_.colInTrack == i) break;
1419 					w = w + widthSpace_ + ((i % 2) ? effIDWidth_ : effValWidth_);
1420 				}
1421 			}
1422 		}
1423 	}
1424 	return QPoint(w, curRowY_);
1425 }
1426 
getScrollableCountByTrack() const1427 int PatternEditorPanel::getScrollableCountByTrack() const
1428 {
1429 	int width = stepNumWidth_;
1430 	size_t i = visTracks_.size();
1431 	do {
1432 		--i;
1433 		width += (baseTrackWidth_ + effWidth_ * rightEffn_.at(i));
1434 		if (geometry().width() < width) {
1435 			return static_cast<int>(i + 1);
1436 		}
1437 	} while (i);
1438 	return 0;
1439 }
1440 
changeEditable()1441 void PatternEditorPanel::changeEditable()
1442 {
1443 	backChanged_ = true;
1444 	repaint();
1445 }
1446 
getFullColmunSize() const1447 int PatternEditorPanel::getFullColmunSize() const
1448 {
1449 	return calculateColNumInRow(static_cast<int>(visTracks_.size()) - 1, 4 + 2 * rightEffn_.back());
1450 }
1451 
updatePositionByStepUpdate(bool isFirstUpdate,bool forceJump,bool trackChanged)1452 void PatternEditorPanel::updatePositionByStepUpdate(bool isFirstUpdate, bool forceJump, bool trackChanged)
1453 {
1454 	if (!forceJump && !config_->getFollowMode()) {	// Repaint only background
1455 		backChanged_ = true;
1456 		repaint();
1457 		return;
1458 	}
1459 
1460 	if (trackChanged) {
1461 		int trackVisIdx = std::distance(visTracks_.begin(), std::find(visTracks_.begin(), visTracks_.end(), bt_->getCurrentTrackAttribute().number));
1462 		int oldTrackVisIdx = std::exchange(curPos_.trackVisIdx, trackVisIdx);
1463 		curPos_.colInTrack = 0;
1464 		if (oldTrackVisIdx < curPos_.trackVisIdx) {
1465 			while (calculateTracksWidthWithRowNum(leftTrackVisIdx_, curPos_.trackVisIdx) > geometry().width()) {
1466 				++leftTrackVisIdx_;
1467 				headerChanged_ = true;
1468 			}
1469 		}
1470 		else {
1471 			if (curPos_.trackVisIdx < leftTrackVisIdx_) {
1472 				leftTrackVisIdx_ = curPos_.trackVisIdx;
1473 				headerChanged_ = true;
1474 			}
1475 		}
1476 
1477 		updateTracksWidthFromLeftToEnd();
1478 
1479 		if (!isIgnoreToSlider_) {
1480 			if (config_->getMoveCursorByHorizontalScroll())
1481 				emit hScrollBarChangeRequested(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack));
1482 			else
1483 				emit hScrollBarChangeRequested(leftTrackVisIdx_);
1484 		}
1485 	}
1486 
1487 	PatternPosition tmp = curPos_;
1488 	curPos_.setRows(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
1489 	int cmp = curPos_.compareRows(tmp);
1490 	if (cmp || isFirstUpdate) {
1491 		emit vScrollBarChangeRequested(
1492 					curPos_.step, static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1);
1493 
1494 		if (isFirstUpdate || (cmp < 0) || (cmp && !config_->getShowPreviousNextOrders())) {
1495 			stepDownCount_ = 0;	// Redraw entire area in first update
1496 		}
1497 		else {
1498 			int d = calculateStepDistance(tmp.order, tmp.step, curPos_.order, curPos_.step);
1499 			stepDownCount_ = (d < (viewedRowCnt_ >> 1)) ? d : 0;
1500 		}
1501 	}
1502 	else if (!trackChanged) return;	// Delayed call, already updated.
1503 
1504 	entryCnt_ = 0;
1505 
1506 	// If stepChanged is false, repaint all pattern
1507 	foreChanged_ = true;
1508 	textChanged_ = true;
1509 	backChanged_ = true;
1510 	repaint();
1511 }
1512 
changeMarker()1513 void PatternEditorPanel::changeMarker()
1514 {
1515 	markerPos_.setRows(bt_->getMarkerOrder(), bt_->getMarkerStep());
1516 	backChanged_ = true;
1517 	repaint();
1518 }
1519 
enterToneData(QKeyEvent * event)1520 bool PatternEditorPanel::enterToneData(QKeyEvent* event)
1521 {
1522 	int baseOct = bt_->getCurrentOctave();
1523 
1524 	if (event->modifiers().testFlag(Qt::NoModifier)) {
1525 		Qt::Key qtKey = static_cast<Qt::Key>(event->key());
1526 		try {
1527 			JamKey possibleJamKey = getJamKeyFromLayoutMapping(qtKey, config_);
1528 			int octaveOffset = 0;
1529 			switch (possibleJamKey) {
1530 			case JamKey::HighD2:
1531 			case JamKey::HighCS2:
1532 			case JamKey::HighC2:
1533 				octaveOffset = 2;
1534 				break;
1535 			case JamKey::HighB:
1536 			case JamKey::HighAS:
1537 			case JamKey::HighA:
1538 			case JamKey::HighGS:
1539 			case JamKey::HighG:
1540 			case JamKey::HighFS:
1541 			case JamKey::HighF:
1542 			case JamKey::HighE:
1543 			case JamKey::HighDS:
1544 			case JamKey::HighD:
1545 			case JamKey::HighCS:
1546 			case JamKey::HighC:
1547 			case JamKey::LowD2:
1548 			case JamKey::LowCS2:
1549 			case JamKey::LowC2:
1550 				octaveOffset = 1;
1551 				break;
1552 			default:
1553 				break;
1554 			}
1555 			setStepKeyOn(JamManager::jamKeyToNote(possibleJamKey), baseOct + octaveOffset);
1556 		} catch (std::invalid_argument &) {}
1557 	}
1558 
1559 	return false;
1560 }
1561 
setStepKeyOn(Note note,int octave)1562 void PatternEditorPanel::setStepKeyOn(Note note, int octave)
1563 {
1564 	if (octave < 8) {
1565 		bt_->setStepNote(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step, octave, note,
1566 						 config_->getInstrumentMask(), config_->getVolumeMask());
1567 		comStack_.lock()->push(new SetKeyOnToStepQtCommand(this));
1568 		if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_);
1569 	}
1570 }
1571 
enterInstrumentData(int key)1572 bool PatternEditorPanel::enterInstrumentData(int key)
1573 {
1574 	switch (key) {
1575 	case Qt::Key_0:	setStepInstrument(0x0);	return true;
1576 	case Qt::Key_1:	setStepInstrument(0x1);	return true;
1577 	case Qt::Key_2:	setStepInstrument(0x2);	return true;
1578 	case Qt::Key_3:	setStepInstrument(0x3);	return true;
1579 	case Qt::Key_4:	setStepInstrument(0x4);	return true;
1580 	case Qt::Key_5:	setStepInstrument(0x5);	return true;
1581 	case Qt::Key_6:	setStepInstrument(0x6);	return true;
1582 	case Qt::Key_7:	setStepInstrument(0x7);	return true;
1583 	case Qt::Key_8:	setStepInstrument(0x8);	return true;
1584 	case Qt::Key_9:	setStepInstrument(0x9);	return true;
1585 	case Qt::Key_A:	setStepInstrument(0xa);	return true;
1586 	case Qt::Key_B:	setStepInstrument(0xb);	return true;
1587 	case Qt::Key_C:	setStepInstrument(0xc);	return true;
1588 	case Qt::Key_D:	setStepInstrument(0xd);	return true;
1589 	case Qt::Key_E:	setStepInstrument(0xe);	return true;
1590 	case Qt::Key_F:	setStepInstrument(0xf);	return true;
1591 	default:	return false;
1592 	}
1593 }
1594 
setStepInstrument(int num)1595 void PatternEditorPanel::setStepInstrument(int num)
1596 {
1597 	int trackNum = visTracks_.at(curPos_.trackVisIdx);
1598 	bt_->setStepInstrumentDigit(curSongNum_, trackNum, curPos_.order, curPos_.step, num, (entryCnt_ == 1));
1599 	comStack_.lock()->push(new SetInstrumentToStepQtCommand(this, curPos_, (entryCnt_ == 1)));
1600 
1601 	emit instrumentEntered(
1602 				bt_->getStepInstrument(curSongNum_, trackNum, curPos_.order, curPos_.step));
1603 
1604 	if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount())
1605 		moveCursorToDown(editableStepCnt_);
1606 }
1607 
enterVolumeData(int key)1608 bool PatternEditorPanel::enterVolumeData(int key)
1609 {
1610 	switch (key) {
1611 	case Qt::Key_0:	setStepVolume(0x0);	return true;
1612 	case Qt::Key_1:	setStepVolume(0x1);	return true;
1613 	case Qt::Key_2:	setStepVolume(0x2);	return true;
1614 	case Qt::Key_3:	setStepVolume(0x3);	return true;
1615 	case Qt::Key_4:	setStepVolume(0x4);	return true;
1616 	case Qt::Key_5:	setStepVolume(0x5);	return true;
1617 	case Qt::Key_6:	setStepVolume(0x6);	return true;
1618 	case Qt::Key_7:	setStepVolume(0x7);	return true;
1619 	case Qt::Key_8:	setStepVolume(0x8);	return true;
1620 	case Qt::Key_9:	setStepVolume(0x9);	return true;
1621 	case Qt::Key_A:	setStepVolume(0xa);	return true;
1622 	case Qt::Key_B:	setStepVolume(0xb);	return true;
1623 	case Qt::Key_C:	setStepVolume(0xc);	return true;
1624 	case Qt::Key_D:	setStepVolume(0xd);	return true;
1625 	case Qt::Key_E:	setStepVolume(0xe);	return true;
1626 	case Qt::Key_F:	setStepVolume(0xf);	return true;
1627 	default:	return false;
1628 	}
1629 }
1630 
setStepVolume(int volume)1631 void PatternEditorPanel::setStepVolume(int volume)
1632 {
1633 	int vol = bt_->setStepVolumeDigit(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step, volume, (entryCnt_ == 1));
1634 	comStack_.lock()->push(new SetVolumeToStepQtCommand(this, curPos_, (entryCnt_ == 1)));
1635 
1636 	if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount())
1637 		moveCursorToDown(editableStepCnt_);
1638 
1639 	emit volumeEntered(vol);
1640 }
1641 
enterEffectID(int key)1642 bool PatternEditorPanel::enterEffectID(int key)
1643 {
1644 	switch (key) {
1645 	case Qt::Key_0:			setStepEffectID("0");	return true;
1646 	case Qt::Key_1:			setStepEffectID("1");	return true;
1647 	case Qt::Key_2:			setStepEffectID("2");	return true;
1648 	case Qt::Key_3:			setStepEffectID("3");	return true;
1649 	case Qt::Key_4:			setStepEffectID("4");	return true;
1650 	case Qt::Key_5:			setStepEffectID("5");	return true;
1651 	case Qt::Key_6:			setStepEffectID("6");	return true;
1652 	case Qt::Key_7:			setStepEffectID("7");	return true;
1653 	case Qt::Key_8:			setStepEffectID("8");	return true;
1654 	case Qt::Key_9:			setStepEffectID("9");	return true;
1655 	case Qt::Key_A:			setStepEffectID("A");	return true;
1656 	case Qt::Key_B:			setStepEffectID("B");	return true;
1657 	case Qt::Key_C:			setStepEffectID("C");	return true;
1658 	case Qt::Key_D:			setStepEffectID("D");	return true;
1659 	case Qt::Key_E:			setStepEffectID("E");	return true;
1660 	case Qt::Key_F:			setStepEffectID("F");	return true;
1661 	case Qt::Key_G:			setStepEffectID("G");	return true;
1662 	case Qt::Key_H:			setStepEffectID("H");	return true;
1663 	case Qt::Key_I:			setStepEffectID("I");	return true;
1664 	case Qt::Key_J:			setStepEffectID("J");	return true;
1665 	case Qt::Key_K:			setStepEffectID("K");	return true;
1666 	case Qt::Key_L:			setStepEffectID("L");	return true;
1667 	case Qt::Key_M:			setStepEffectID("M");	return true;
1668 	case Qt::Key_N:			setStepEffectID("N");	return true;
1669 	case Qt::Key_O:			setStepEffectID("O");	return true;
1670 	case Qt::Key_P:			setStepEffectID("P");	return true;
1671 	case Qt::Key_Q:			setStepEffectID("Q");	return true;
1672 	case Qt::Key_R:			setStepEffectID("R");	return true;
1673 	case Qt::Key_S:			setStepEffectID("S");	return true;
1674 	case Qt::Key_T:			setStepEffectID("T");	return true;
1675 	case Qt::Key_U:			setStepEffectID("U");	return true;
1676 	case Qt::Key_V:			setStepEffectID("V");	return true;
1677 	case Qt::Key_W:			setStepEffectID("W");	return true;
1678 	case Qt::Key_X:			setStepEffectID("X");	return true;
1679 	case Qt::Key_Y:			setStepEffectID("Y");	return true;
1680 	case Qt::Key_Z:			setStepEffectID("Z");	return true;
1681 	default:										return false;
1682 	}
1683 }
1684 
setStepEffectID(QString str)1685 void PatternEditorPanel::setStepEffectID(QString str)
1686 {
1687 	int curTrackNum = visTracks_.at(curPos_.trackVisIdx);
1688 	bt_->setStepEffectIDCharacter(curSongNum_, curTrackNum, curPos_.order, curPos_.step,
1689 								  (curPos_.colInTrack - 3) / 2, str.toStdString(),
1690 								  config_->getFill00ToEffectValue(), (entryCnt_ == 1));
1691 	comStack_.lock()->push(new SetEffectIDToStepQtCommand(this, curPos_, (entryCnt_ == 1)));
1692 
1693 	PatternPosition editPos = curPos_;
1694 
1695 	if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) {
1696 		if (config_->getMoveCursorToRight()) moveCursorToRight(1);
1697 		else moveCursorToDown(editableStepCnt_);
1698 	}
1699 
1700 	// Send effect description
1701 	std::string id = bt_->getStepEffectID(curSongNum_, visTracks_.at(editPos.trackVisIdx), editPos.order,
1702 										  editPos.step, (editPos.colInTrack - 3) / 2);
1703 	SoundSource src = songStyle_.trackAttribs.at(static_cast<size_t>(curTrackNum)).source;
1704 	emit effectEntered(EffectDescription::getEffectFormatAndDetailString(Effect::toEffectType(src, id)));
1705 }
1706 
enterEffectValue(int key)1707 bool PatternEditorPanel::enterEffectValue(int key)
1708 {
1709 	switch (key) {
1710 	case Qt::Key_0:	setStepEffectValue(0x0);	return true;
1711 	case Qt::Key_1:	setStepEffectValue(0x1);	return true;
1712 	case Qt::Key_2:	setStepEffectValue(0x2);	return true;
1713 	case Qt::Key_3:	setStepEffectValue(0x3);	return true;
1714 	case Qt::Key_4:	setStepEffectValue(0x4);	return true;
1715 	case Qt::Key_5:	setStepEffectValue(0x5);	return true;
1716 	case Qt::Key_6:	setStepEffectValue(0x6);	return true;
1717 	case Qt::Key_7:	setStepEffectValue(0x7);	return true;
1718 	case Qt::Key_8:	setStepEffectValue(0x8);	return true;
1719 	case Qt::Key_9:	setStepEffectValue(0x9);	return true;
1720 	case Qt::Key_A:	setStepEffectValue(0xa);	return true;
1721 	case Qt::Key_B:	setStepEffectValue(0xb);	return true;
1722 	case Qt::Key_C:	setStepEffectValue(0xc);	return true;
1723 	case Qt::Key_D:	setStepEffectValue(0xd);	return true;
1724 	case Qt::Key_E:	setStepEffectValue(0xe);	return true;
1725 	case Qt::Key_F:	setStepEffectValue(0xf);	return true;
1726 	default:	return false;
1727 	}
1728 }
1729 
setStepEffectValue(int value)1730 void PatternEditorPanel::setStepEffectValue(int value)
1731 {
1732 	int trackNum = visTracks_.at(curPos_.trackVisIdx);
1733 	int n = (curPos_.colInTrack - 4) / 2;
1734 	EffectDisplayControl ctrl = EffectDisplayControl::Unset;
1735 	SoundSource src = songStyle_.trackAttribs[static_cast<size_t>(trackNum)].source;
1736 	switch (Effect::toEffectType(
1737 				src,
1738 				bt_->getStepEffectID(curSongNum_, trackNum, curPos_.order, curPos_.step, n))) {
1739 	case EffectType::VolumeDelay:
1740 		if (src == SoundSource::FM && config_->getReverseFMVolumeOrder())
1741 			ctrl = EffectDisplayControl::ReverseFMVolumeDelay;
1742 		break;
1743 	case EffectType::Brightness:
1744 		if (config_->getReverseFMVolumeOrder()) ctrl = EffectDisplayControl::ReverseFMBrightness;
1745 		break;
1746 	default:
1747 		break;
1748 	}
1749 	bt_->setStepEffectValueDigit(curSongNum_, trackNum, curPos_.order, curPos_.step, n, value, ctrl, (entryCnt_ == 1));
1750 	comStack_.lock()->push(new SetEffectValueToStepQtCommand(this, curPos_, (entryCnt_ == 1)));
1751 
1752 	if ((!bt_->isPlaySong() || !bt_->isFollowPlay()) && !updateEntryCount()) {
1753 		if (config_->getMoveCursorToRight()) moveCursorToRight(1);
1754 		else moveCursorToDown(editableStepCnt_);
1755 	}
1756 }
1757 
insertStep()1758 void PatternEditorPanel::insertStep()
1759 {
1760 	bt_->insertStep(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step);
1761 	comStack_.lock()->push(new InsertStepQtCommand(this));
1762 }
1763 
deletePreviousStep()1764 void PatternEditorPanel::deletePreviousStep()
1765 {
1766 	if (curPos_.step) {
1767 		bt_->deletePreviousStep(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step);
1768 		comStack_.lock()->push(new DeletePreviousStepQtCommand(this));
1769 		if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(-1);
1770 	}
1771 }
1772 
copySelectedCells()1773 void PatternEditorPanel::copySelectedCells()
1774 {
1775 	if (selLeftAbovePos_.order == -1) return;
1776 
1777 	int r = visTracks_.at(selRightBelowPos_.trackVisIdx) * 11 + selRightBelowPos_.colInTrack;
1778 	int l = visTracks_.at(selLeftAbovePos_.trackVisIdx) * 11 + selLeftAbovePos_.colInTrack;
1779 	int w = 1 + r - l;	// Real selected region width
1780 	int h = 1 + calculateStepDistance(selLeftAbovePos_.order, selLeftAbovePos_.step,
1781 									  selRightBelowPos_.order, selRightBelowPos_.step);
1782 	QString str = QString("PATTERN_COPY:%1,%2,%3,")
1783 				  .arg(QString::number(selLeftAbovePos_.colInTrack),
1784 					   QString::number(w),
1785 					   QString::number(h));
1786 	// NOTE: After this line, PatternPosition.trackVisIdx indecates a real track number.
1787 	PatternPosition pos = {
1788 		selLeftAbovePos_.trackVisIdx, selLeftAbovePos_.colInTrack,
1789 		selLeftAbovePos_.order, selLeftAbovePos_.step
1790 	};
1791 	while (true) {
1792 		int trackIdx = visTracks_.at(pos.trackVisIdx);
1793 		switch (pos.colInTrack) {
1794 		case 0:
1795 			str += QString::number(bt_->getStepNoteNumber(curSongNum_, trackIdx, pos.order, pos.step));
1796 			break;
1797 		case 1:
1798 			str += QString::number(bt_->getStepInstrument(curSongNum_, trackIdx, pos.order, pos.step));
1799 			break;
1800 		case 2:
1801 			str += QString::number(bt_->getStepVolume(curSongNum_, trackIdx, pos.order, pos.step));
1802 			break;
1803 		case 3:
1804 			str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, trackIdx, pos.order, pos.step, 0));
1805 			break;
1806 		case 4:
1807 			str += QString::number(bt_->getStepEffectValue(curSongNum_, trackIdx, pos.order, pos.step, 0));
1808 			break;
1809 		case 5:
1810 			str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, trackIdx, pos.order, pos.step, 1));
1811 			break;
1812 		case 6:
1813 			str += QString::number(bt_->getStepEffectValue(curSongNum_, trackIdx, pos.order, pos.step, 1));
1814 			break;
1815 		case 7:
1816 			str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, trackIdx, pos.order, pos.step, 2));
1817 			break;
1818 		case 8:
1819 			str += QString::number(bt_->getStepEffectValue(curSongNum_, trackIdx, pos.order, pos.step, 2));
1820 			break;
1821 		case 9:
1822 			str += QString::fromStdString(bt_->getStepEffectID(curSongNum_, trackIdx, pos.order, pos.step, 3));
1823 			break;
1824 		case 10:
1825 			str += QString::number(bt_->getStepEffectValue(curSongNum_, trackIdx, pos.order, pos.step, 3));
1826 			break;
1827 		}
1828 
1829 		if (pos.isEqualCols(selRightBelowPos_)) {
1830 			if (pos.isEqualRows(selRightBelowPos_)) {
1831 				break;
1832 			}
1833 			else {
1834 				pos.setCols(selLeftAbovePos_.trackVisIdx, selLeftAbovePos_.colInTrack);
1835 				++pos.step;
1836 			}
1837 		}
1838 		else {
1839 			++pos.colInTrack;
1840 			pos.trackVisIdx += (pos.colInTrack / 11);
1841 			pos.colInTrack %= 11;
1842 		}
1843 
1844 		str += ",";
1845 	}
1846 
1847 	QApplication::clipboard()->setText(str);
1848 }
1849 
eraseSelectedCells()1850 void PatternEditorPanel::eraseSelectedCells()
1851 {
1852 	bt_->erasePatternCells(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack,
1853 						   selLeftAbovePos_.order, selLeftAbovePos_.step,
1854 						   visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step);
1855 	comStack_.lock()->push(new EraseCellsInPatternQtCommand(this));
1856 }
1857 
pasteCopiedCells(const PatternPosition & cursorPos)1858 void PatternEditorPanel::pasteCopiedCells(const PatternPosition& cursorPos)
1859 {
1860 	int sCol = 0;
1861 	PatternCells cells = decodeCells(QApplication::clipboard()->text(), sCol);
1862 	PatternPosition pos = getPasteLeftAbovePosition(sCol, cursorPos, cells.front().size());
1863 	if (config_->getPasteMode() == Configuration::FILL && selLeftAbovePos_.order != -1) {
1864 		cells = compandPasteCells(pos, cells);
1865 	}
1866 
1867 	bt_->pastePatternCells(
1868 				curSongNum_, visTracks_.at(pos.trackVisIdx), pos.colInTrack, pos.order, pos.step, std::move(cells));
1869 	comStack_.lock()->push(new PasteCopiedDataToPatternQtCommand(this));
1870 }
1871 
pasteMixCopiedCells(const PatternPosition & cursorPos)1872 void PatternEditorPanel::pasteMixCopiedCells(const PatternPosition& cursorPos)
1873 {
1874 	int sCol = 0;
1875 	PatternCells cells = decodeCells(QApplication::clipboard()->text(), sCol);
1876 	PatternPosition pos = getPasteLeftAbovePosition(sCol, cursorPos, cells.front().size());
1877 	if (config_->getPasteMode() == Configuration::FILL && selLeftAbovePos_.order != -1) {
1878 		cells = compandPasteCells(pos, cells);
1879 	}
1880 
1881 	bt_->pasteMixPatternCells(
1882 				curSongNum_, visTracks_.at(pos.trackVisIdx), pos.colInTrack, pos.order, pos.step, std::move(cells));
1883 	comStack_.lock()->push(new PasteMixCopiedDataToPatternQtCommand(this));
1884 }
1885 
pasteOverwriteCopiedCells(const PatternPosition & cursorPos)1886 void PatternEditorPanel::pasteOverwriteCopiedCells(const PatternPosition& cursorPos)
1887 {
1888 	int sCol = 0;
1889 	PatternCells cells = decodeCells(QApplication::clipboard()->text(), sCol);
1890 	PatternPosition pos = getPasteLeftAbovePosition(sCol, cursorPos, cells.front().size());
1891 	if (config_->getPasteMode() == Configuration::FILL && selLeftAbovePos_.order != -1) {
1892 		cells = compandPasteCells(pos, cells);
1893 	}
1894 
1895 	bt_->pasteOverwritePatternCells(
1896 				curSongNum_, visTracks_.at(pos.trackVisIdx), pos.colInTrack, pos.order, pos.step, std::move(cells));
1897 	comStack_.lock()->push(new PasteOverwriteCopiedDataToPatternQtCommand(this));
1898 }
1899 
pasteInsertCopiedCells(const PatternPosition & cursorPos)1900 void PatternEditorPanel::pasteInsertCopiedCells(const PatternPosition& cursorPos)
1901 {
1902 	int sCol = 0;
1903 	PatternCells cells = decodeCells(QApplication::clipboard()->text(), sCol);
1904 	PatternPosition pos = getPasteLeftAbovePosition(sCol, cursorPos, cells.front().size());
1905 	if (config_->getPasteMode() == Configuration::FILL && selLeftAbovePos_.order != -1) {
1906 		cells = compandPasteCells(pos, cells);
1907 	}
1908 
1909 	bt_->pasteInsertPatternCells(
1910 				curSongNum_, visTracks_.at(pos.trackVisIdx), pos.colInTrack, pos.order, pos.step, std::move(cells));
1911 	comStack_.lock()->push(new PasteInsertCopiedDataToPatternQtCommand(this));
1912 }
1913 
decodeCells(QString str,int & startCol)1914 PatternEditorPanel::PatternCells PatternEditorPanel::decodeCells(QString str, int& startCol)
1915 {
1916 	str.remove(QRegularExpression("PATTERN_(COPY|CUT):"));
1917 	QString hdRe = "^([0-9]+),([0-9]+),([0-9]+),";
1918 	QRegularExpression re(hdRe);
1919 	QRegularExpressionMatch match = re.match(str);
1920 	startCol = match.captured(1).toInt();
1921 	int w = match.captured(2).toInt();
1922 	size_t h = match.captured(3).toUInt();
1923 	str.remove(re);
1924 
1925 	std::vector<std::vector<std::string>> cells;
1926 	re = QRegularExpression("^([^,]+),");
1927 	for (size_t i = 0; i < h; ++i) {
1928 		cells.emplace_back();
1929 		for (int j = 0; j < w; ++j) {
1930 			match = re.match(str);
1931 			if (match.hasMatch()) {
1932 				cells.at(i).push_back(match.captured(1).toStdString());
1933 				str.remove(re);
1934 			}
1935 			else {
1936 				cells.at(i).push_back(str.toStdString());
1937 				break;
1938 			}
1939 		}
1940 	}
1941 
1942 	return cells;
1943 }
1944 
getPasteLeftAbovePosition(int pasteCol,const PatternPosition & cursorPos,size_t cellW) const1945 PatternPosition PatternEditorPanel::getPasteLeftAbovePosition(
1946 		int pasteCol, const PatternPosition& cursorPos, size_t cellW) const
1947 {
1948 	PatternPosition pos;
1949 	Configuration::PasteMode mode = config_->getPasteMode();
1950 	if ((mode == Configuration::SELECTION || mode == Configuration::FILL)
1951 			&& selLeftAbovePos_.colInTrack != -1) {
1952 		pos = selLeftAbovePos_;
1953 		pos.colInTrack = pasteCol;
1954 	}
1955 	else {
1956 		pos = cursorPos;
1957 		if (pasteCol < 3 || (pos.colInTrack - pasteCol) % 2
1958 				|| (11 - pos.colInTrack) < static_cast<int>(cellW)) {
1959 			pos.colInTrack = pasteCol;
1960 		}
1961 	}
1962 
1963 	return pos;
1964 }
1965 
cutSelectedCells()1966 void PatternEditorPanel::cutSelectedCells()
1967 {
1968 	if (selLeftAbovePos_.order == -1) return;
1969 
1970 	copySelectedCells();
1971 	eraseSelectedCells();
1972 	QString str = QApplication::clipboard()->text();
1973 	str.replace("COPY", "CUT");
1974 	QApplication::clipboard()->setText(str);
1975 }
1976 
compandPasteCells(const PatternPosition & laPos,const PatternCells & cells)1977 PatternEditorPanel::PatternCells PatternEditorPanel::compandPasteCells(const PatternPosition& laPos, const PatternCells& cells)
1978 {
1979 	PatternCells newCells;
1980 	int ow = cells.front().size();
1981 	int oh = cells.size();
1982 	int l = laPos.trackVisIdx * 11 + laPos.colInTrack;
1983 	int r = selRightBelowPos_.trackVisIdx * 11 + selRightBelowPos_.colInTrack;
1984 	int w = r - l + 1;	// Real selected region width
1985 	size_t h = static_cast<size_t>(calculateStepDistance(laPos.order, laPos.step,
1986 														 selRightBelowPos_.order, selRightBelowPos_.step) + 1);
1987 	int bw = ((ow - 1) / 11 + 1) * 11;
1988 	size_t padSize = bw - ow;
1989 	std::vector<std::string> pad(padSize);
1990 	int lc = (laPos.colInTrack + ow) % 11;
1991 	for (size_t i = 0; i < padSize; ++i) {
1992 		switch (lc) {
1993 		case 3:
1994 		case 5:
1995 		case 7:
1996 		case 9:
1997 			pad[i] = "--";
1998 			break;
1999 		default:
2000 			pad[i] = "-1";
2001 			break;
2002 		}
2003 		lc = (lc + 1) % 11;
2004 	}
2005 	for (size_t i = 0; i < h; ++i) {
2006 		newCells.emplace_back(w);
2007 		auto&& rowBeginIt = newCells.back().begin();
2008 		for (int dw = w, p = 0; dw > 0; dw -= bw, p += bw) {
2009 			int ws = std::min(ow, dw);
2010 			std::copy_n(cells.at(i % oh).begin(), ws, rowBeginIt + p);
2011 			std::copy_n(pad.begin(), std::min(dw - ws, static_cast<int>(padSize)), rowBeginIt + p + ws);
2012 		}
2013 	}
2014 
2015 	return newCells;
2016 }
2017 
transposeNote(const PatternPosition & startPos,const PatternPosition & endPos,int seminote)2018 void PatternEditorPanel::transposeNote(const PatternPosition& startPos, const PatternPosition& endPos, int seminote)
2019 {
2020 	int beginTrackIdx = (startPos.colInTrack == 0) ? startPos.trackVisIdx : startPos.trackVisIdx + 1;
2021 	if (beginTrackIdx <= endPos.trackVisIdx) {
2022 		bt_->transposeNoteInPattern(curSongNum_, visTracks_.at(beginTrackIdx), startPos.order, startPos.step,
2023 									visTracks_.at(endPos.trackVisIdx), endPos.step, seminote);
2024 		comStack_.lock()->push(new TransposeNoteInPatternQtCommand(this));
2025 	}
2026 }
2027 
changeValuesInPattern(const PatternPosition & startPos,const PatternPosition & endPos,int value)2028 void PatternEditorPanel::changeValuesInPattern(const PatternPosition& startPos, const PatternPosition& endPos, int value)
2029 {
2030 	if (startPos.compareCols(endPos) <= 0) {
2031 		bt_->changeValuesInPattern(curSongNum_, visTracks_.at(startPos.trackVisIdx), startPos.colInTrack, startPos.order, startPos.step,
2032 								   visTracks_.at(endPos.trackVisIdx), endPos.colInTrack, endPos.step, value);
2033 		comStack_.lock()->push(new ChangeValuesInPatternQtCommand(this));
2034 	}
2035 }
2036 
toggleTrack(int trackIdx)2037 void PatternEditorPanel::toggleTrack(int trackIdx)
2038 {
2039 	int trackNum = visTracks_.at(trackIdx);
2040 	bt_->setTrackMuteState(trackNum, !bt_->isMute(trackNum));
2041 	isMuteElse_ = false;
2042 	redrawByMaskChanged();
2043 }
2044 
soloTrack(int trackIdx)2045 void PatternEditorPanel::soloTrack(int trackIdx)
2046 {
2047 	int soloTrackNum = visTracks_.at(trackIdx);
2048 	int trackCnt = static_cast<int>(songStyle_.trackAttribs.size());
2049 	if (isMuteElse_) {
2050 		if (bt_->isMute(soloTrackNum)) {
2051 			for (int t = 0; t < trackCnt; ++t)
2052 				bt_->setTrackMuteState(t, t != soloTrackNum);
2053 		}
2054 		else {
2055 			isMuteElse_ = false;
2056 			for (int t = 0; t < trackCnt; ++t)
2057 				bt_->setTrackMuteState(t, false);
2058 		}
2059 	}
2060 	else {
2061 		isMuteElse_ = true;
2062 		for (int t = 0; t < trackCnt; ++t)
2063 			bt_->setTrackMuteState(t, (t == soloTrackNum) ? false : isMuteElse_);
2064 	}
2065 	redrawByMaskChanged();
2066 }
2067 
setSelectedRectangle(const PatternPosition & start,const PatternPosition & end)2068 void PatternEditorPanel::setSelectedRectangle(const PatternPosition& start, const PatternPosition& end)
2069 {
2070 	int patMax = static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, end.order)) - 1;
2071 	if (start.compareCols(end) > 0) {
2072 		if (start.step > end.step) {
2073 			selLeftAbovePos_ = end;
2074 			selRightBelowPos_ = {
2075 				start.trackVisIdx, start.colInTrack,
2076 				end.order, (start.step > patMax) ? patMax : start.step
2077 			};
2078 		}
2079 		else {
2080 			selLeftAbovePos_ = { end.trackVisIdx, end.colInTrack, end.order, start.step };
2081 			selRightBelowPos_ = { start.trackVisIdx, start.colInTrack, end.order, end.step };
2082 		}
2083 	}
2084 	else {
2085 		if (start.step > end.step) {
2086 			selLeftAbovePos_ = { start.trackVisIdx, start.colInTrack, end.order, end.step };
2087 			selRightBelowPos_ = {
2088 				end.trackVisIdx, end.colInTrack,
2089 				end.order, (start.step > patMax) ? patMax : start.step
2090 			};
2091 		}
2092 		else {
2093 			selLeftAbovePos_ = { start.trackVisIdx, start.colInTrack, end.order, start.step };
2094 			selRightBelowPos_ = end;
2095 		}
2096 	}
2097 
2098 	emit selected(true);
2099 
2100 	backChanged_ = true;
2101 	repaint();
2102 }
2103 
isSelectedCell(int trackVisIdx,int colNum,int orderNum,int stepNum)2104 bool PatternEditorPanel::isSelectedCell(int trackVisIdx, int colNum, int orderNum, int stepNum)
2105 {
2106 	PatternPosition pos{ trackVisIdx, colNum, orderNum, stepNum };
2107 	return (selLeftAbovePos_.compareCols(pos) <= 0 && selRightBelowPos_.compareCols(pos) >= 0
2108 			&& selLeftAbovePos_.compareRows(pos) <= 0 && selRightBelowPos_.compareRows(pos) >= 0);
2109 }
2110 
showPatternContextMenu(const PatternPosition & pos,const QPoint & point)2111 void PatternEditorPanel::showPatternContextMenu(const PatternPosition& pos, const QPoint& point)
2112 {
2113 	QMenu menu;
2114 	// Leave Before Qt5.7.0 style due to windows xp
2115 	QAction* undo = menu.addAction(tr("&Undo"));
2116 	undo->setIcon(QIcon(":/icon/undo"));
2117 	QObject::connect(undo, &QAction::triggered, this, [&]() {
2118 		bt_->undo();
2119 		comStack_.lock()->undo();
2120 	});
2121 	QAction* redo = menu.addAction(tr("&Redo"));
2122 	redo->setIcon(QIcon(":/icon/redo"));
2123 	QObject::connect(redo, &QAction::triggered, this, [&]() {
2124 		bt_->redo();
2125 		comStack_.lock()->redo();
2126 	});
2127 	menu.addSeparator();
2128 	QAction* copy = menu.addAction(tr("&Copy"));
2129 	copy->setIcon(QIcon(":/icon/copy"));
2130 	QObject::connect(copy, &QAction::triggered, this, &PatternEditorPanel::copySelectedCells);
2131 	QAction* cut = menu.addAction(tr("Cu&t"));
2132 	cut->setIcon(QIcon(":/icon/cut"));
2133 	QObject::connect(cut, &QAction::triggered, this, &PatternEditorPanel::cutSelectedCells);
2134 	QAction* paste = menu.addAction(tr("&Paste"));
2135 	paste->setIcon(QIcon(":/icon/paste"));
2136 	QObject::connect(paste, &QAction::triggered, this, [&]() { pasteCopiedCells(pos); });
2137 	auto pasteSp = new QMenu(tr("Paste Specia&l"));
2138 	menu.addMenu(pasteSp);
2139 	QAction* pasteMix = pasteSp->addAction(tr("&Mix"));
2140 	QObject::connect(pasteMix, &QAction::triggered, this, [&]() { pasteMixCopiedCells(pos); });
2141 	QAction* pasteOver = pasteSp->addAction(tr("&Overwrite"));
2142 	QObject::connect(pasteOver, &QAction::triggered, this, [&]() { pasteOverwriteCopiedCells(pos); });
2143 	QAction* pasteIns = pasteSp->addAction(tr("&Insert"));
2144 	QObject::connect(pasteIns, &QAction::triggered, this, [&]() { pasteInsertCopiedCells(pos); });
2145 	QAction* erase = menu.addAction(tr("&Erase"));
2146 	QObject::connect(erase, &QAction::triggered, this, &PatternEditorPanel::eraseSelectedCells);
2147 	QAction* select = menu.addAction(tr("Select &All"));
2148 	QObject::connect(select, &QAction::triggered, this, [&]() { onSelectPressed(1); });
2149 	menu.addSeparator();
2150 	auto pattern = new QMenu(tr("Patter&n"));
2151 	menu.addMenu(pattern);
2152 	QAction* interpolate = pattern->addAction(tr("&Interpolate"));
2153 	QObject::connect(interpolate, &QAction::triggered, this, &PatternEditorPanel::onInterpolatePressed);
2154 	QAction* reverse = pattern->addAction(tr("&Reverse"));
2155 	QObject::connect(reverse, &QAction::triggered, this, &PatternEditorPanel::onReversePressed);
2156 	QAction* replace = pattern->addAction(tr("R&eplace Instrument"));
2157 	QObject::connect(replace, &QAction::triggered, this, &PatternEditorPanel::onReplaceInstrumentPressed);
2158 	pattern->addSeparator();
2159 	QAction* expand = pattern->addAction(tr("E&xpand"));
2160 	QObject::connect(expand, &QAction::triggered, this, &PatternEditorPanel::onExpandPressed);
2161 	QAction* shrink = pattern->addAction(tr("S&hrink"));
2162 	QObject::connect(shrink, &QAction::triggered, this, &PatternEditorPanel::onShrinkPressed);
2163 	pattern->addSeparator();
2164 	auto transpose = new QMenu(tr("&Transpose"));
2165 	pattern->addMenu(transpose);
2166 	QAction* deNote = transpose->addAction(tr("&Decrease Note"));
2167 	QObject::connect(deNote, &QAction::triggered, this, [&]() { onNoteTransposePressed(-1); });
2168 	QAction* inNote = transpose->addAction(tr("&Increase Note"));
2169 	QObject::connect(inNote, &QAction::triggered, this, [&]() { onNoteTransposePressed(1); });
2170 	QAction* deOct = transpose->addAction(tr("D&ecrease Octave"));
2171 	QObject::connect(deOct, &QAction::triggered, this, [&]() { onNoteTransposePressed(-12); });
2172 	QAction* inOct = transpose->addAction(tr("I&ncrease Octave"));
2173 	QObject::connect(inOct, &QAction::triggered, this, [&]() { onNoteTransposePressed(12); });
2174 	auto changeVals = new QMenu(tr("&Change Values"));
2175 	pattern->addMenu(changeVals);
2176 	QAction* fdeVal = changeVals->addAction(tr("Fine &Decrease Values"));
2177 	QObject::connect(fdeVal, &QAction::triggered, this, [&]() { onChangeValuesPressed(-1); });
2178 	QAction* finVal = changeVals->addAction(tr("Fine &Increase Values"));
2179 	QObject::connect(finVal, &QAction::triggered, this, [&]() { onChangeValuesPressed(1); });
2180 	QAction* cdeVal = changeVals->addAction(tr("Coarse D&ecrease Values"));
2181 	QObject::connect(cdeVal, &QAction::triggered, this, [&]() { onChangeValuesPressed(-16); });
2182 	QAction* cinVal = changeVals->addAction(tr("Coarse I&ncrease Values"));
2183 	QObject::connect(cinVal, &QAction::triggered, this, [&]() { onChangeValuesPressed(16); });
2184 	menu.addSeparator();
2185 	QAction* toggle = menu.addAction(tr("To&ggle Track"));
2186 	QObject::connect(toggle, &QAction::triggered, this, [&] { toggleTrack(pos.trackVisIdx); });
2187 	QAction* solo = menu.addAction(tr("&Solo Track"));
2188 	QObject::connect(solo, &QAction::triggered, this, [&] { soloTrack(pos.trackVisIdx); });
2189 	menu.addSeparator();
2190 	QAction* exeff = menu.addAction(tr("Expand E&ffect Column"));
2191 	QObject::connect(exeff, &QAction::triggered, this, [&] { onExpandEffectColumnPressed(pos.trackVisIdx); });
2192 	QAction* sheff = menu.addAction(tr("Shrin&k Effect Column"));
2193 	QObject::connect(sheff, &QAction::triggered, this, [&] { onShrinkEffectColumnPressed(pos.trackVisIdx); });
2194 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
2195 	undo->setShortcutVisibleInContextMenu(true);
2196 	redo->setShortcutVisibleInContextMenu(true);
2197 	copy->setShortcutVisibleInContextMenu(true);
2198 	cut->setShortcutVisibleInContextMenu(true);
2199 	paste->setShortcutVisibleInContextMenu(true);
2200 	pasteMix->setShortcutVisibleInContextMenu(true);
2201 	erase->setShortcutVisibleInContextMenu(true);
2202 	select->setShortcutVisibleInContextMenu(true);
2203 	interpolate->setShortcutVisibleInContextMenu(true);
2204 	reverse->setShortcutVisibleInContextMenu(true);
2205 	replace->setShortcutVisibleInContextMenu(true);
2206 	deNote->setShortcutVisibleInContextMenu(true);
2207 	inNote->setShortcutVisibleInContextMenu(true);
2208 	deOct->setShortcutVisibleInContextMenu(true);
2209 	inOct->setShortcutVisibleInContextMenu(true);
2210 	fdeVal->setShortcutVisibleInContextMenu(true);
2211 	finVal->setShortcutVisibleInContextMenu(true);
2212 	cdeVal->setShortcutVisibleInContextMenu(true);
2213 	cinVal->setShortcutVisibleInContextMenu(true);
2214 	toggle->setShortcutVisibleInContextMenu(true);
2215 	solo->setShortcutVisibleInContextMenu(true);
2216 	exeff->setShortcutVisibleInContextMenu(true);
2217 	sheff->setShortcutVisibleInContextMenu(true);
2218 #endif
2219 	auto shortcuts = config_->getShortcuts();
2220 	undo->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Z));
2221 	redo->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Y));
2222 	undo->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Z));
2223 	copy->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C));
2224 	cut->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_X));
2225 	paste->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V));
2226 	pasteMix->setShortcut(strToKeySeq(shortcuts.at(Configuration::PasteMix)));
2227 	pasteOver->setShortcut(strToKeySeq(shortcuts.at(Configuration::PasteOverwrite)));
2228 	pasteIns->setShortcut(strToKeySeq(shortcuts.at(Configuration::PasteInsert)));
2229 	erase->setShortcut(QKeySequence(Qt::Key_Delete));
2230 	select->setShortcut(strToKeySeq(shortcuts.at(Configuration::SelectAll)));
2231 	interpolate->setShortcut(strToKeySeq(shortcuts.at(Configuration::Interpolate)));
2232 	reverse->setShortcut(strToKeySeq(shortcuts.at(Configuration::Reverse)));
2233 	replace->setShortcut(strToKeySeq(shortcuts.at(Configuration::ReplaceInstrument)));
2234 	expand->setShortcut(strToKeySeq(shortcuts.at(Configuration::ExpandPattern)));
2235 	shrink->setShortcut(strToKeySeq(shortcuts.at(Configuration::ShrinkPattern)));
2236 	deNote->setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreaseNote)));
2237 	inNote->setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreaseNote)));
2238 	deOct->setShortcut(strToKeySeq(shortcuts.at(Configuration::DecreaseOctave)));
2239 	inOct->setShortcut(strToKeySeq(shortcuts.at(Configuration::IncreaseOctave)));
2240 	fdeVal->setShortcut(strToKeySeq(shortcuts.at(Configuration::FineDecreaseValues)));
2241 	finVal->setShortcut(strToKeySeq(shortcuts.at(Configuration::FineIncreaseValues)));
2242 	cdeVal->setShortcut(strToKeySeq(shortcuts.at(Configuration::CoarseDecreaseValues)));
2243 	cinVal->setShortcut(strToKeySeq(shortcuts.at(Configuration::CoarseIncreaseValuse)));
2244 	toggle->setShortcut(strToKeySeq(shortcuts.at(Configuration::ToggleTrack)));
2245 	solo->setShortcut(strToKeySeq(shortcuts.at(Configuration::SoloTrack)));
2246 	exeff->setShortcut(strToKeySeq(shortcuts.at(Configuration::ExpandEffect)));
2247 	sheff->setShortcut(strToKeySeq(shortcuts.at(Configuration::ShrinkEffect)));
2248 
2249 	if (bt_->isJamMode() || pos.order < 0 || pos.trackVisIdx < 0) {
2250 		copy->setEnabled(false);
2251 		cut->setEnabled(false);
2252 		paste->setEnabled(false);
2253 		pasteMix->setEnabled(false);
2254 		pasteOver->setEnabled(false);
2255 		pasteIns->setEnabled(false);
2256 		erase->setEnabled(false);
2257 		interpolate->setEnabled(false);
2258 		reverse->setEnabled(false);
2259 		replace->setEnabled(false);
2260 		expand->setEnabled(false);
2261 		shrink->setEnabled(false);
2262 		deNote->setEnabled(false);
2263 		inNote->setEnabled(false);
2264 		deOct->setEnabled(false);
2265 		inOct->setEnabled(false);
2266 		fdeVal->setEnabled(false);
2267 		finVal->setEnabled(false);
2268 		cdeVal->setEnabled(false);
2269 		cinVal->setEnabled(false);
2270 	}
2271 	else {
2272 		QString clipText = QApplication::clipboard()->text();
2273 		if (!clipText.startsWith("PATTERN_COPY") && !clipText.startsWith("PATTERN_CUT")) {
2274 			paste->setEnabled(false);
2275 			pasteMix->setEnabled(false);
2276 			pasteOver->setEnabled(false);
2277 			pasteIns->setEnabled(false);
2278 		}
2279 		if (selRightBelowPos_.order < 0
2280 				|| !isSelectedCell(pos.trackVisIdx, pos.colInTrack, pos.order, pos.step)) {
2281 			copy->setEnabled(false);
2282 			cut->setEnabled(false);
2283 			erase->setEnabled(false);
2284 			interpolate->setEnabled(false);
2285 			reverse->setEnabled(false);
2286 			replace->setEnabled(false);
2287 			expand->setEnabled(false);
2288 			shrink->setEnabled(false);
2289 			deNote->setEnabled(false);
2290 			inNote->setEnabled(false);
2291 			deOct->setEnabled(false);
2292 			inOct->setEnabled(false);
2293 			fdeVal->setEnabled(false);
2294 			finVal->setEnabled(false);
2295 			cdeVal->setEnabled(false);
2296 			cinVal->setEnabled(false);
2297 		}
2298 	}
2299 	if (!comStack_.lock()->canUndo()) {
2300 		undo->setEnabled(false);
2301 	}
2302 	if (!comStack_.lock()->canRedo()) {
2303 		redo->setEnabled(false);
2304 	}
2305 	if (pos.trackVisIdx < 0) {
2306 		toggle->setEnabled(false);
2307 		solo->setEnabled(false);
2308 		exeff->setEnabled(false);
2309 		sheff->setEnabled(false);
2310 	}
2311 
2312 	menu.exec(mapToGlobal(point));
2313 }
2314 
2315 /********** Slots **********/
onHScrollBarChanged(int num)2316 void PatternEditorPanel::onHScrollBarChanged(int num)
2317 {
2318 	Ui::EventGuard eg(isIgnoreToSlider_);
2319 
2320 	// Skip if position has already changed in panel
2321 	if (config_->getMoveCursorByHorizontalScroll()) {
2322 		if (int dif = num - calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack))
2323 			moveCursorToRight(dif);
2324 	}
2325 	else {
2326 		if (int dif = num - leftTrackVisIdx_)
2327 			moveViewToRight(dif);
2328 	}
2329 }
2330 
onVScrollBarChanged(int num)2331 void PatternEditorPanel::onVScrollBarChanged(int num)
2332 {
2333 	Ui::EventGuard eg(isIgnoreToSlider_);
2334 
2335 	// Skip if position has already changed in panel
2336 	if (int dif = num - curPos_.step) moveCursorToDown(dif);
2337 }
2338 
onOrderListCurrentTrackChanged(int idx)2339 void PatternEditorPanel::onOrderListCurrentTrackChanged(int idx)
2340 {
2341 	Ui::EventGuard eg(isIgnoreToOrder_);
2342 
2343 	int dif = calculateColumnDistance(curPos_.trackVisIdx, curPos_.colInTrack, idx, 0);
2344 	moveCursorToRight(dif);
2345 }
2346 
onOrderListCurrentOrderChanged(int num)2347 void PatternEditorPanel::onOrderListCurrentOrderChanged(int num)
2348 {
2349 	Ui::EventGuard eg(isIgnoreToOrder_);
2350 
2351 	int step = std::min(curPos_.step, static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, num)) - 1);
2352 	int dif = calculateStepDistance(curPos_.order, curPos_.step, num, step);
2353 	moveCursorToDown(dif);
2354 }
2355 
onOrderListEdited()2356 void PatternEditorPanel::onOrderListEdited()
2357 {
2358 	// Reset position memory
2359 	hovPos_ = { -1, -1, -1, -1 };
2360 	mousePressPos_ = { -1, -1, -1, -1 };
2361 	mouseReleasePos_ = { -1, -1, -1, -1 };
2362 	selLeftAbovePos_ = { -1, -1, -1, -1 };
2363 	selRightBelowPos_ = { -1, -1, -1, -1 };
2364 	shiftPressedPos_ = { -1, -1, -1, -1 };
2365 	selectAllState_ = -1;
2366 	emit selected(false);
2367 
2368 	redrawByPatternChanged(true);
2369 }
2370 
onDefaultPatternSizeChanged()2371 void PatternEditorPanel::onDefaultPatternSizeChanged()
2372 {
2373 	// Check pattern size
2374 	int end = static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order));
2375 	if (curPos_.step >= end) curPos_.step = end - 1;
2376 
2377 	redrawByPatternChanged(true);
2378 }
2379 
onShortcutUpdated()2380 void PatternEditorPanel::onShortcutUpdated()
2381 {
2382 	auto shortcuts = config_->getShortcuts();
2383 
2384 	hlUpSc_.setKey(strToKeySeq(shortcuts.at(Configuration::PrevHighlighted)));
2385 	hlUpWSSc_.setKey(strToKeySeq("Shift+" + shortcuts.at(Configuration::PrevHighlighted)));
2386 	hlDnSc_.setKey(strToKeySeq(shortcuts.at(Configuration::NextHighlighted)));
2387 	hlDnWSSc_.setKey(strToKeySeq("Shift+" + shortcuts.at(Configuration::NextHighlighted)));
2388 	keyOffSc_.setKey(strToKeySeq(shortcuts.at(Configuration::KeyOff)));
2389 	echoBufSc_.setKey(strToKeySeq(shortcuts.at(Configuration::EchoBuffer)));
2390 	expandColSc_.setKey(strToKeySeq(shortcuts.at(Configuration::ExpandEffect)));
2391 	shrinkColSc_.setKey(strToKeySeq(shortcuts.at(Configuration::ShrinkEffect)));
2392 }
2393 
setPatternHighlight1Count(int count)2394 void PatternEditorPanel::setPatternHighlight1Count(int count)
2395 {
2396 	hl1Cnt_ = count;
2397 
2398 	backChanged_ = true;
2399 	textChanged_ = true;
2400 	repaint();
2401 }
2402 
setPatternHighlight2Count(int count)2403 void PatternEditorPanel::setPatternHighlight2Count(int count)
2404 {
2405 	hl2Cnt_ = count;
2406 
2407 	backChanged_ = true;
2408 	textChanged_ = true;
2409 	repaint();
2410 }
2411 
setEditableStep(int n)2412 void PatternEditorPanel::setEditableStep(int n)
2413 {
2414 	editableStepCnt_ = n;
2415 }
2416 
onSongLoaded()2417 void PatternEditorPanel::onSongLoaded()
2418 {
2419 	// NOTE: Temporary fix for https://github.com/rerrahkr/BambooTracker/issues/276
2420 	isInitedFirstMod_.store(true);
2421 
2422 	// Initialize cursor position
2423 	curSongNum_ = bt_->getCurrentSongNumber();
2424 	SongType prevType = songStyle_.type;
2425 	songStyle_ = bt_->getSongStyle(curSongNum_);
2426 	visTracks_ = adaptVisibleTrackList(visTracks_, prevType, songStyle_.type);
2427 	rightEffn_.resize(visTracks_.size());
2428 	std::transform(visTracks_.begin(), visTracks_.end(), rightEffn_.begin(), [&](int t) {
2429 		return static_cast<int>(bt_->getEffectDisplayWidth(curSongNum_, t));
2430 	});
2431 	curPos_ = {
2432 		visTracks_.front(),
2433 		0,
2434 		bt_->getCurrentOrderNumber(),
2435 		bt_->getCurrentStepNumber()
2436 	};
2437 	// Set cursor to the most lest-placed visible track
2438 	if (visTracks_.front() != bt_->getCurrentTrackAttribute().number) {
2439 		bt_->setCurrentTrack(visTracks_.front());
2440 	}
2441 	leftTrackVisIdx_ = 0;
2442 	updateTracksWidthFromLeftToEnd();
2443 
2444 	hovPos_ = { -1, -1, -1, -1 };
2445 	mousePressPos_ = { -1, -1, -1, -1 };
2446 	mouseReleasePos_ = { -1, -1, -1, -1 };
2447 	selLeftAbovePos_ = { -1, -1, -1, -1 };
2448 	selRightBelowPos_ = { -1, -1, -1, -1 };
2449 	shiftPressedPos_ = { -1, -1, -1, -1 };
2450 	markerPos_ = { -1, -1, -1, -1 };
2451 	entryCnt_ = 0;
2452 	selectAllState_ = -1;
2453 	emit selected(false);
2454 
2455 	redrawAll();
2456 }
2457 
onDeletePressed()2458 void PatternEditorPanel::onDeletePressed()
2459 {
2460 	if (bt_->isJamMode()) return;
2461 
2462 	if (selLeftAbovePos_.order != -1) {	// Delete region
2463 		eraseSelectedCells();
2464 	}
2465 	else {
2466 		switch (curPos_.colInTrack) {
2467 		case 0:
2468 			bt_->eraseStepNote(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step);
2469 			comStack_.lock()->push(new EraseStepQtCommand(this));
2470 			break;
2471 		case 1:
2472 			bt_->eraseStepInstrument(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step);
2473 			comStack_.lock()->push(new EraseInstrumentInStepQtCommand(this));
2474 			break;
2475 		case 2:
2476 			bt_->eraseStepVolume(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step);
2477 			comStack_.lock()->push(new EraseVolumeInStepQtCommand(this));
2478 			break;
2479 		case 3:
2480 		case 5:
2481 		case 7:
2482 		case 9:
2483 			bt_->eraseStepEffect(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step,
2484 								 (curPos_.colInTrack - 3) / 2);
2485 			comStack_.lock()->push(new EraseEffectInStepQtCommand(this));
2486 			break;
2487 		case 4:
2488 		case 6:
2489 		case 8:
2490 		case 10:
2491 			bt_->eraseStepEffectValue(curSongNum_, visTracks_.at(curPos_.trackVisIdx), curPos_.order, curPos_.step,
2492 									  (curPos_.colInTrack - 4) / 2);
2493 			comStack_.lock()->push(new EraseEffectValueInStepQtCommand(this));
2494 			break;
2495 		}
2496 		if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(editableStepCnt_);
2497 	}
2498 }
2499 
onPastePressed()2500 void PatternEditorPanel::onPastePressed()
2501 {
2502 	if (!bt_->isJamMode()) pasteCopiedCells(curPos_);
2503 }
2504 
onPasteMixPressed()2505 void PatternEditorPanel::onPasteMixPressed()
2506 {
2507 	if (!bt_->isJamMode()) pasteMixCopiedCells(curPos_);
2508 }
2509 
onPasteOverwritePressed()2510 void PatternEditorPanel::onPasteOverwritePressed()
2511 {
2512 	if (!bt_->isJamMode()) pasteOverwriteCopiedCells(curPos_);
2513 }
2514 
onPasteInsertPressed()2515 void PatternEditorPanel::onPasteInsertPressed()
2516 {
2517 	if (!bt_->isJamMode()) pasteInsertCopiedCells(curPos_);
2518 }
2519 
onSelectPressed(int type)2520 void PatternEditorPanel::onSelectPressed(int type)
2521 {
2522 	switch (type) {
2523 	case 0:	// None
2524 	{
2525 		selLeftAbovePos_ = { -1, -1, -1, -1 };
2526 		selRightBelowPos_ = { -1, -1, -1, -1 };
2527 		selectAllState_ = -1;
2528 		emit selected(false);
2529 		backChanged_ = true;
2530 		repaint();
2531 		break;
2532 	}
2533 	case 1:	// All
2534 	{
2535 		selectAllState_ = (selectAllState_ + 1) % 2;
2536 		int max = static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1;
2537 		PatternPosition start, end;
2538 		if (selectAllState_) {
2539 			start = { 0, 0, curPos_.order, 0 };
2540 			end = { static_cast<int>(visTracks_.size() - 1), 10, curPos_.order, max };
2541 		}
2542 		else {
2543 			start = { curPos_.trackVisIdx, 0, curPos_.order, 0 };
2544 			end = { curPos_.trackVisIdx, 10, curPos_.order, max };
2545 		}
2546 		setSelectedRectangle(start, end);
2547 		break;
2548 	}
2549 	case 2:	// Row
2550 	{
2551 		selectAllState_ = -1;
2552 		PatternPosition start = { 0, 0, curPos_.order, curPos_.step };
2553 		PatternPosition end = { static_cast<int>(visTracks_.size() - 1), 10, curPos_.order, curPos_.step };
2554 		setSelectedRectangle(start, end);
2555 		break;
2556 	}
2557 	case 3:	// Column
2558 	{
2559 		selectAllState_ = -1;
2560 		PatternPosition start = { curPos_.trackVisIdx, curPos_.colInTrack, curPos_.order, 0 };
2561 		PatternPosition end = {
2562 			curPos_.trackVisIdx,
2563 			curPos_.colInTrack,
2564 			curPos_.order,
2565 			static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order) - 1)
2566 		};
2567 		setSelectedRectangle(start, end);
2568 		break;
2569 	}
2570 	case 4:	// Pattern
2571 	{
2572 		selectAllState_ = -1;
2573 		PatternPosition start = { curPos_.trackVisIdx, 0, curPos_.order, 0 };
2574 		PatternPosition end = {
2575 			curPos_.trackVisIdx,
2576 			10,
2577 			curPos_.order,
2578 			static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order) - 1)
2579 		};
2580 		setSelectedRectangle(start, end);
2581 		break;
2582 	}
2583 	case 5:	// Order
2584 	{
2585 		selectAllState_ = -1;
2586 		PatternPosition start = { 0, 0, curPos_.order, 0 };
2587 		PatternPosition end = {
2588 			static_cast<int>(visTracks_.size() - 1),
2589 			10,
2590 			curPos_.order,
2591 			static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order) - 1)
2592 		};
2593 		setSelectedRectangle(start, end);
2594 		break;
2595 	}
2596 	}
2597 }
2598 
onNoteTransposePressed(int seminote)2599 void PatternEditorPanel::onNoteTransposePressed(int seminote)
2600 {
2601 	if (bt_->isJamMode()) return;
2602 
2603 	if (selLeftAbovePos_.order != -1)
2604 		transposeNote(selLeftAbovePos_, selRightBelowPos_, seminote);
2605 	else
2606 		transposeNote(curPos_, curPos_, seminote);
2607 }
2608 
onToggleTrackPressed()2609 void PatternEditorPanel::onToggleTrackPressed()
2610 {
2611 	toggleTrack(curPos_.trackVisIdx);
2612 }
2613 
onSoloTrackPressed()2614 void PatternEditorPanel::onSoloTrackPressed()
2615 {
2616 	soloTrack(curPos_.trackVisIdx);
2617 }
2618 
onUnmuteAllPressed()2619 void PatternEditorPanel::onUnmuteAllPressed()
2620 {
2621 	int trackCnt = static_cast<int>(songStyle_.trackAttribs.size());
2622 	for (int t = 0; t < trackCnt; ++t)
2623 		bt_->setTrackMuteState(t, false);
2624 	isMuteElse_ = false;
2625 	redrawByMaskChanged();
2626 }
2627 
onExpandPressed()2628 void PatternEditorPanel::onExpandPressed()
2629 {
2630 	if (selLeftAbovePos_.order == -1) return;
2631 
2632 	bt_->expandPattern(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack,
2633 					   selLeftAbovePos_.order, selLeftAbovePos_.step,
2634 					   visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step);
2635 	comStack_.lock()->push(new ExpandPatternQtCommand(this));
2636 }
2637 
onShrinkPressed()2638 void PatternEditorPanel::onShrinkPressed()
2639 {
2640 	if (selLeftAbovePos_.order == -1) return;
2641 
2642 	bt_->shrinkPattern(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack,
2643 					   selLeftAbovePos_.order, selLeftAbovePos_.step,
2644 					   visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step);
2645 	comStack_.lock()->push(new ShrinkPatternQtCommand(this));
2646 }
2647 
onInterpolatePressed()2648 void PatternEditorPanel::onInterpolatePressed()
2649 {
2650 	if (selLeftAbovePos_.order == -1) return;
2651 
2652 	bt_->interpolatePattern(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack,
2653 							selLeftAbovePos_.order, selLeftAbovePos_.step,
2654 							visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step);
2655 	comStack_.lock()->push(new InterpolatePatternQtCommand(this));
2656 }
2657 
onReversePressed()2658 void PatternEditorPanel::onReversePressed()
2659 {
2660 	if (selLeftAbovePos_.order == -1) return;
2661 
2662 	bt_->reversePattern(curSongNum_, visTracks_.at(selLeftAbovePos_.trackVisIdx), selLeftAbovePos_.colInTrack,
2663 						selLeftAbovePos_.order, selLeftAbovePos_.step,
2664 						visTracks_.at(selRightBelowPos_.trackVisIdx), selRightBelowPos_.colInTrack, selRightBelowPos_.step);
2665 	comStack_.lock()->push(new ReversePatternQtCommand(this));
2666 }
2667 
onReplaceInstrumentPressed()2668 void PatternEditorPanel::onReplaceInstrumentPressed()
2669 {
2670 	if (selLeftAbovePos_.order == -1) return;
2671 
2672 	int curInst = bt_->getCurrentInstrumentNumber();
2673 	if (curInst == -1) return;
2674 
2675 	int beginTrackIdx = (selLeftAbovePos_.colInTrack < 2) ? selLeftAbovePos_.trackVisIdx : (selLeftAbovePos_.trackVisIdx + 1);
2676 	int endTrackIdx = (selRightBelowPos_.colInTrack == 0) ? (selRightBelowPos_.trackVisIdx - 1) : selRightBelowPos_.trackVisIdx;
2677 	if (beginTrackIdx <= endTrackIdx) {
2678 		bt_->replaceInstrumentInPattern(curSongNum_,
2679 										visTracks_.at(beginTrackIdx), selLeftAbovePos_.order, selLeftAbovePos_.step,
2680 										visTracks_.at(endTrackIdx), selRightBelowPos_.step, curInst);
2681 		comStack_.lock()->push(new ReplaceInstrumentInPatternQtCommand(this));
2682 	}
2683 }
2684 
onExpandEffectColumnPressed(int trackVisIdx)2685 void PatternEditorPanel::onExpandEffectColumnPressed(int trackVisIdx)
2686 {
2687 	size_t ti = static_cast<size_t>(trackVisIdx);
2688 	if (rightEffn_.at(ti) == 3) return;
2689 	bt_->setEffectDisplayWidth(curSongNum_, visTracks_.at(trackVisIdx), static_cast<size_t>(++rightEffn_[ti]));
2690 	updateTracksWidthFromLeftToEnd();
2691 
2692 	if (config_->getMoveCursorByHorizontalScroll()) {
2693 		emit effectColsCompanded(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack), getFullColmunSize());
2694 	}
2695 	else {
2696 		emit effectColsCompanded(leftTrackVisIdx_, getScrollableCountByTrack());
2697 	}
2698 
2699 	redrawAll();
2700 }
2701 
onShrinkEffectColumnPressed(int trackVisIdx)2702 void PatternEditorPanel::onShrinkEffectColumnPressed(int trackVisIdx)
2703 {
2704 	size_t ti = static_cast<size_t>(trackVisIdx);
2705 	if (rightEffn_.at(ti) == 0) return;
2706 	bt_->setEffectDisplayWidth(curSongNum_, visTracks_.at(trackVisIdx), static_cast<size_t>(--rightEffn_[ti]));
2707 	updateTracksWidthFromLeftToEnd();
2708 
2709 	if (config_->getMoveCursorByHorizontalScroll()) {
2710 		emit effectColsCompanded(calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack), getFullColmunSize());
2711 	}
2712 	else {
2713 		emit effectColsCompanded(leftTrackVisIdx_, getScrollableCountByTrack());
2714 	}
2715 
2716 	redrawAll();
2717 }
2718 
onFollowModeChanged()2719 void PatternEditorPanel::onFollowModeChanged()
2720 {
2721 	curPos_.setRows(bt_->getCurrentOrderNumber(), bt_->getCurrentStepNumber());
2722 
2723 	emit vScrollBarChangeRequested(
2724 				curPos_.step, static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, curPos_.order)) - 1);
2725 
2726 	// Force redraw all area
2727 	followModeChanged_ = true;
2728 	redrawPatterns();
2729 }
2730 
onChangeValuesPressed(int value)2731 void PatternEditorPanel::onChangeValuesPressed(int value)
2732 {
2733 	if (bt_->isJamMode()) return;
2734 
2735 	if (selLeftAbovePos_.order != -1)
2736 		changeValuesInPattern(selLeftAbovePos_, selRightBelowPos_, value);
2737 	else
2738 		changeValuesInPattern(curPos_, curPos_, value);
2739 }
2740 
onPlayStepPressed()2741 void PatternEditorPanel::onPlayStepPressed()
2742 {
2743 	moveCursorToDown(1);
2744 }
2745 
2746 /********** Events **********/
event(QEvent * event)2747 bool PatternEditorPanel::event(QEvent *event)
2748 {
2749 	switch (event->type()) {
2750 	case QEvent::KeyPress:
2751 		return keyPressed(dynamic_cast<QKeyEvent*>(event));
2752 	case QEvent::KeyRelease:
2753 		return keyReleased(dynamic_cast<QKeyEvent*>(event));
2754 	case QEvent::HoverMove:
2755 		return mouseHoverd(dynamic_cast<QHoverEvent*>(event));
2756 	default:
2757 		return QWidget::event(event);
2758 	}
2759 }
2760 
keyPressed(QKeyEvent * event)2761 bool PatternEditorPanel::keyPressed(QKeyEvent *event)
2762 {
2763 	/* General Keys */
2764 	switch (event->key()) {
2765 	case Qt::Key_Shift:
2766 		shiftPressedPos_ = curPos_;
2767 		return true;
2768 	case Qt::Key_Tab:
2769 		if (curPos_.trackVisIdx == static_cast<int>(visTracks_.size()) - 1) {
2770 			if (config_->getWarpCursor())
2771 				moveCursorToRight(-calculateColNumInRow(curPos_.trackVisIdx, curPos_.colInTrack));
2772 		}
2773 		else {
2774 			moveCursorToRight(5 + 2 * rightEffn_[static_cast<size_t>(curPos_.trackVisIdx)] - curPos_.colInTrack);
2775 		}
2776 		return true;
2777 	case Qt::Key_Backtab:
2778 		if (curPos_.trackVisIdx == 0) {
2779 			if (config_->getWarpCursor())
2780 				moveCursorToRight(getFullColmunSize() - 1);
2781 		}
2782 		else {
2783 			moveCursorToRight(-5 - 2 * rightEffn_[static_cast<size_t>(curPos_.trackVisIdx) - 1] - curPos_.colInTrack);
2784 		}
2785 		return true;
2786 	case Qt::Key_Insert:
2787 		if (bt_->isJamMode()) {
2788 			return false;
2789 		}
2790 		else {
2791 			insertStep();
2792 			return true;
2793 		}
2794 	case Qt::Key_Backspace:
2795 		if (bt_->isJamMode()) {
2796 			return false;
2797 		}
2798 		else {
2799 			deletePreviousStep();
2800 			return true;
2801 		}
2802 	case Qt::Key_Menu:
2803 	{
2804 		QPoint point = calculateCurrentCursorPosition();
2805 		point.setX(point.x() + 24);
2806 		point.setY(point.y() - 16);
2807 		showPatternContextMenu(curPos_, point);
2808 		return true;
2809 	}
2810 	default:
2811 		if (!bt_->isJamMode()) {
2812 			// Pattern edit
2813 			if (!config_->getKeyRepetition() && event->isAutoRepeat()) return false;
2814 			switch (curPos_.colInTrack) {
2815 			case 0:
2816 				return enterToneData(event);
2817 			case 1:
2818 				if (event->modifiers().testFlag(Qt::NoModifier)) return enterInstrumentData(event->key());
2819 				break;
2820 			case 2:
2821 				if (event->modifiers().testFlag(Qt::NoModifier)) return enterVolumeData(event->key());
2822 				break;
2823 			case 3:
2824 			case 5:
2825 			case 7:
2826 			case 9:
2827 				if (event->modifiers().testFlag(Qt::NoModifier)) return enterEffectID(event->key());
2828 				break;
2829 			case 4:
2830 			case 6:
2831 			case 8:
2832 			case 10:
2833 				if (event->modifiers().testFlag(Qt::NoModifier)) return enterEffectValue(event->key());
2834 				break;
2835 			}
2836 		}
2837 		return false;
2838 	}
2839 }
2840 
keyReleased(QKeyEvent * event)2841 bool PatternEditorPanel::keyReleased(QKeyEvent* event)
2842 {
2843 	switch (event->key()) {
2844 	case Qt::Key_Shift:
2845 		shiftPressedPos_ = { -1, -1, -1, -1 };
2846 		return true;
2847 	default:
2848 		return false;
2849 	}
2850 }
2851 
paintEvent(QPaintEvent * event)2852 void PatternEditorPanel::paintEvent(QPaintEvent *event)
2853 {
2854 	if (bt_ && isInitedFirstMod_.load()) {
2855 		// Check order size
2856 		int odrSize = static_cast<int>(bt_->getOrderSize(curSongNum_));
2857 		if (curPos_.order >= odrSize) curPos_.setRows(odrSize - 1, 0);
2858 
2859 		const QRect& area = event->rect();
2860 		if (area.x() == 0 && area.y() == 0) {
2861 			drawPattern(area);
2862 		}
2863 		else {
2864 			drawPattern(rect());
2865 		}
2866 	}
2867 }
2868 
resizeEvent(QResizeEvent * event)2869 void PatternEditorPanel::resizeEvent(QResizeEvent *event)
2870 {
2871 	QWidget::resizeEvent(event);
2872 	funcResize();
2873 	redrawAll();
2874 }
2875 
mousePressEvent(QMouseEvent * event)2876 void PatternEditorPanel::mousePressEvent(QMouseEvent *event)
2877 {
2878 	Q_UNUSED(event)
2879 
2880 	mousePressPos_ = hovPos_;
2881 	doubleClickPos_ = mousePressPos_;
2882 	mouseReleasePos_ = { -1, -1, -1, -1 };
2883 	isPressedPlus_ = false;
2884 	isPressedMinus_ = false;
2885 
2886 	if (event->button() == Qt::LeftButton) {
2887 		if (mousePressPos_.order == -2 && mousePressPos_.trackVisIdx >= 0) {
2888 			int w = calculateTracksWidthWithRowNum(leftTrackVisIdx_, mousePressPos_.trackVisIdx - 1)
2889 					+ hdMuteToggleWidth_ + stepFontWidth_ / 2;
2890 			if (w < event->pos().x() && event->pos().x() < w + hdEffCompandButtonWidth_ + stepFontWidth_) {
2891 				if (event->pos().y() < headerHeight_ / 2) isPressedPlus_ = true;
2892 				else isPressedMinus_ = true;
2893 			}
2894 		}
2895 		selLeftAbovePos_ = { -1, -1, -1, -1 };
2896 		selRightBelowPos_ = { -1, -1, -1, -1 };
2897 		selectAllState_ = -1;
2898 		emit selected(false);
2899 	}
2900 }
2901 
mouseMoveEvent(QMouseEvent * event)2902 void PatternEditorPanel::mouseMoveEvent(QMouseEvent* event)
2903 {
2904 	if (event->buttons() & Qt::LeftButton) {
2905 		if (mousePressPos_.trackVisIdx < 0 || mousePressPos_.order < 0) return;	// Start point is out of range
2906 
2907 		if (hovPos_.trackVisIdx >= 0 && hovPos_.order >= 0) {
2908 			setSelectedRectangle(mousePressPos_, hovPos_);
2909 		}
2910 
2911 		if (event->x() < stepNumWidth_ && leftTrackVisIdx_ > 0) {
2912 			if (config_->getMoveCursorByHorizontalScroll()) {
2913 				moveCursorToRight(-(5 + 2 * rightEffn_.at(static_cast<size_t>(leftTrackVisIdx_) - 1)));
2914 			}
2915 			else {
2916 				moveViewToRight(-1);
2917 			}
2918 		}
2919 		else if (event->x() > geometry().width() - stepNumWidth_ && hovPos_.trackVisIdx != -1) {
2920 			if (config_->getMoveCursorByHorizontalScroll()) {
2921 				moveCursorToRight(5 + 2 * rightEffn_.at(static_cast<size_t>(leftTrackVisIdx_)));
2922 			}
2923 			else {
2924 				moveViewToRight(1);
2925 			}
2926 		}
2927 		if (event->pos().y() < headerHeight_ + stepFontHeight_) {
2928 			if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(-1);
2929 		}
2930 		else if (event->pos().y() > geometry().height() - stepFontHeight_) {
2931 			if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(1);
2932 		}
2933 	}
2934 }
2935 
mouseReleaseEvent(QMouseEvent * event)2936 void PatternEditorPanel::mouseReleaseEvent(QMouseEvent* event)
2937 {
2938 	mouseReleasePos_ = hovPos_;
2939 
2940 	switch (event->button()) {
2941 	case Qt::LeftButton:
2942 		if (mousePressPos_ == mouseReleasePos_) {	// Jump cell
2943 			if (hovPos_.order >= 0 && hovPos_.step >= 0
2944 					&& hovPos_.trackVisIdx >= 0 && hovPos_.colInTrack >= 0) {
2945 				int horDif = calculateColumnDistance(curPos_.trackVisIdx, curPos_.colInTrack,
2946 													 hovPos_.trackVisIdx, hovPos_.colInTrack);
2947 				int verDif = calculateStepDistance(curPos_.order, curPos_.step,
2948 												   hovPos_.order, hovPos_.step);
2949 				moveCursorToRight(horDif);
2950 				if (!bt_->isPlaySong() || !bt_->isFollowPlay()) moveCursorToDown(verDif);
2951 			}
2952 			else if (hovPos_.order == -2 && hovPos_.trackVisIdx >= 0) {	// Header
2953 				if (isPressedPlus_) {
2954 					onExpandEffectColumnPressed(hovPos_.trackVisIdx);
2955 				}
2956 				else if (isPressedMinus_) {
2957 					onShrinkEffectColumnPressed(hovPos_.trackVisIdx);
2958 				}
2959 				else {
2960 					toggleTrack(hovPos_.trackVisIdx);
2961 					int horDif = calculateColumnDistance(curPos_.trackVisIdx, curPos_.colInTrack,
2962 														 hovPos_.trackVisIdx, 0);
2963 					moveCursorToRight(horDif);
2964 				}
2965 			}
2966 			else if (hovPos_.trackVisIdx == -2 && hovPos_.order >= 0 && hovPos_.step >= 0) {	// Step number
2967 				if (!bt_->isPlaySong() || !bt_->isFollowPlay()) {
2968 					int verDif = calculateStepDistance(curPos_.order, curPos_.step,
2969 													   hovPos_.order, hovPos_.step);
2970 					moveCursorToDown(verDif);
2971 				}
2972 			}
2973 		}
2974 		break;
2975 
2976 	case Qt::RightButton:	// Show context menu
2977 	{
2978 		if (mousePressPos_.order == -2) {	// Header
2979 			QMenu menu;
2980 			// Leave Before Qt5.7.0 style due to windows xp
2981 			QAction* toggle = menu.addAction(tr("To&ggle Track"));
2982 			QObject::connect(toggle, &QAction::triggered, this, [&] { toggleTrack(mousePressPos_.trackVisIdx); });
2983 			QAction* solo = menu.addAction(tr("&Solo Track"));
2984 			QObject::connect(solo, &QAction::triggered, this, [&] { soloTrack(mousePressPos_.trackVisIdx); });
2985 			QAction* unmute = menu.addAction(tr("&Unmute All Tracks"));
2986 			QObject::connect(unmute, &QAction::triggered, this, &PatternEditorPanel::onUnmuteAllPressed);
2987 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
2988 			toggle->setShortcutVisibleInContextMenu(true);
2989 			solo->setShortcutVisibleInContextMenu(true);
2990 #endif
2991 			auto shortcuts = config_->getShortcuts();
2992 			toggle->setShortcut(strToKeySeq(shortcuts.at(Configuration::ToggleTrack)));
2993 			solo->setShortcut(strToKeySeq(shortcuts.at(Configuration::SoloTrack)));
2994 			if (mousePressPos_.trackVisIdx < 0) {
2995 				toggle->setEnabled(false);
2996 				solo->setEnabled(false);
2997 				unmute->setEnabled(false);
2998 			}
2999 			menu.exec(mapToGlobal(event->pos()));
3000 		}
3001 		else {	// Pattern
3002 			showPatternContextMenu(mousePressPos_, event->pos());
3003 		}
3004 
3005 		break;
3006 	}
3007 
3008 	case Qt::XButton1:
3009 	{
3010 		if (!bt_->isPlaySong() || !bt_->isFollowPlay()) {
3011 			int order = curPos_.order - 1;
3012 			if (order < 0) order = static_cast<int>(bt_->getOrderSize(curSongNum_)) - 1;
3013 			int step = std::min(
3014 						   curPos_.step,
3015 						   static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, order)) - 1);
3016 			int d = calculateStepDistance(curPos_.order, curPos_.step, order, step);
3017 			moveCursorToDown(d);
3018 		}
3019 		break;
3020 	}
3021 
3022 	case Qt::XButton2:
3023 	{
3024 		if (!bt_->isPlaySong() || !bt_->isFollowPlay()) {
3025 			int order = curPos_.order + 1;
3026 			if (static_cast<int>(bt_->getOrderSize(curSongNum_)) - 1 < order) order = 0;
3027 			int step = std::min(
3028 						   curPos_.step,
3029 						   static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, order)) - 1);
3030 			int d = calculateStepDistance(curPos_.order, curPos_.step, order, step);
3031 			moveCursorToDown(d);
3032 		}
3033 		break;
3034 	}
3035 
3036 	default:
3037 		break;
3038 	}
3039 
3040 	mousePressPos_ = { -1, -1, -1, -1 };
3041 	mouseReleasePos_ = { -1, -1, -1, -1 };
3042 }
3043 
mouseDoubleClickEvent(QMouseEvent * event)3044 void PatternEditorPanel::mouseDoubleClickEvent(QMouseEvent* event)
3045 {
3046 	if (event->button() == Qt::LeftButton) {
3047 		if (doubleClickPos_.order >= 0) {
3048 			if (!config_->getDontSelectOnDoubleClick()) {
3049 				if (doubleClickPos_.trackVisIdx >= 0) {
3050 					onSelectPressed(4);
3051 					return;
3052 				}
3053 				else if (doubleClickPos_.trackVisIdx == -2) {
3054 					onSelectPressed(5);
3055 					return;
3056 				}
3057 			}
3058 		}
3059 		else if (doubleClickPos_.order == -2) {
3060 			if (doubleClickPos_.trackVisIdx >= 0 && !isPressedPlus_ && !isPressedMinus_) {
3061 				bool flag = true;
3062 				int trackCnt = static_cast<int>(songStyle_.trackAttribs.size());
3063 				int clickedNum = visTracks_.at(doubleClickPos_.trackVisIdx);
3064 				for (int t = 0; t < trackCnt; ++t) {
3065 					if (t != clickedNum) flag &= bt_->isMute(t);
3066 				}
3067 				if (flag) onUnmuteAllPressed();
3068 				else soloTrack(doubleClickPos_.trackVisIdx);
3069 				return;
3070 			}
3071 		}
3072 	}
3073 
3074 	// Else
3075 	mousePressEvent(event);
3076 }
3077 
mouseHoverd(QHoverEvent * event)3078 bool PatternEditorPanel::mouseHoverd(QHoverEvent *event)
3079 {
3080 	QPoint pos = event->pos();
3081 	PatternPosition oldPos = hovPos_;
3082 
3083 	// Detect Step
3084 	if (pos.y() <= headerHeight_) {
3085 		// Track header
3086 		hovPos_.setRows(-2, -2);
3087 	}
3088 	else {
3089 		if (pos.y() < curRowY_) {
3090 			int tmpOdr = curPos_.order;
3091 			int tmpStep = curPos_.step +  (pos.y() - curRowY_) / stepFontHeight_ - 1;
3092 			while (true) {
3093 				if (tmpStep < 0) {
3094 					if (tmpOdr == 0) {
3095 						hovPos_.setRows(-1, -1);
3096 						break;
3097 					}
3098 					else {
3099 						tmpStep += bt_->getPatternSizeFromOrderNumber(curSongNum_, --tmpOdr);
3100 					}
3101 				}
3102 				else {
3103 					hovPos_.setRows(tmpOdr, tmpStep);
3104 					break;
3105 				}
3106 			}
3107 		}
3108 		else {
3109 			int tmpOdr = curPos_.order;
3110 			int tmpStep = curPos_.step +  (pos.y() - curRowY_) / stepFontHeight_;
3111 			while (true) {
3112 				int endStep = static_cast<int>(bt_->getPatternSizeFromOrderNumber(curSongNum_, tmpOdr));
3113 				if (tmpStep < endStep) {
3114 					hovPos_.setRows(tmpOdr, tmpStep);
3115 					break;
3116 				}
3117 				else {
3118 					if (tmpOdr == static_cast<int>(bt_->getOrderSize(curSongNum_)) - 1) {
3119 						hovPos_.setRows(-1, -1);
3120 						break;
3121 					}
3122 					else {
3123 						++tmpOdr;
3124 						tmpStep -= endStep;
3125 					}
3126 				}
3127 			}
3128 		}
3129 	}
3130 
3131 	// Detect column
3132 	if (pos.x() <= stepNumWidth_) {
3133 		// Row number
3134 		hovPos_.setCols(-2, -2);
3135 	}
3136 	else {
3137 		int tmpWidth = stepNumWidth_;
3138 		for (int i = leftTrackVisIdx_; ; ) {
3139 			tmpWidth += (toneNameWidth_ + widthSpaceDbl_);
3140 			if (pos.x() <= tmpWidth) {
3141 				hovPos_.setCols(i, 0);
3142 				break;
3143 			}
3144 			tmpWidth += (instWidth_ + widthSpaceDbl_);
3145 			if (pos.x() <= tmpWidth) {
3146 				hovPos_.setCols(i, 1);
3147 				break;
3148 			}
3149 			tmpWidth += (volWidth_ + widthSpaceDbl_);
3150 			if (pos.x() <= tmpWidth) {
3151 				hovPos_.setCols(i, 2);
3152 				break;
3153 			}
3154 			bool flag = false;
3155 			for (int j = 0; j <= rightEffn_.at(static_cast<size_t>(i)); ++j) {
3156 				tmpWidth += (effIDWidth_ + widthSpace_);
3157 				if (pos.x() <= tmpWidth) {
3158 					hovPos_.setCols(i, 3 + 2 * j);
3159 					flag = true;
3160 					break;
3161 				}
3162 				tmpWidth += (effValWidth_ + widthSpace_);
3163 				if (pos.x() <= tmpWidth) {
3164 					hovPos_.setCols(i, 4 + 2 * j);
3165 					flag = true;
3166 					break;
3167 				}
3168 			}
3169 			if (flag) break;
3170 			++i;
3171 
3172 			if (i == static_cast<int>(visTracks_.size())) {
3173 				hovPos_.setCols(-1, -1);
3174 				break;
3175 			}
3176 		}
3177 	}
3178 
3179 	if (hovPos_ != oldPos) redrawByHoverChanged();
3180 
3181 	return true;
3182 }
3183 
wheelEvent(QWheelEvent * event)3184 void PatternEditorPanel::wheelEvent(QWheelEvent *event)
3185 {
3186 	if (bt_->isPlaySong() && bt_->isFollowPlay()) return;
3187 	int cnt = event->angleDelta().y() / 120;
3188 	if (event->modifiers().testFlag(Qt::ControlModifier)) {
3189 		onNoteTransposePressed(cnt);
3190 	}
3191 	else if (event->modifiers().testFlag(Qt::ShiftModifier)) {
3192 		onChangeValuesPressed(cnt);
3193 	}
3194 	else {
3195 		moveCursorToDown(-cnt);
3196 	}
3197 }
3198 
leaveEvent(QEvent * event)3199 void PatternEditorPanel::leaveEvent(QEvent* event)
3200 {
3201 	Q_UNUSED(event)
3202 	// Clear mouse hover selection
3203 	hovPos_ = { -1, -1, -1, -1 };
3204 }
3205 
midiThreadReceivedEvent(double delay,const uint8_t * msg,size_t len,void * userData)3206 void PatternEditorPanel::midiThreadReceivedEvent(double delay, const uint8_t *msg, size_t len, void *userData)
3207 {
3208 	PatternEditorPanel *self = reinterpret_cast<PatternEditorPanel *>(userData);
3209 
3210 	Q_UNUSED(delay)
3211 
3212 	// Note-On/Note-Off
3213 	if (len == 3 && (msg[0] & 0xe0) == 0x80) {
3214 		uint8_t status = msg[0];
3215 		uint8_t key = msg[1];
3216 		uint8_t velocity = msg[2];
3217 		QMetaMethod method = self->metaObject()->method(self->midiKeyEventMethod_);
3218 		method.invoke(self, Qt::QueuedConnection,
3219 					  Q_ARG(uchar, status), Q_ARG(uchar, key), Q_ARG(uchar, velocity));
3220 	}
3221 }
3222 
midiKeyEvent(uchar status,uchar key,uchar velocity)3223 void PatternEditorPanel::midiKeyEvent(uchar status, uchar key, uchar velocity)
3224 {
3225 	if (!bt_->isJamMode()) {
3226 		bool release = ((status & 0xf0) == 0x80) || velocity == 0;
3227 		if (!release) {
3228 			std::pair<int, Note> octaveAndNote = noteNumberToOctaveAndNote(static_cast<int>(key) - 12);
3229 			setStepKeyOn(octaveAndNote.second, octaveAndNote.first);
3230 		}
3231 	}
3232 }
3233