1 /*
2  *  tracker/PatternEditorControl.cpp
3  *
4  *  Copyright 2009 Peter Barth
5  *
6  *  This file is part of Milkytracker.
7  *
8  *  Milkytracker is free software: you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation, either version 3 of the License, or
11  *  (at your option) any later version.
12  *
13  *  Milkytracker is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22 
23 #include "PatternEditorControl.h"
24 #include "GraphicsAbstract.h"
25 #include "Tools.h"
26 #include "Screen.h"
27 #include "Control.h"
28 #include "Font.h"
29 #include "XModule.h"
30 #include "Menu.h"
31 #include "Undo.h"
32 #include "ContextMenu.h"
33 #include "KeyBindings.h"
34 #include "DialogBase.h"
35 #include "PPUIConfig.h"
36 
37 #include "TrackerConfig.h"
38 
39 #define SCROLLBARWIDTH  SCROLLBUTTONSIZE
40 
PatternEditorControl(pp_int32 id,PPScreen * parentScreen,EventListenerInterface * eventListener,const PPPoint & location,const PPSize & size,bool border)41 PatternEditorControl::PatternEditorControl(pp_int32 id, PPScreen* parentScreen, EventListenerInterface* eventListener,
42 										   const PPPoint& location, const PPSize& size, bool border/*= true*/) :
43 	PPControl(id, parentScreen, eventListener, location, size),
44 	borderColor(&TrackerConfig::colorThemeMain),
45 	cursorColor(&TrackerConfig::colorPatternEditorCursorLine),
46 	selectionColor(&TrackerConfig::colorPatternEditorSelection),
47 	menuPosX(0), menuPosY(0),
48 	menuInvokeChannel(-1), lastMenuInvokeChannel(-1),
49 	eventKeyDownBindings(NULL),
50 	scanCodeBindings(NULL),
51 	eventKeyDownBindingsMilkyTracker(NULL), scanCodeBindingsMilkyTracker(NULL), eventKeyDownBindingsFastTracker(NULL), scanCodeBindingsFastTracker(NULL),
52 	patternEditor(NULL), module(NULL), pattern(NULL),
53 	ppreCursor(NULL),
54 	lastAction(RMouseDownActionInvalid), RMouseDownInChannelHeading(-1)
55 {
56 	// default color
57 	bgColor.r = 0;
58 	bgColor.g = 0;
59 	bgColor.b = 0;
60 
61 	vLeftScrollbar = new PPScrollbar(0, parentScreen, this, PPPoint(location.x, location.y), size.height, false);
62 	vRightScrollbar = new PPScrollbar(1, parentScreen, this, PPPoint(location.x + size.width - SCROLLBARWIDTH, location.y), size.height, false);
63 	hTopScrollbar = new PPScrollbar(2, parentScreen, this, PPPoint(location.x + SCROLLBARWIDTH, location.y), size.width - SCROLLBARWIDTH*2, true);
64 	hBottomScrollbar = new PPScrollbar(3, parentScreen, this, PPPoint(location.x + SCROLLBARWIDTH, location.y + size.height - SCROLLBARWIDTH), size.width - SCROLLBARWIDTH*2, true);
65 
66 	caughtControl = NULL;
67 	controlCaughtByLMouseButton = controlCaughtByRMouseButton = false;
68 	pattern = NULL;
69 
70 	startIndex = 0;
71 	startPos = 0;
72 
73 	songPos.orderListIndex = songPos.row = -1;
74 
75 	startSelection = false;
76 
77 	// assuming false is zero :)
78 	memset(muteChannels, 0, sizeof(muteChannels));
79 	memset(recChannels, 0 ,sizeof(recChannels));
80 
81 	// context menu
82 	editMenuControl = new PPContextMenu(4, parentScreen, this, PPPoint(0,0), TrackerConfig::colorThemeMain, false, PPFont::getFont(PPFont::FONT_SYSTEM));
83 
84 	editMenuControl->addEntry("Mute channel", MenuCommandIDMuteChannel);
85 	editMenuControl->addEntry("Solo channel", MenuCommandIDSoloChannel);
86 	editMenuControl->addEntry("Unmute all", MenuCommandIDUnmuteAll);
87 	editMenuControl->addEntry("\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4", -1);
88 	editMenuControl->addEntry("Mark channel", MenuCommandIDSelectChannel);
89 	editMenuControl->addEntry("Mark all", MenuCommandIDSelectAll);
90 	editMenuControl->addEntry("\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4", -1);
91 	editMenuControl->addEntry("Undo", MenuCommandIDUndo);
92 	editMenuControl->addEntry("Redo", MenuCommandIDRedo);
93 	editMenuControl->addEntry("\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4", -1);
94 	editMenuControl->addEntry("Cut", MenuCommandIDCut);
95 	editMenuControl->addEntry("Copy", MenuCommandIDCopy);
96 	editMenuControl->addEntry("Paste", MenuCommandIDPaste);
97 	editMenuControl->addEntry("Porous Paste", MenuCommandIDPorousPaste);
98 	editMenuControl->addEntry("\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4", -1);
99 	editMenuControl->addEntry("Swap channels", MenuCommandIDSwapChannels);
100 
101 	editMenuControl->setNotifyParentOnHide(true);
102 
103 	initKeyBindings();
104 
105 #ifdef __LOWRES__
106 	setFont(PPFont::getFont(PPFont::FONT_TINY));
107 	switchEditMode(EditModeMilkyTracker);
108 #else
109 	setFont(PPFont::getFont(PPFont::FONT_SYSTEM));
110 	switchEditMode(EditModeFastTracker);
111 #endif
112 
113 	setRecordMode(false);
114 
115 	transposeHandlerResponder = new TransposeHandlerResponder(*this);
116 	dialog = NULL;
117 }
118 
~PatternEditorControl()119 PatternEditorControl::~PatternEditorControl()
120 {
121 	if (patternEditor)
122 		patternEditor->removeNotificationListener(this);
123 
124 	delete vLeftScrollbar;
125 	delete vRightScrollbar;
126 	delete hTopScrollbar;
127 	delete hBottomScrollbar;
128 
129 	delete editMenuControl;
130 
131 	delete eventKeyDownBindingsMilkyTracker;
132 	delete scanCodeBindingsMilkyTracker;
133 	delete eventKeyDownBindingsFastTracker;
134 	delete scanCodeBindingsFastTracker;
135 
136 	delete transposeHandlerResponder;
137 	delete dialog;
138 }
139 
setFont(PPFont * font)140 void PatternEditorControl::setFont(PPFont* font)
141 {
142 	this->font = font;
143 
144 	adjustExtents();
145 
146 	if (editMenuControl->getSize().width < slotSize)
147 		editMenuControl->setFont(font);
148 	else
149 		editMenuControl->setFont(PPFont::getFont(PPFont::FONT_SYSTEM));
150 
151 	assureCursorVisible();
152 }
153 
setSize(const PPSize & size)154 void PatternEditorControl::setSize(const PPSize& size)
155 {
156 	PPControl::setSize(size);
157 
158 	visibleWidth = size.width - (getRowCountWidth() + 4) - SCROLLBARWIDTH*2;
159 	visibleHeight = size.height - (font->getCharHeight() + 4) - SCROLLBARWIDTH*2;
160 
161 	delete vLeftScrollbar;
162 	delete vRightScrollbar;
163 	delete hTopScrollbar;
164 	delete hBottomScrollbar;
165 
166 	vLeftScrollbar = new PPScrollbar(0, parentScreen, this, PPPoint(location.x, location.y), size.height, false);
167 	vRightScrollbar = new PPScrollbar(1, parentScreen, this, PPPoint(location.x + size.width - SCROLLBARWIDTH, location.y), size.height, false);
168 	hTopScrollbar = new PPScrollbar(2, parentScreen, this, PPPoint(location.x + SCROLLBARWIDTH, location.y), size.width - SCROLLBARWIDTH*2, true);
169 	hBottomScrollbar = new PPScrollbar(3, parentScreen, this, PPPoint(location.x + SCROLLBARWIDTH, location.y + size.height - SCROLLBARWIDTH), size.width - SCROLLBARWIDTH*2, true);
170 
171 	adjustScrollBarSizes();
172 	assureCursorVisible();
173 }
174 
setLocation(const PPPoint & location)175 void PatternEditorControl::setLocation(const PPPoint& location)
176 {
177 	PPControl::setLocation(location);
178 
179 	visibleWidth = size.width - (getRowCountWidth() + 4) - SCROLLBARWIDTH*2;
180 	visibleHeight = size.height - (font->getCharHeight() + 4) - SCROLLBARWIDTH*2;
181 
182 	delete vLeftScrollbar;
183 	delete vRightScrollbar;
184 	delete hTopScrollbar;
185 	delete hBottomScrollbar;
186 
187 	vLeftScrollbar = new PPScrollbar(0, parentScreen, this, PPPoint(location.x, location.y), size.height, false);
188 	vRightScrollbar = new PPScrollbar(1, parentScreen, this, PPPoint(location.x + size.width - SCROLLBARWIDTH, location.y), size.height, false);
189 	hTopScrollbar = new PPScrollbar(2, parentScreen, this, PPPoint(location.x + SCROLLBARWIDTH, location.y), size.width - SCROLLBARWIDTH*2, true);
190 	hBottomScrollbar = new PPScrollbar(3, parentScreen, this, PPPoint(location.x + SCROLLBARWIDTH, location.y + size.height - SCROLLBARWIDTH), size.width - SCROLLBARWIDTH*2, true);
191 
192 	adjustScrollBarSizes();
193 	assureCursorVisible();
194 }
195 
myMod(pp_int32 a,pp_int32 b)196 static inline pp_int32 myMod(pp_int32 a, pp_int32 b)
197 {
198 	pp_int32 r = a % b;
199 	return r < 0 ? b + r : r;
200 }
201 
paint(PPGraphicsAbstract * g)202 void PatternEditorControl::paint(PPGraphicsAbstract* g)
203 {
204 	if (!isVisible())
205 		return;
206 
207 	// ;----------------- everything all right?
208 	validate();
209 
210 	// ;----------------- colors
211 	PPColor lineColor;
212 
213 	if (hasFocus || !properties.showFocus)
214 		lineColor = *cursorColor;
215 	else
216 		lineColor.r = lineColor.g = lineColor.b = 64;
217 
218 	PPColor bColor = *borderColor, dColor = *borderColor, bCursor = lineColor, dCursor = lineColor;
219 	// adjust dark color
220 	dColor.scaleFixed(32768);
221 	// adjust bright color
222 	bColor.scaleFixed(87163);
223 	// adjust dark color
224 	dCursor.scaleFixed(32768);
225 	// adjust bright color
226 	bCursor.scaleFixed(87163);
227 
228 	g->setRect(location.x+SCROLLBARWIDTH, location.y+SCROLLBARWIDTH,
229 			   location.x + size.width - SCROLLBARWIDTH, location.y + size.height - SCROLLBARWIDTH);
230 
231 	g->setColor(bgColor);
232 
233 	g->fill();
234 
235 	g->setFont(font);
236 
237 	// ;----------------- not going any further with invalid pattern
238 	if (pattern == NULL)
239 		return;
240 
241 	// ;----------------- make layout extents
242 	adjustExtents();
243 
244 	char name[32];
245 
246 	mp_sint32 i,j;
247 
248 	// ;----------------- selection layout
249 	PatternEditorTools::Position selectionStart, selectionEnd;
250 	selectionStart = patternEditor->getSelection().start;
251 	selectionEnd = patternEditor->getSelection().end;
252 
253 	PatternEditorTools::flattenSelection(selectionStart, selectionEnd);
254 
255 	// only entire instrument column is allowed
256 	if (selectionStart.inner >= 1 && selectionStart.inner<=2)
257 		selectionStart.inner = 1;
258 	if (selectionEnd.inner >= 1 && selectionEnd.inner<=2)
259 		selectionEnd.inner = 2;
260 	// only entire volume column is allowed
261 	if (selectionStart.inner >= 3 && selectionStart.inner<=4)
262 		selectionStart.inner = 3;
263 	if (selectionEnd.inner >= 3 && selectionEnd.inner<=4)
264 		selectionEnd.inner = 4;
265 
266 	PatternEditorTools::Position& cursor = patternEditor->getCursor();
267 
268 	if (cursor.inner < 0 || cursor.inner >= 8)
269 		cursor.inner = 0;
270 
271 	// ;----------------- some constants
272 	const pp_uint32 fontCharWidth3x = font->getCharWidth()*3 + 1;
273 	const pp_uint32 fontCharWidth2x = font->getCharWidth()*2 + 1;
274 	const pp_uint32 fontCharWidth1x = font->getCharWidth()*1 + 1;
275 
276 	PatternTools* patternTools = &this->patternTools;
277 
278 	// ;----------------- Little adjustment for scrolling in center
279 	if (properties.scrollMode == ScrollModeToCenter)
280 	{
281 		if ((size.height - (SCROLLBARWIDTH + ((signed)font->getCharHeight()+4)))/(signed)font->getCharHeight() > (pattern->rows - startIndex + 1) && startIndex > 0)
282 			startIndex--;
283 	}
284 
285 	// ;----------------- start painting rows
286 	pp_int32 startx = location.x + SCROLLBARWIDTH + getRowCountWidth() + 4;
287 
288 	pp_int32 previousPatternIndex = currentOrderlistIndex;
289 	pp_int32 previousRowIndex = 0;
290 
291 	pp_int32 nextPatternIndex = currentOrderlistIndex;
292 	pp_int32 nextRowIndex = this->pattern->rows-1;
293 
294 	pp_int32 songPosOrderListIndex = currentOrderlistIndex;
295 
296 	TXMPattern* pattern = this->pattern;
297 
298 	// ----------------- colors -----------------
299 	PPColor noteColor = TrackerConfig::colorPatternEditorNote;
300 	PPColor insColor = TrackerConfig::colorPatternEditorInstrument;
301 	PPColor volColor = TrackerConfig::colorPatternEditorVolume;
302 	PPColor effColor = TrackerConfig::colorPatternEditorEffect;
303 	PPColor opColor = TrackerConfig::colorPatternEditorOperand;
304 	PPColor hiLightPrimary = TrackerConfig::colorHighLight_1;
305 	PPColor hiLightSecondary = TrackerConfig::colorHighLight_2;
306 	PPColor hiLightPrimaryRow = TrackerConfig::colorRowHighLight_1;
307 	PPColor hiLightSecondaryRow = TrackerConfig::colorRowHighLight_2;
308 
309 	PPColor textColor = PPUIConfig::getInstance()->getColor(PPUIConfig::ColorStaticText);
310 
311 	pp_int32 numVisibleChannels = patternEditor->getNumChannels();
312 
313 	for (pp_int32 i2 = startIndex;; i2++)
314 	{
315 		i = i2 < 0 ? startIndex - i2 - 1: i2;
316 
317 		pp_int32 px = location.x + SCROLLBARWIDTH;
318 
319 		pp_int32 py = location.y + (i-startIndex) * font->getCharHeight() + SCROLLBARWIDTH + (font->getCharHeight() + 4);
320 
321 		// rows are already in invisible area => abort
322 		if (py >= location.y + size.height)
323 			break;
324 
325 		pp_int32 row = i;
326 
327 		if (properties.prospective && properties.scrollMode == ScrollModeStayInCenter && currentOrderlistIndex != -1)
328 		{
329 			if (i < 0)
330 			{
331 				previousRowIndex--;
332 				if (previousRowIndex < 0)
333 				{
334 					previousPatternIndex--;
335 					if (previousPatternIndex >= 0)
336 					{
337 						pattern = &module->phead[module->header.ord[previousPatternIndex]];
338 						previousRowIndex = pattern->rows-1;
339 
340 						noteColor.set(TrackerConfig::colorThemeMain.r, TrackerConfig::colorThemeMain.g, TrackerConfig::colorThemeMain.b);
341 						insColor = volColor = effColor = opColor = noteColor;
342 					}
343 					else
344 					{
345 						continue;
346 					}
347 				}
348 
349 				songPosOrderListIndex = previousPatternIndex;
350 				row = previousRowIndex;
351 			}
352 			else if (i >= this->pattern->rows)
353 			{
354 				nextRowIndex++;
355 				if (nextRowIndex == pattern->rows && nextPatternIndex < module->header.ordnum)
356 				{
357 					nextPatternIndex++;
358 					if (nextPatternIndex < module->header.ordnum)
359 					{
360 						pattern = &module->phead[module->header.ord[nextPatternIndex]];
361 						nextRowIndex = 0;
362 
363 						// Outside current range display colors of main theme
364 						noteColor.set(TrackerConfig::colorThemeMain.r, TrackerConfig::colorThemeMain.g, TrackerConfig::colorThemeMain.b);
365 						insColor = volColor = effColor = opColor = noteColor;
366 					}
367 					else
368 					{
369 						continue;
370 					}
371 				}
372 				else if (nextPatternIndex >= module->header.ordnum)
373 				{
374 					continue;
375 				}
376 
377 				songPosOrderListIndex = nextPatternIndex;
378 				row = nextRowIndex;
379 			}
380 			else
381 			{
382 				songPosOrderListIndex = currentOrderlistIndex;
383 				pattern = this->pattern;
384 
385 				// inside current range display colors as usual
386 				noteColor = TrackerConfig::colorPatternEditorNote;
387 				insColor = TrackerConfig::colorPatternEditorInstrument;
388 				volColor = TrackerConfig::colorPatternEditorVolume;
389 				effColor = TrackerConfig::colorPatternEditorEffect;
390 				opColor = TrackerConfig::colorPatternEditorOperand;
391 			}
392 		}
393 		else
394 		{
395 			if (i2 < 0 || i2 >= pattern->rows)
396 				continue;
397 
398 			row = i;
399 		}
400 
401 		// draw rows
402 		if (!(i % properties.highlightSpacingPrimary) && properties.highLightRowPrimary)
403 		{
404 			g->setColor(hiLightPrimaryRow);
405 			for (pp_int32 k = 0; k < (pp_int32)font->getCharHeight(); k++)
406 				g->drawHLine(startx - (getRowCountWidth() + 4), startx+visibleWidth, py + k);
407 		}
408 		else if (!(i % properties.highlightSpacingSecondary) && properties.highLightRowSecondary)
409 		{
410 			g->setColor(hiLightSecondaryRow);
411 			for (pp_int32 k = 0; k < (pp_int32)font->getCharHeight(); k++)
412 				g->drawHLine(startx - (getRowCountWidth() + 4), startx+visibleWidth, py + k);
413 		}
414 
415 		// draw position line
416 		if ((row == songPos.row && songPosOrderListIndex == songPos.orderListIndex) ||
417 			(i >= 0 && i <= pattern->rows - 1 && i == songPos.row && songPos.orderListIndex == -1))
418 		{
419 			PPColor lineColor(TrackerConfig::colorThemeMain.r>>1, TrackerConfig::colorThemeMain.g>>1, TrackerConfig::colorThemeMain.b>>1);
420 			g->setColor(lineColor);
421 			for (pp_int32 k = 0; k < (pp_int32)font->getCharHeight(); k++)
422 				g->drawHLine(startx - (getRowCountWidth() + 4), startx+visibleWidth, py + k);
423 		}
424 
425 		// draw cursor line
426 		if (i == cursor.row)
427 		{
428 			g->setColor(bCursor);
429 			g->drawHLine(startx - (getRowCountWidth() + 4), startx+visibleWidth, py - 1);
430 			g->setColor(dCursor);
431 			g->drawHLine(startx - (getRowCountWidth() + 4), startx+visibleWidth, py + (pp_int32)font->getCharHeight());
432 
433 			g->setColor(lineColor);
434 			for (pp_int32 k = 0; k < (pp_int32)font->getCharHeight(); k++)
435 				g->drawHLine(startx - (getRowCountWidth() + 4), startx+visibleWidth, py + k);
436 		}
437 
438 		// draw rows
439 		if (!(i % properties.highlightSpacingPrimary))
440 			g->setColor(hiLightPrimary);
441 		else if (!(i % properties.highlightSpacingSecondary))
442 			g->setColor(hiLightSecondary);
443 		else
444 			g->setColor(textColor);
445 
446 		if (properties.hexCount)
447 			PatternTools::convertToHex(name, myMod(row, pattern->rows), properties.prospective ? 2 : PatternTools::getHexNumDigits(pattern->rows-1));
448 		else
449 			PatternTools::convertToDec(name, myMod(row, pattern->rows), properties.prospective ? 3 : PatternTools::getDecNumDigits(pattern->rows-1));
450 
451 		g->drawString(name, px, py);
452 
453 		// draw channels
454 		for (j = startPos; j < numVisibleChannels; j++)
455 		{
456 
457 			pp_int32 px = (location.x + (j-startPos) * slotSize + SCROLLBARWIDTH) + (getRowCountWidth() + 4);
458 
459 			// columns are already in invisible area => abort
460 			if (px >= location.x + size.width)
461 				break;
462 
463 			pp_int32 py = location.y + SCROLLBARWIDTH;
464 
465 			if (menuInvokeChannel == j)
466 				g->setColor(255-dColor.r, 255-dColor.g, 255-dColor.b);
467 			else
468 				g->setColor(dColor);
469 
470 			{
471 				PPColor nsdColor = g->getColor(), nsbColor = g->getColor();
472 
473 				if (menuInvokeChannel != j)
474 				{
475 					// adjust not so dark color
476 					nsdColor.scaleFixed(50000);
477 
478 					// adjust bright color
479 					nsbColor.scaleFixed(80000);
480 				}
481 				else
482 				{
483 					// adjust not so dark color
484 					nsdColor.scaleFixed(30000);
485 
486 					// adjust bright color
487 					nsbColor.scaleFixed(60000);
488 				}
489 
490 				PPRect rect(px, py, px+slotSize, py + font->getCharHeight()+1);
491 				g->fillVerticalShaded(rect, nsbColor, nsdColor, false);
492 
493 			}
494 
495 			if (muteChannels[j])
496 			{
497 				g->setColor(128, 128, 128);
498 			}
499 			else
500 			{
501 				if (!(j&1))
502 					g->setColor(hiLightPrimary);
503 				else
504 					g->setColor(textColor);
505 
506 				if (j == menuInvokeChannel)
507 				{
508 					PPColor col = g->getColor();
509 					col.r = textColor.r - col.r;
510 					col.g = textColor.g - col.g;
511 					col.b = textColor.b - col.b;
512 					col.clamp();
513 					g->setColor(col);
514 				}
515 			}
516 
517 			sprintf(name, "%i", j+1);
518 
519 			if (muteChannels[j])
520 				strcat(name, " <Mute>");
521 
522 			g->drawString(name, px + (slotSize>>1)-(((pp_int32)strlen(name)*font->getCharWidth())>>1), py+1);
523 		}
524 
525 		for (j = startPos; j < numVisibleChannels; j++)
526 		{
527 			pp_int32 px = (j-startPos) * slotSize + startx;
528 
529 			// columns are already in invisible area => abort
530 			if (px >= location.x + size.width)
531 				break;
532 
533 			if (j >= selectionStart.channel && i >= selectionStart.row &&
534 				j <= selectionEnd.channel && i <= selectionEnd.row && i < this->pattern->rows)
535 			{
536 				g->setColor(*selectionColor);
537 
538 				if ((row == songPos.row && songPosOrderListIndex == songPos.orderListIndex) ||
539 					(i >= 0 && i <= pattern->rows - 1 && i == songPos.row && songPos.orderListIndex == -1))
540 				{
541 					PPColor c = g->getColor();
542 					c.r = (TrackerConfig::colorThemeMain.r + c.r)>>1;
543 					c.g = (TrackerConfig::colorThemeMain.g + c.g)>>1;
544 					c.b = (TrackerConfig::colorThemeMain.b + c.b)>>1;
545 					c.clamp();
546 					g->setColor(c);
547 				}
548 
549 				if (i == cursor.row)
550 				{
551 					PPColor c = g->getColor();
552 					c.r+=lineColor.r;
553 					c.g+=lineColor.g;
554 					c.b+=lineColor.b;
555 					c.clamp();
556 					g->setColor(c);
557 				}
558 
559 				if (selectionStart.channel == selectionEnd.channel && j == selectionStart.channel)
560 				{
561 					pp_int32 startx = cursorPositions[selectionStart.inner];
562 					pp_int32 endx = cursorPositions[selectionEnd.inner] + cursorSizes[selectionEnd.inner];
563 					g->fill(PPRect(px + startx, py - (i == cursor.row ? 1 : 0), px + endx, py + font->getCharHeight() + (i == cursor.row ? 1 : 0)));
564 				}
565 				else if (j == selectionStart.channel)
566 				{
567 					pp_int32 offset = cursorPositions[selectionStart.inner];
568 					g->fill(PPRect(px + offset, py - (i == cursor.row ? 1 : 0), px + slotSize, py + font->getCharHeight() + (i == cursor.row ? 1 : 0)));
569 				}
570 				else if (j == selectionEnd.channel)
571 				{
572 					pp_int32 offset = cursorPositions[selectionEnd.inner] + cursorSizes[selectionEnd.inner];
573 					g->fill(PPRect(px, py - (i == cursor.row ? 1 : 0), px + offset, py + font->getCharHeight() + (i == cursor.row ? 1 : 0)));
574 				}
575 				else
576 				{
577 					g->fill(PPRect(px, py - (i == cursor.row ? 1 : 0), px + slotSize, py + font->getCharHeight() + (i == cursor.row ? 1 : 0)));
578 				}
579 			}
580 
581 			// --------------------- draw cursor ---------------------
582 			if (j == cursor.channel &&
583 				i == cursor.row)
584 			{
585 				if (hasFocus || !properties.showFocus)
586 					g->setColor(TrackerConfig::colorPatternEditorCursor);
587 				else
588 					g->setColor(PPUIConfig::getInstance()->getColor(PPUIConfig::ColorGrayedOutSelection));
589 
590 				for (pp_int32 k = cursorPositions[cursor.inner]; k < cursorPositions[cursor.inner]+cursorSizes[cursor.inner]; k++)
591 					g->drawVLine(py, py + font->getCharHeight(), px + k);
592 
593 				PPColor c = g->getColor();
594 				PPColor c2 = c;
595 				c.scaleFixed(32768);
596 				c2.scaleFixed(87163);
597 				g->setColor(c2);
598 				g->drawHLine(px + cursorPositions[cursor.inner], px + cursorPositions[cursor.inner]+cursorSizes[cursor.inner], py - 1);
599 				g->setColor(c);
600 				g->drawHLine(px + cursorPositions[cursor.inner], px + cursorPositions[cursor.inner]+cursorSizes[cursor.inner], py + font->getCharHeight());
601 			}
602 
603 			patternTools->setPosition(pattern, j, row);
604 
605 			PPColor noteCol = noteColor;
606 
607 			// Show notes in red if outside PT 3 octaves
608 			if(properties.ptNoteLimit
609 			   && ((patternTools->getNote() >= 71 && patternTools->getNote() < patternTools->getNoteOffNote())
610 				   || patternTools->getNote() < 36))
611 			{
612 				noteCol.set(0xff,00,00);
613 			}
614 
615 			if (muteChannels[j])
616 			{
617 				noteCol.scaleFixed(properties.muteFade);
618 			}
619 
620 			g->setColor(noteCol);
621 			patternTools->getNoteName(name, patternTools->getNote());
622 			g->drawString(name,px, py);
623 
624 			px += fontCharWidth3x + properties.spacing;
625 
626 			if (muteChannels[j])
627 			{
628 				PPColor insCol = insColor;
629 				insCol.scaleFixed(properties.muteFade);
630 				g->setColor(insCol);
631 			}
632 			else
633 				g->setColor(insColor);
634 
635 			pp_uint32 i = patternTools->getInstrument();
636 
637 			if (i)
638 				patternTools->convertToHex(name, i, 2);
639 			else
640 			{
641 				name[0] = name[1] = '\xf4';
642 				name[2] = 0;
643 			}
644 
645 			if (name[0] == '0')
646 			name[0] = '\xf4';
647 
648 			g->drawString(name,px, py);
649 
650 			px += fontCharWidth2x + properties.spacing;
651 
652 			if (muteChannels[j])
653 			{
654 				PPColor volCol = volColor;
655 				volCol.scaleFixed(properties.muteFade);
656 				g->setColor(volCol);
657 			}
658 			else
659 				g->setColor(volColor);
660 
661 			pp_int32 eff, op;
662 
663 			name[0] = name[1] = '\xf4';
664 			name[2] = 0;
665 			if (pattern->effnum >= 2)
666 			{
667 				patternTools->getFirstEffect(eff, op);
668 
669 				patternTools->convertEffectsToFT2(eff, op);
670 
671 				pp_int32 volume = patternTools->getVolumeFromEffect(eff, op);
672 
673 				patternTools->getVolumeName(name, volume);
674 			}
675 
676 			g->drawString(name,px, py);
677 
678 			px += fontCharWidth2x + properties.spacing;
679 
680 			if (muteChannels[j])
681 			{
682 				PPColor effCol = effColor;
683 				effCol.scaleFixed(properties.muteFade);
684 				g->setColor(effCol);
685 			}
686 			else
687 				g->setColor(effColor);
688 
689 			if (pattern->effnum == 1)
690 			{
691 				patternTools->getFirstEffect(eff, op);
692 				patternTools->convertEffectsToFT2(eff, op);
693 			}
694 			else
695 			{
696 				patternTools->getNextEffect(eff, op);
697 				patternTools->convertEffectsToFT2(eff, op);
698 			}
699 
700 			if (eff == 0 && op == 0)
701 			{
702 				name[0] = properties.zeroEffectCharacter;
703 				name[1] = 0;
704 			}
705 			else
706 			{
707 				patternTools->getEffectName(name, eff);
708 			}
709 
710 			g->drawString(name,px, py);
711 
712 			px += fontCharWidth1x;
713 
714 			if (muteChannels[j])
715 			{
716 				PPColor opCol = opColor;
717 				opCol.scaleFixed(properties.muteFade);
718 				g->setColor(opCol);
719 			}
720 			else
721 				g->setColor(opColor);
722 
723 			if (eff == 0 && op == 0)
724 			{
725 				name[0] = name[1] = properties.zeroEffectCharacter;
726 				name[2] = 0;
727 			}
728 			else
729 			{
730 				patternTools->convertToHex(name, op, 2);
731 			}
732 
733 			g->drawString(name,px, py);
734 		}
735 	}
736 
737 	for (j = startPos; j < numVisibleChannels; j++)
738 	{
739 
740 		pp_int32 px = (location.x + (j-startPos) * slotSize + SCROLLBARWIDTH) + (getRowCountWidth() + 4);
741 
742 		// columns are already in invisible area => abort
743 		if (px >= location.x + size.width)
744 			break;
745 
746 		px += fontCharWidth3x + properties.spacing;
747 		px += fontCharWidth2x*3-1 + properties.spacing*2;
748 		px += fontCharWidth1x;
749 
750 		g->setColor(*borderColor);
751 
752 		g->drawVLine(location.y, location.y + size.height, px+1);
753 
754 		g->setColor(bColor);
755 
756 		g->drawVLine(location.y, location.y + size.height, px);
757 
758 		g->setColor(dColor);
759 
760 		g->drawVLine(location.y, location.y + size.height, px+2);
761 	}
762 
763 	// ;----------------- Margin lines
764 	// draw margin vertical line
765 	g->setColor(*borderColor);
766 
767 	pp_int32 px = location.x + SCROLLBARWIDTH;
768 	px+=getRowCountWidth() + 1;
769 	g->drawVLine(location.y, location.y + size.height, px+1);
770 
771 	g->setColor(bColor);
772 	g->drawVLine(location.y, location.y + size.height, px);
773 
774 	g->setColor(dColor);
775 	g->drawVLine(location.y, location.y + size.height, px+2);
776 
777 	// draw margin horizontal lines
778 	for (j = 0; j < visibleWidth / slotSize + 1; j++)
779 	{
780 		pp_int32 px = (location.x + j * slotSize + SCROLLBARWIDTH) + (getRowCountWidth() + 4) - 1;
781 
782 		// columns are already in invisible area => abort
783 		if (px >= location.x + size.width)
784 			break;
785 
786 		pp_int32 py = location.y + SCROLLBARWIDTH;
787 
788 		py+=font->getCharHeight() + 1;
789 
790 		// Did we reach the maximum number of channels already?
791 		// no: just draw seperate horizontal line segments between the vertical margin lines
792 		if (startPos + j < numVisibleChannels)
793 		{
794 			g->setColor(*borderColor);
795 			g->drawHLine(px, px + slotSize - 1, py+1);
796 
797 			g->setColor(bColor);
798 			g->drawHLine(px + 1, px + slotSize - 2, py);
799 
800 			g->setColor(dColor);
801 			g->drawHLine(px + 1, px + slotSize - 2, py+2);
802 		}
803 		// yes: draw the horizontal margin line completely to the right and abort loop
804 		else
805 		{
806 			g->setColor(*borderColor);
807 			g->drawHLine(px, location.x + size.width, py+1);
808 
809 			g->setColor(bColor);
810 			g->drawHLine(px + 1, location.x + size.width, py);
811 
812 			g->setColor(dColor);
813 			g->drawHLine(px + 1, location.x + size.width, py+2);
814 			break;
815 		}
816 	}
817 
818 	// --------------------- draw moved selection ---------------------
819 
820 	if (hasValidSelection() && moveSelection)
821 	{
822 		pp_int32 moveSelectionRows = moveSelectionFinalPos.row - moveSelectionInitialPos.row;
823 		pp_int32 moveSelectionChannels = moveSelectionFinalPos.channel - moveSelectionInitialPos.channel;
824 
825 		pp_int32 i1 = selectionStart.row + moveSelectionRows;
826 		pp_int32 j1 = selectionStart.channel + moveSelectionChannels;
827 		pp_int32 i2 = selectionEnd.row + moveSelectionRows;
828 		pp_int32 j2 = selectionEnd.channel + moveSelectionChannels;
829 
830 		if (i2 >= 0 && j2 >= 0 && i1 < pattern->rows && j1 < numVisibleChannels)
831 		{
832 			i1 = PPTools::clamp(i1, 0, pattern->rows);
833 			i2 = PPTools::clamp(i2, 0, pattern->rows);
834 			j1 = PPTools::clamp(j1, 0, numVisibleChannels);
835 			j2 = PPTools::clamp(j2, 0, numVisibleChannels);
836 
837 			pp_int32 x1 = (location.x + (j1-startPos) * slotSize + SCROLLBARWIDTH) + cursorPositions[selectionStart.inner] + (getRowCountWidth() + 4);
838 			pp_int32 y1 = (location.y + (i1-startIndex) * font->getCharHeight() + SCROLLBARWIDTH) + (font->getCharHeight() + 4);
839 
840 			pp_int32 x2 = (location.x + (j2-startPos) * slotSize + SCROLLBARWIDTH) + cursorPositions[selectionEnd.inner]+cursorSizes[selectionEnd.inner] + (getRowCountWidth() + 3);
841 			pp_int32 y2 = (location.y + (i2-startIndex) * font->getCharHeight() + SCROLLBARWIDTH) + (font->getCharHeight()*2 + 2);
842 
843 			// use a different color for cloning the selection instead of moving it
844 			if (::getKeyModifier() & selectionKeyModifier)
845 				g->setColor(hiLightPrimary);
846 			else
847 				g->setColor(textColor);
848 
849 			const pp_int32 dashLen = 6;
850 
851 			// inner dashed lines
852 			g->drawHLineDashed(x1, x2, y1, dashLen, 3);
853 			g->drawHLineDashed(x1, x2, y2, dashLen, 3+y2-y1);
854 			g->drawVLineDashed(y1, y2, x1, dashLen, 3);
855 			g->drawVLineDashed(y1, y2+2, x2, dashLen, 3+x2-x1);
856 
857 			// outer dashed lines
858 			g->drawHLineDashed(x1-1, x2+1, y1-1, dashLen, 1);
859 			g->drawHLineDashed(x1-1, x2, y2+1, dashLen, 3+y2-y1);
860 			g->drawVLineDashed(y1-1, y2+1, x1-1, dashLen, 1);
861 			g->drawVLineDashed(y1-1, y2+2, x2+1, dashLen, 3+x2-x1);
862 		}
863 
864 	}
865 
866 	// draw scrollbars
867 	hTopScrollbar->paint(g);
868 	hBottomScrollbar->paint(g);
869 	vLeftScrollbar->paint(g);
870 	vRightScrollbar->paint(g);
871 }
872 
attachPatternEditor(PatternEditor * patternEditor)873 void PatternEditorControl::attachPatternEditor(PatternEditor* patternEditor)
874 {
875 	if (this->patternEditor)
876 		this->patternEditor->removeNotificationListener(this);
877 
878 	this->patternEditor = patternEditor;
879 	patternEditor->addNotificationListener(this);
880 
881 	this->module = patternEditor->getModule();
882 	this->pattern = patternEditor->getPattern();
883 
884 	adjustScrollBarPositionsAndSizes();
885 }
886 
reset()887 void PatternEditorControl::reset()
888 {
889 	patternEditor->reset();
890 
891 	menuInvokeChannel = -1;
892 	menuPosX = menuPosY = 0;
893 
894 	// clear selection
895 	startSelection = false;
896 }
897 
setNumVisibleChannels(pp_int32 channels)898 void PatternEditorControl::setNumVisibleChannels(pp_int32 channels)
899 {
900 	patternEditor->setNumVisibleChannels(channels);
901 
902 	adjustScrollBarPositionsAndSizes();
903 
904 	validate();
905 }
906 
getRowCountWidth()907 pp_int32 PatternEditorControl::getRowCountWidth()
908 {
909 	if (pattern == NULL)
910 		return 0;
911 
912 	if (!properties.prospective)
913 	{
914 		if (properties.hexCount)
915 			return font->getCharWidth()*PatternTools::getHexNumDigits(pattern->rows-1);
916 		else
917 			return font->getCharWidth()*PatternTools::getDecNumDigits(pattern->rows-1);
918 	}
919 	else
920 	{
921 		if (properties.hexCount)
922 			return font->getCharWidth()*2;
923 		else
924 			return font->getCharWidth()*3;
925 	}
926 }
927 
adjustExtents()928 void PatternEditorControl::adjustExtents()
929 {
930 	visibleWidth = size.width - (getRowCountWidth() + 4) - SCROLLBARWIDTH*2;
931 	visibleHeight = size.height - (font->getCharHeight() + 4) - SCROLLBARWIDTH*2;
932 
933 	slotSize = 10*font->getCharWidth() + 3*1 + 4 + 3*properties.spacing;
934 
935 	cursorPositions[0] = 0;
936 	cursorPositions[1] = 3 * font->getCharWidth() + 1 + properties.spacing;
937 	cursorPositions[2] = cursorPositions[1] + font->getCharWidth();
938 	cursorPositions[3] = cursorPositions[2] + font->getCharWidth() + 1 + properties.spacing;
939 	cursorPositions[4] = cursorPositions[3] + font->getCharWidth();
940 	cursorPositions[5] = cursorPositions[4] + font->getCharWidth() + 1 + properties.spacing;
941 	cursorPositions[6] = cursorPositions[5] + font->getCharWidth() + 1;
942 	cursorPositions[7] = cursorPositions[6] + font->getCharWidth();
943 	cursorPositions[8] = /*cursorPositions[7] + font->getCharWidth() + 1*/slotSize;
944 
945 	cursorSizes[0] = 3 * font->getCharWidth(); // note
946 	cursorSizes[1] = font->getCharWidth(); // instrument digit 1
947 	cursorSizes[2] = font->getCharWidth(); // instrument digit 2
948 	cursorSizes[3] = font->getCharWidth(); // volume digit 1
949 	cursorSizes[4] = font->getCharWidth(); // volume digit 2
950 	cursorSizes[5] = font->getCharWidth(); // effect "digit"
951 	cursorSizes[6] = font->getCharWidth(); // operand digit 1
952 	cursorSizes[7] = font->getCharWidth(); // operand digit 2
953 }
954 
adjustVerticalScrollBarPositions(mp_sint32 startIndex)955 void PatternEditorControl::adjustVerticalScrollBarPositions(mp_sint32 startIndex)
956 {
957 	// adjust scrollbar positions
958 	if (properties.scrollMode != ScrollModeStayInCenter)
959 	{
960 		pp_int32 visibleItems = (visibleHeight) / font->getCharHeight();
961 		float v = (float)(pattern->rows - visibleItems);
962 		vLeftScrollbar->setBarPosition((pp_int32)(startIndex*(65536.0f/v)));
963 		vRightScrollbar->setBarPosition((pp_int32)(startIndex*(65536.0f/v)));
964 	}
965 	else
966 	{
967 		float v = (float)patternEditor->getCursor().row / (float)(pattern->rows-1);
968 		vLeftScrollbar->setBarPosition((pp_int32)(65536.0f*v));
969 		vRightScrollbar->setBarPosition((pp_int32)(65536.0f*v));
970 	}
971 }
972 
adjustHorizontalScrollBarPositions(mp_sint32 startPos)973 void PatternEditorControl::adjustHorizontalScrollBarPositions(mp_sint32 startPos)
974 {
975 	pp_int32 visibleItems = (visibleWidth) / slotSize;
976 
977 	float v = (float)(patternEditor->getNumChannels() - visibleItems);
978 
979 	hTopScrollbar->setBarPosition((pp_int32)(startPos*(65536.0f/v)));
980 	hBottomScrollbar->setBarPosition((pp_int32)(startPos*(65536.0f/v)));
981 }
982 
adjustScrollBarSizes()983 void PatternEditorControl::adjustScrollBarSizes()
984 {
985 	float s;
986 	if (properties.scrollMode != ScrollModeStayInCenter)
987 	{
988 		s = (float)(visibleHeight) / (float)(pattern->rows*(font->getCharHeight()));
989 	}
990 	else
991 	{
992 		//s = (float)(visibleHeight>>1) / (float)((pattern->rows-1)*(font->getCharHeight()));
993 
994 		s = 1.0f / (float)pattern->rows;
995 		if (s > 1.0f)
996 			s = 1.0f;
997 	}
998 
999 	vLeftScrollbar->setBarSize((pp_int32)(s*65536.0f), false);
1000 	vRightScrollbar->setBarSize((pp_int32)(s*65536.0f), false);
1001 
1002 	s = (float)(visibleWidth) / (float)(patternEditor->getNumChannels()*slotSize);
1003 	hTopScrollbar->setBarSize((pp_int32)(s*65536.0f), false);
1004 	hBottomScrollbar->setBarSize((pp_int32)(s*65536.0f), false);
1005 }
1006 
adjustScrollBarPositionsAndSizes()1007 void PatternEditorControl::adjustScrollBarPositionsAndSizes()
1008 {
1009 	if (pattern == NULL)
1010 		return;
1011 
1012 	adjustScrollBarSizes();
1013 
1014 	adjustVerticalScrollBarPositions(startIndex);
1015 	adjustHorizontalScrollBarPositions(startPos);
1016 }
1017 
setScrollbarPositions(mp_sint32 startIndex,mp_sint32 startPos)1018 void PatternEditorControl::setScrollbarPositions(mp_sint32 startIndex, mp_sint32 startPos)
1019 {
1020 	adjustHorizontalScrollBarPositions(startPos);
1021 	adjustVerticalScrollBarPositions(startIndex);
1022 }
1023 
scrollCursorUp()1024 void PatternEditorControl::scrollCursorUp()
1025 {
1026 	if (pattern == NULL)
1027 		return;
1028 
1029 	/*cursor.row--;
1030 	if (cursor.row < 0)
1031 		cursor.row = 0;*/
1032 
1033 	eventKeyDownBinding_UP();
1034 }
1035 
scrollCursorDown()1036 void PatternEditorControl::scrollCursorDown()
1037 {
1038 	if (pattern == NULL)
1039 		return;
1040 
1041 	/*cursor.row++;
1042 	if (cursor.row > pattern->rows - 1)
1043 		cursor.row = pattern->rows - 1;*/
1044 
1045 	eventKeyDownBinding_DOWN();
1046 }
1047 
assureCursorVisible(bool row,bool channel)1048 void PatternEditorControl::assureCursorVisible(bool row/* = true*/, bool channel/* = true*/)
1049 {
1050 	if (pattern == NULL)
1051 		return;
1052 
1053 	PatternEditorTools::Position& cursor = patternEditor->getCursor();
1054 
1055 	if (row)
1056 	{
1057 
1058 		if (cursor.row >= 0)
1059 		{
1060 
1061 			switch (properties.scrollMode)
1062 			{
1063 				case ScrollModeToEnd:
1064 				{
1065 					pp_int32 visibleItems = (visibleHeight - font->getCharHeight()) / font->getCharHeight();
1066 
1067 
1068 					if ((startIndex <= cursor.row) &&
1069 						((cursor.row - startIndex) * font->getCharHeight()) <= (visibleHeight - font->getCharHeight()))
1070 					{
1071 					}
1072 					else if (cursor.row > startIndex &&
1073 							 cursor.row + visibleItems < pattern->rows)
1074 					{
1075 						//startIndex = cursorPositionRow;
1076 						startIndex+=(cursor.row-(startIndex+visibleItems));
1077 					}
1078 					else if (cursor.row < startIndex &&
1079 							 cursor.row + visibleItems < pattern->rows)
1080 					{
1081 						//startIndex = cursorPositionRow;
1082 						startIndex+=(cursor.row-startIndex);
1083 					}
1084 					else
1085 					{
1086 						startIndex = cursor.row - visibleItems;
1087 						if (startIndex < 0)
1088 							startIndex = 0;
1089 					}
1090 					break;
1091 				}
1092 
1093 				case ScrollModeToCenter:
1094 				{
1095 					pp_int32 mid = (visibleHeight/2) / font->getCharHeight();
1096 					startIndex = cursor.row - mid;
1097 					if (startIndex < 0)
1098 						startIndex = 0;
1099 					break;
1100 				}
1101 
1102 				case ScrollModeStayInCenter:
1103 				{
1104 					pp_int32 mid = (visibleHeight/2) / font->getCharHeight();
1105 					startIndex = cursor.row - mid;
1106 					break;
1107 				}
1108 			}
1109 		}
1110 
1111 		adjustVerticalScrollBarPositions(startIndex);
1112 	}
1113 
1114 	if (channel)
1115 	{
1116 
1117 		if (cursor.channel >= 0)
1118 		{
1119 			pp_int32 visibleChannels = ((visibleWidth) / slotSize) - 1;
1120 
1121 			pp_int32 cursorPos = (cursor.channel-startPos) * slotSize +
1122 								 cursorPositions[cursor.inner];
1123 
1124 			pp_int32 cursorWidth = cursorPositions[cursor.inner+1] -
1125 								   cursorPositions[cursor.inner];
1126 
1127 			if ((startPos <= cursor.channel) &&
1128 				cursorPos <= visibleWidth - cursorWidth)
1129 			{
1130 			}
1131 			else if (cursor.channel > startPos &&
1132 				cursorPos < visibleWidth - cursorWidth)
1133 			{
1134 				startPos+=(cursor.channel-(startPos+visibleChannels));
1135 			}
1136 			else if (cursor.channel < startPos &&
1137 				cursor.channel + visibleChannels < (signed)patternEditor->getNumChannels())
1138 			{
1139 				startPos+=(cursor.channel-startPos);
1140 			}
1141 			else
1142 			{
1143 				startPos = cursor.channel - visibleChannels;
1144 				if (startPos < 0)
1145 					startPos = 0;
1146 			}
1147 
1148 		}
1149 
1150 		adjustHorizontalScrollBarPositions(startPos);
1151 	}
1152 
1153 }
1154 
getNextRecordingChannel(mp_sint32 currentChannel)1155 mp_sint32 PatternEditorControl::getNextRecordingChannel(mp_sint32 currentChannel)
1156 {
1157 	if (currentChannel < 0 || currentChannel >= TrackerConfig::MAXCHANNELS)
1158 		return -1;
1159 
1160 	for (pp_int32 i = currentChannel+1; i < currentChannel + 1 + module->header.channum; i++)
1161 	{
1162 		pp_int32 c = i % module->header.channum;
1163 		if (recChannels[c])
1164 			return c;
1165 	}
1166 
1167 	return currentChannel;
1168 }
1169 
advanceRow(bool assureCursor,bool repaint)1170 void PatternEditorControl::advanceRow(bool assureCursor/* = true*/, bool repaint/* = true*/)
1171 {
1172 	if (!properties.rowAdvance)
1173 		return;
1174 
1175 	/*if (cursor.row + rowInsertAdd < pattern->rows - 1)
1176 	{
1177 		cursor.row+=rowInsertAdd;
1178 	}
1179 	else
1180 		cursor.row = pattern->rows - 1;*/
1181 
1182 	PatternEditorTools::Position& cursor = patternEditor->getCursor();
1183 
1184 	cursor.row = (cursor.row + properties.rowInsertAdd)/* % pattern->rows*/;
1185 
1186 	pp_int32 res = 0;
1187 	if (cursor.row > pattern->rows-1)
1188 	{
1189 		res = notifyUpdate(PatternEditorControl::AdvanceCodeWrappedEnd);
1190 	}
1191 
1192 	if (!res && cursor.row > pattern->rows-1)
1193 	{
1194 		cursor.row %= pattern->rows;
1195 	}
1196 
1197 	if (assureCursor)
1198 		assureCursorVisible();
1199 
1200 	if (repaint)
1201 		parentScreen->paintControl(this);
1202 }
1203 
advance()1204 void PatternEditorControl::advance()
1205 {
1206 	PatternEditorTools::Position& cursor = patternEditor->getCursor();
1207 
1208 	// Multichannel editing means:
1209 	// If we're in the note column and a note has been entered
1210 	// locate cursor to next recording channel
1211 	if (properties.multiChannelEdit && cursor.inner == 0)
1212 		cursor.channel = getNextRecordingChannel(cursor.channel);
1213 
1214 	advanceRow(false, false);
1215 
1216 	assureCursorVisible();
1217 }
1218 
setRow(pp_int32 row,bool bAssureCursorVisible)1219 void PatternEditorControl::setRow(pp_int32 row, bool bAssureCursorVisible/* = true*/)
1220 {
1221 	if (pattern == NULL)
1222 		return;
1223 
1224 	PatternEditorTools::Position& cursor = patternEditor->getCursor();
1225 
1226 	if (row >= 0 && row < pattern->rows)
1227 		cursor.row = row;
1228 
1229 	if (bAssureCursorVisible)
1230 		assureCursorVisible(true, false);
1231 }
1232 
setChannel(pp_int32 chn,pp_int32 posInner)1233 void PatternEditorControl::setChannel(pp_int32 chn, pp_int32 posInner)
1234 {
1235 	if (pattern == NULL)
1236 		return;
1237 
1238 	PatternEditorTools::Position& cursor = patternEditor->getCursor();
1239 
1240 	if (chn >= 0 && chn < pattern->channum)
1241 		cursor.channel = chn;
1242 
1243 	cursor.inner = posInner;
1244 
1245 	assureCursorVisible(false, true);
1246 }
1247 
validate()1248 void PatternEditorControl::validate()
1249 {
1250 	if (pattern == NULL)
1251 		return;
1252 
1253 	// validate scrollbar sizes
1254 	adjustScrollBarSizes();
1255 	/*float s = (float)(visibleHeight) / (float)(pattern->rows*(font->getCharHeight()));
1256 
1257 	vLeftScrollbar->setBarSize((pp_int32)(s*65536.0f), false);
1258 	vRightScrollbar->setBarSize((pp_int32)(s*65536.0f), false);
1259 
1260 	s = (float)(visibleWidth) / (float)(numVisibleChannels*slotSize);
1261 
1262 	hTopScrollbar->setBarSize((pp_int32)(s*65536.0f), false);
1263 	hBottomScrollbar->setBarSize((pp_int32)(s*65536.0f), false);*/
1264 
1265 	PatternEditorTools::Position& cursor = patternEditor->getCursor();
1266 
1267 	// validate cursor position
1268 	if (cursor.channel >= patternEditor->getNumChannels())
1269 	{
1270 		cursor.channel = patternEditor->getNumChannels()-1;
1271 		assureCursorVisible(false, true);
1272 	}
1273 
1274 	if (cursor.row >= pattern->rows)
1275 	{
1276 		cursor.row = pattern->rows-1;
1277 		assureCursorVisible(true, false);
1278 	}
1279 
1280 	pp_int32 visibleItems = (visibleHeight - font->getCharHeight()) / font->getCharHeight();
1281 
1282 	if (properties.scrollMode != ScrollModeStayInCenter)
1283 	{
1284 		if (startIndex + visibleItems > pattern->rows)
1285 		{
1286 			startIndex-=(startIndex + visibleItems)-pattern->rows;
1287 		}
1288 
1289 		if (startIndex < 0)
1290 			startIndex = 0;
1291 	}
1292 
1293 	pp_int32 visibleChannels = (visibleWidth) / slotSize;
1294 	if (startPos + visibleChannels > patternEditor->getNumChannels())
1295 	{
1296 		startPos-=(startPos + visibleChannels)-patternEditor->getNumChannels();
1297 	}
1298 
1299 	if (startPos < 0)
1300 		startPos = 0;
1301 
1302 	// validate selection
1303 	/*flattenSelection();
1304 
1305 	if ((selectionStart.channel >= 0 && selectionStart.channel > numVisibleChannels-1) ||
1306 		(selectionStart.row >= 0 && selectionStart.row > pattern->rows-1))
1307 	{
1308 		resetSelection();
1309 		return;
1310 	}
1311 
1312 	if (selectionEnd.channel >= 0 && selectionEnd.channel > numVisibleChannels-1)
1313 		selectionEnd.channel = numVisibleChannels-1;
1314 
1315 	if (selectionEnd.row >= 0 && selectionEnd.row > pattern->rows-1)
1316 		selectionEnd.row = pattern->rows-1;*/
1317 }
1318 
hasValidSelection() const1319 bool PatternEditorControl::hasValidSelection() const
1320 {
1321 	return patternEditor->hasValidSelection();
1322 }
1323 
markChannel(pp_int32 channel,bool invert)1324 void PatternEditorControl::markChannel(pp_int32 channel, bool invert/* = true*/)
1325 {
1326 	PatternEditor::Selection currentSelection = patternEditor->getSelection();
1327 
1328 	if (currentSelection.start.channel == channel &&
1329 		currentSelection.start.row == 0 &&
1330 		currentSelection.end.channel == channel &&
1331 		currentSelection.end.row == pattern->rows - 1 &&
1332 		invert)
1333 	{
1334 		patternEditor->resetSelection();
1335 	}
1336 	else
1337 	{
1338 		patternEditor->selectChannel(channel);
1339 	}
1340 }
1341 
selectAll()1342 void PatternEditorControl::selectAll()
1343 {
1344 	patternEditor->selectAll();
1345 }
1346 
deselectAll()1347 void PatternEditorControl::deselectAll()
1348 {
1349 	patternEditor->resetSelection();
1350 }
1351 
executeMenuCommand(pp_int32 commandId)1352 void PatternEditorControl::executeMenuCommand(pp_int32 commandId)
1353 {
1354 	switch (commandId)
1355 	{
1356 		case MenuCommandIDMuteChannel:
1357 		{
1358 			muteChannels[menuInvokeChannel] = !muteChannels[menuInvokeChannel];
1359 			PPEvent e(eValueChanged, &muteChannels, sizeof(muteChannels));
1360 			eventListener->handleEvent(reinterpret_cast<PPObject*>(this), &e);
1361 			break;
1362 		}
1363 
1364 		case MenuCommandIDSoloChannel:
1365 		{
1366 			for (pp_uint32 i = 0; i < sizeof(muteChannels)/sizeof(pp_uint8); i++)
1367 				muteChannels[i] = true;
1368 			muteChannels[menuInvokeChannel] = false;
1369 			PPEvent e(eValueChanged, &muteChannels, sizeof(muteChannels));
1370 			eventListener->handleEvent(reinterpret_cast<PPObject*>(this), &e);
1371 			break;
1372 		}
1373 
1374 		case MenuCommandIDUnmuteAll:
1375 		{
1376 			for (pp_uint32 i = 0; i < sizeof(muteChannels)/sizeof(pp_uint8); i++)
1377 				muteChannels[i] = false;
1378 			PPEvent e(eValueChanged, &muteChannels, sizeof(muteChannels));
1379 			eventListener->handleEvent(reinterpret_cast<PPObject*>(this), &e);
1380 			break;
1381 		}
1382 
1383 		// cut
1384 		case MenuCommandIDCut:
1385 			eventKeyCharBinding_Cut();
1386 			break;
1387 
1388 		// copy
1389 		case MenuCommandIDCopy:
1390 			eventKeyCharBinding_Copy();
1391 			break;
1392 
1393 		// paste
1394 		case MenuCommandIDPaste:
1395 			eventKeyCharBinding_Paste();
1396 			break;
1397 
1398 		// transparent paste
1399 		case MenuCommandIDPorousPaste:
1400 			eventKeyCharBinding_TransparentPaste();
1401 			break;
1402 
1403 		// mark all
1404 		case MenuCommandIDSelectAll:
1405 			eventKeyCharBinding_SelectAll();
1406 			break;
1407 
1408 		// mark channel
1409 		case MenuCommandIDSelectChannel:
1410 			markChannel(menuInvokeChannel);
1411 			break;
1412 
1413 		case MenuCommandIDUndo:
1414 			patternEditor->undo();
1415 			break;
1416 
1417 		case MenuCommandIDRedo:
1418 			patternEditor->redo();
1419 			break;
1420 
1421 		case MenuCommandIDSwapChannels:
1422 			patternEditor->swapChannels(patternEditor->getCursor().channel, menuInvokeChannel);
1423 			break;
1424 	}
1425 
1426 	// Hack:
1427 	if (parentScreen->getContextMenuControl() == editMenuControl)
1428 	{
1429 		menuInvokeChannel = -1;
1430 		lastMenuInvokeChannel = -1;
1431 	}
1432 
1433 }
1434 
switchEditMode(EditModes mode)1435 void PatternEditorControl::switchEditMode(EditModes mode)
1436 {
1437 	switch (mode)
1438 	{
1439 		case EditModeMilkyTracker:
1440 		{
1441 			// Assign keyboard bindings
1442 			eventKeyDownBindings = eventKeyDownBindingsMilkyTracker;
1443 			scanCodeBindings = scanCodeBindingsMilkyTracker;
1444 
1445 			selectionKeyModifier = KeyModifierSHIFT;
1446 			break;
1447 		}
1448 
1449 		case EditModeFastTracker:
1450 		{
1451 			// Assign keyboard bindings
1452 			eventKeyDownBindings = eventKeyDownBindingsFastTracker;
1453 			scanCodeBindings = scanCodeBindingsFastTracker;
1454 
1455 			selectionKeyModifier = KeyModifierALT;
1456 			break;
1457 		}
1458 	}
1459 
1460 	editMode = mode;
1461 }
1462 
unmuteAll()1463 void PatternEditorControl::unmuteAll()
1464 {
1465 	memset(muteChannels, 0, sizeof(muteChannels));
1466 
1467 	PPEvent e(eValueChanged, &muteChannels, sizeof(muteChannels));
1468 	eventListener->handleEvent(reinterpret_cast<PPObject*>(this), &e);
1469 }
1470 
setRecordMode(bool b)1471 void PatternEditorControl::setRecordMode(bool b)
1472 {
1473 	cursorColor = b ? &TrackerConfig::colorPatternEditorCursorLineHighLight : &TrackerConfig::colorPatternEditorCursorLine;
1474 }
1475 
editorNotification(EditorBase * sender,EditorBase::EditorNotifications notification)1476 void PatternEditorControl::editorNotification(EditorBase* sender, EditorBase::EditorNotifications notification)
1477 {
1478 	switch (notification)
1479 	{
1480 		case PatternEditor::EditorDestruction:
1481 		{
1482 			patternEditor = NULL;
1483 			break;
1484 		}
1485 
1486 		case PatternEditor::NotificationReload:
1487 		{
1488 			pattern = patternEditor->getPattern();
1489 			module = patternEditor->getModule();
1490 			adjustScrollBarPositionsAndSizes();
1491 			validate();
1492 			break;
1493 		}
1494 
1495 		case PatternEditor::NotificationChanges:
1496 		{
1497 			bool lazyUpdateNotifications = patternEditor->getLazyUpdateNotifications();
1498 			bool numRowsChanged = patternEditor->getLastOperationDidChangeRows();
1499 			bool cursorChanged = patternEditor->getLastOperationDidChangeCursor();
1500 
1501 			// if this is a lazy notification it means we're only
1502 			// notifying that something has changed, instead of refreshing the screen
1503 			// which means that the screen is probably refreshed somewhere else
1504 			// and the notification itself comes actually from this control
1505 			if (lazyUpdateNotifications)
1506 			{
1507 				notifyChanges();
1508 				return;
1509 			}
1510 
1511 			notifyChanges();
1512 
1513 			if (numRowsChanged)
1514 				adjustScrollBarPositionsAndSizes();
1515 
1516 			if (cursorChanged)
1517 				assureCursorVisible();
1518 
1519 			validate();
1520 
1521 			parentScreen->paintControl(this, false);
1522 			break;
1523 		}
1524 
1525 		case PatternEditor::NotificationFeedUndoData:
1526 		{
1527 			undoInfo = UndoInfo(startIndex, startPos);
1528 			patternEditor->setUndoUserData(&undoInfo, sizeof(undoInfo));
1529 			break;
1530 		}
1531 
1532 		case PatternEditor::NotificationFetchUndoData:
1533 			if (sizeof(undoInfo) == patternEditor->getUndoUserDataLen())
1534 			{
1535 				memcpy(&undoInfo, patternEditor->getUndoUserData(), sizeof(undoInfo));
1536 
1537 				assureCursorVisible();
1538 				setScrollbarPositions(undoInfo.startIndex, undoInfo.startPos);
1539 				validate();
1540 				parentScreen->paintControl(this, false);
1541 				notifyUpdate();
1542 			}
1543 			break;
1544 		default:
1545 			break;
1546 	}
1547 }
1548 
1549