1 // for finding memory leaks in debug mode with Visual Studio
2 #if defined _DEBUG && defined _MSC_VER
3 #include <crtdbg.h>
4 #endif
5 
6 #include <stdio.h>
7 #include <stdbool.h>
8 #include "ft2_header.h"
9 #include "ft2_gui.h"
10 #include "ft2_video.h"
11 #include "scopes/ft2_scopes.h"
12 #include "ft2_help.h"
13 #include "ft2_sample_ed.h"
14 #include "ft2_inst_ed.h"
15 #include "ft2_pattern_ed.h"
16 #include "ft2_mouse.h"
17 #include "ft2_config.h"
18 #include "ft2_diskop.h"
19 #include "ft2_audioselector.h"
20 #include "ft2_midi.h"
21 #include "ft2_bmp.h"
22 #include "ft2_structs.h"
23 
24 #define NUM_CURSORS 6
25 
26 mouse_t mouse; // globalized
27 
28 static bool mouseBusyGfxBackwards;
29 static int16_t mouseShape;
30 static int32_t mouseModeGfxOffs, mouseBusyGfxFrame;
31 static SDL_Cursor *cursors[NUM_CURSORS];
32 
setSystemCursor(SDL_Cursor * cur)33 static bool setSystemCursor(SDL_Cursor *cur)
34 {
35 	if (cur == NULL)
36 	{
37 		SDL_SetCursor(SDL_GetDefaultCursor());
38 		return false;
39 	}
40 
41 	SDL_SetCursor(cur);
42 	return true;
43 }
44 
freeMouseCursors(void)45 void freeMouseCursors(void)
46 {
47 	SDL_SetCursor(SDL_GetDefaultCursor());
48 	for (int32_t i = 0; i < NUM_CURSORS; i++)
49 	{
50 		if (cursors[i] != NULL)
51 		{
52 			SDL_FreeCursor(cursors[i]);
53 			cursors[i] = NULL;
54 		}
55 	}
56 }
57 
createMouseCursors(void)58 bool createMouseCursors(void) // creates scaled SDL surfaces for current mouse pointer shape
59 {
60 	freeMouseCursors();
61 
62 	const uint8_t *cursorsSrc = bmp.mouseCursors;
63 	switch (config.mouseType)
64 	{
65 		case MOUSE_IDLE_SHAPE_NICE: cursorsSrc += 0 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
66 		case MOUSE_IDLE_SHAPE_UGLY: cursorsSrc += 1 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
67 		case MOUSE_IDLE_SHAPE_AWFUL: cursorsSrc += 2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
68 		case MOUSE_IDLE_SHAPE_USABLE: cursorsSrc += 3 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
69 		default: break;
70 	}
71 
72 	for (int32_t i = 0; i < NUM_CURSORS; i++)
73 	{
74 		const int32_t scaleFactor = video.yScale;
75 
76 		SDL_Surface *surface = SDL_CreateRGBSurface(0, MOUSE_CURSOR_W*scaleFactor, MOUSE_CURSOR_H*scaleFactor, 32, 0, 0, 0, 0);
77 		if (surface == NULL)
78 		{
79 			freeMouseCursors();
80 			config.specialFlags2 &= ~HARDWARE_MOUSE; // enable software mouse
81 			SDL_ShowCursor(SDL_FALSE);
82 			return false;
83 		}
84 
85 		const uint32_t colorkey = SDL_MapRGB(surface->format, 0x00, 0xFF, 0x00); // colorkey
86 		const uint32_t fg = SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF); // foreground
87 		const uint32_t border = SDL_MapRGB(surface->format, 0x00, 0x00, 0x00); // border
88 
89 		SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
90 		SDL_SetColorKey(surface, SDL_TRUE, colorkey);
91 		SDL_SetSurfaceRLE(surface, SDL_TRUE);
92 
93 		const uint8_t *srcPixels8;
94 		if (i == 3) // text edit cursor
95 			srcPixels8 = &bmp.mouseCursors[12 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)];
96 		else if (i == 4) // mouse busy (wall clock)
97 			srcPixels8 = &bmp.mouseCursorBusyClock[2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]; // pick a good still-frame
98 		else if (i == 5) // mouse busy (hourglass)
99 			srcPixels8 = &bmp.mouseCursorBusyGlass[2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]; // pick a good still-frame
100 		else // normal idle cursor + disk op. "delete/rename" cursors
101 			srcPixels8 = &cursorsSrc[i * (4 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H))];
102 
103 		SDL_LockSurface(surface);
104 
105 		uint32_t *dstPixels32 = (uint32_t *)surface->pixels;
106 		for (int32_t k = 0; k < surface->w*surface->h; k++) // fill surface with colorkey pixels
107 			dstPixels32[k] = colorkey;
108 
109 		// blit upscaled cursor to surface
110 		for (int32_t y = 0; y < MOUSE_CURSOR_H; y++)
111 		{
112 			uint32_t *outX = &dstPixels32[(y * scaleFactor) * surface->w];
113 			for (int32_t yScale = 0; yScale < scaleFactor; yScale++)
114 			{
115 				const uint8_t *srcPtr = &srcPixels8[y * MOUSE_CURSOR_W];
116 				for (int32_t x = 0; x < MOUSE_CURSOR_W; x++)
117 				{
118 					const uint8_t srcPix = srcPtr[x];
119 					if (srcPix != PAL_TRANSPR)
120 					{
121 						uint32_t pixel = colorkey;
122 						if (srcPix == PAL_MOUSEPT)
123 							pixel = fg;
124 						else if (srcPix == PAL_BCKGRND)
125 							pixel = border;
126 
127 						for (int32_t xScale = 0; xScale < scaleFactor; xScale++)
128 							outX[xScale] = pixel;
129 					}
130 
131 					outX += scaleFactor;
132 				}
133 			}
134 		}
135 		SDL_UnlockSurface(surface);
136 
137 		uint32_t hotX = 0;
138 		uint32_t hotY = 0;
139 
140 		if (i == 3) // text edit cursor bias
141 		{
142 			hotX = 2 * video.xScale;
143 			hotY = 6 * video.yScale;
144 		}
145 
146 		cursors[i] = SDL_CreateColorCursor(surface, hotX, hotY);
147 		if (cursors[i] == NULL)
148 		{
149 			SDL_FreeSurface(surface);
150 			freeMouseCursors();
151 			config.specialFlags2 &= ~HARDWARE_MOUSE; // enable software mouse
152 			SDL_ShowCursor(SDL_FALSE);
153 			return false;
154 		}
155 
156 		SDL_FreeSurface(surface);
157 	}
158 
159 	if (config.specialFlags2 & HARDWARE_MOUSE)
160 	{
161 		     if (mouse.mode == MOUSE_MODE_NORMAL) setSystemCursor(cursors[0]);
162 		else if (mouse.mode == MOUSE_MODE_DELETE) setSystemCursor(cursors[1]);
163 		else if (mouse.mode == MOUSE_MODE_RENAME) setSystemCursor(cursors[2]);
164 
165 		SDL_ShowCursor(SDL_TRUE);
166 	}
167 	else
168 	{
169 		SDL_ShowCursor(SDL_FALSE);
170 	}
171 
172 	return true;
173 }
174 
setMousePosToCenter(void)175 void setMousePosToCenter(void)
176 {
177 	if (video.fullscreen)
178 	{
179 		mouse.setPosX = video.displayW >> 1;
180 		mouse.setPosY = video.displayH >> 1;
181 	}
182 	else
183 	{
184 		mouse.setPosX = video.renderW >> 1;
185 		mouse.setPosY = video.renderH >> 1;
186 	}
187 
188 	mouse.setPosFlag = true;
189 }
190 
animateBusyMouse(void)191 void animateBusyMouse(void)
192 {
193 	if (config.mouseAnimType == MOUSE_BUSY_SHAPE_CLOCK)
194 	{
195 		if (config.specialFlags2 & HARDWARE_MOUSE)
196 		{
197 			setSystemCursor(cursors[4]);
198 			return;
199 		}
200 
201 		if ((editor.framesPassed % 7) == 6)
202 		{
203 			if (mouseBusyGfxBackwards)
204 			{
205 				if (--mouseBusyGfxFrame <= 0)
206 				{
207 					mouseBusyGfxFrame = 0;
208 					mouseBusyGfxBackwards = false;
209 				}
210 			}
211 			else
212 			{
213 				if (++mouseBusyGfxFrame >= MOUSE_CLOCK_ANI_FRAMES-1)
214 				{
215 					mouseBusyGfxFrame = MOUSE_CLOCK_ANI_FRAMES - 1;
216 					mouseBusyGfxBackwards = true;
217 				}
218 			}
219 
220 			changeSpriteData(SPRITE_MOUSE_POINTER,
221 				&bmp.mouseCursorBusyClock[(mouseBusyGfxFrame % MOUSE_CLOCK_ANI_FRAMES) * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]);
222 		}
223 	}
224 	else
225 	{
226 		if (config.specialFlags2 & HARDWARE_MOUSE)
227 		{
228 			setSystemCursor(cursors[5]);
229 			return;
230 		}
231 
232 		if ((editor.framesPassed % 5) == 4)
233 		{
234 			mouseBusyGfxFrame = (mouseBusyGfxFrame + 1) % MOUSE_GLASS_ANI_FRAMES;
235 
236 			changeSpriteData(SPRITE_MOUSE_POINTER,
237 				&bmp.mouseCursorBusyGlass[mouseBusyGfxFrame * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]);
238 		}
239 	}
240 }
241 
setMouseShape(int16_t shape)242 void setMouseShape(int16_t shape)
243 {
244 	const uint8_t *gfxPtr;
245 
246 	if (editor.busy)
247 	{
248 		if (config.mouseAnimType == MOUSE_BUSY_SHAPE_CLOCK)
249 			gfxPtr = &bmp.mouseCursorBusyClock[(mouseBusyGfxFrame % MOUSE_GLASS_ANI_FRAMES) * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)];
250 		else
251 			gfxPtr = &bmp.mouseCursorBusyGlass[(mouseBusyGfxFrame % MOUSE_CLOCK_ANI_FRAMES) * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)];
252 	}
253 	else
254 	{
255 		gfxPtr = &bmp.mouseCursors[mouseModeGfxOffs];
256 		switch (shape)
257 		{
258 			case MOUSE_IDLE_SHAPE_NICE:   gfxPtr +=  0 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
259 			case MOUSE_IDLE_SHAPE_UGLY:   gfxPtr +=  1 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
260 			case MOUSE_IDLE_SHAPE_AWFUL:  gfxPtr +=  2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
261 			case MOUSE_IDLE_SHAPE_USABLE: gfxPtr +=  3 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
262 			case MOUSE_IDLE_TEXT_EDIT:    gfxPtr += 12 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
263 			default: return;
264 		}
265 	}
266 
267 	mouseShape = shape;
268 	changeSpriteData(SPRITE_MOUSE_POINTER, gfxPtr);
269 
270 	if (config.specialFlags2 & HARDWARE_MOUSE)
271 	{
272 		     if (mouse.mode == MOUSE_MODE_NORMAL) setSystemCursor(cursors[0]);
273 		else if (mouse.mode == MOUSE_MODE_DELETE) setSystemCursor(cursors[1]);
274 		else if (mouse.mode == MOUSE_MODE_RENAME) setSystemCursor(cursors[2]);
275 	}
276 }
277 
setTextEditMouse(void)278 static void setTextEditMouse(void)
279 {
280 	setMouseShape(MOUSE_IDLE_TEXT_EDIT);
281 	mouse.xBias = -2;
282 	mouse.yBias = -6;
283 
284 	if (config.specialFlags2 & HARDWARE_MOUSE)
285 		setSystemCursor(cursors[3]);
286 }
287 
clearTextEditMouse(void)288 static void clearTextEditMouse(void)
289 {
290 	setMouseShape(config.mouseType);
291 	mouse.xBias = 0;
292 	mouse.yBias = 0;
293 
294 	if (config.specialFlags2 & HARDWARE_MOUSE)
295 		setSystemCursor(cursors[0]);
296 }
297 
changeCursorIfOverTextBoxes(void)298 static void changeCursorIfOverTextBoxes(void)
299 {
300 	int32_t i;
301 
302 	mouse.mouseOverTextBox = false;
303 	if (editor.busy || mouse.mode != MOUSE_MODE_NORMAL)
304 		return;
305 
306 	const int32_t mx = mouse.x;
307 	const int32_t my = mouse.y;
308 
309 	textBox_t *t = textBoxes;
310 	for (i = 0; i < NUM_TEXTBOXES; i++, t++)
311 	{
312 		if (ui.sysReqShown && i != 0) // Sys. Req can only have one (special) text box
313 			continue;
314 
315 		if (!t->visible)
316 			continue;
317 
318 		if (!t->changeMouseCursor && (!editor.editTextFlag || i != mouse.lastEditBox))
319 			continue; // some kludge of sorts
320 
321 		if (my >= t->y && my < t->y+t->h && mx >= t->x && mx < t->x+t->w)
322 		{
323 			mouse.mouseOverTextBox = true;
324 
325 			if (mouseShape != MOUSE_IDLE_TEXT_EDIT)
326 				setTextEditMouse();
327 
328 			return;
329 		}
330 	}
331 
332 	// we're not inside a text edit box, set back mouse cursor
333 	if (i == NUM_TEXTBOXES && mouseShape == MOUSE_IDLE_TEXT_EDIT)
334 		clearTextEditMouse();
335 }
336 
setMouseMode(uint8_t mode)337 void setMouseMode(uint8_t mode)
338 {
339 	switch (mode)
340 	{
341 		case MOUSE_MODE_NORMAL: { mouse.mode = mode; mouseModeGfxOffs = 0 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); } break;
342 		case MOUSE_MODE_DELETE: { mouse.mode = mode; mouseModeGfxOffs = 4 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); } break;
343 		case MOUSE_MODE_RENAME: { mouse.mode = mode; mouseModeGfxOffs = 8 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); } break;
344 
345 		default: return;
346 	}
347 
348 	setMouseShape(config.mouseType);
349 }
350 
resetMouseBusyAnimation(void)351 void resetMouseBusyAnimation(void)
352 {
353 	mouseBusyGfxBackwards = false;
354 	mouseBusyGfxFrame = 0;
355 }
356 
setMouseBusy(bool busy)357 void setMouseBusy(bool busy) // can be called from other threads
358 {
359 	if (busy)
360 	{
361 		ui.setMouseIdle = false;
362 		ui.setMouseBusy = true;
363 	}
364 	else
365 	{
366 		ui.setMouseBusy = false;
367 		ui.setMouseIdle = true;
368 	}
369 }
370 
mouseAnimOn(void)371 void mouseAnimOn(void)
372 {
373 	ui.setMouseBusy = false;
374 	ui.setMouseIdle = false;
375 
376 	editor.busy = true;
377 	setMouseShape(config.mouseAnimType);
378 }
379 
mouseAnimOff(void)380 void mouseAnimOff(void)
381 {
382 	ui.setMouseBusy = false;
383 	ui.setMouseIdle = false;
384 
385 	editor.busy = false;
386 	setMouseShape(config.mouseType);
387 }
388 
mouseWheelDecRow(void)389 static void mouseWheelDecRow(void)
390 {
391 	if (songPlaying)
392 		return;
393 
394 	int16_t row = editor.row - 1;
395 	if (row < 0)
396 		row = patternNumRows[editor.editPattern] - 1;
397 
398 	setPos(-1, row, true);
399 }
400 
mouseWheelIncRow(void)401 static void mouseWheelIncRow(void)
402 {
403 	if (songPlaying)
404 		return;
405 
406 	int16_t row = editor.row + 1;
407 	if (row >= patternNumRows[editor.editPattern])
408 		row = 0;
409 
410 	setPos(-1, row, true);
411 }
412 
mouseWheelHandler(bool directionUp)413 void mouseWheelHandler(bool directionUp)
414 {
415 	if (ui.sysReqShown || editor.editTextFlag)
416 		return;
417 
418 	if (ui.extended)
419 	{
420 		if (mouse.y <= 52)
421 		{
422 			     if (mouse.x <= 111) directionUp ? decSongPos() : incSongPos();
423 			else if (mouse.x >= 386) directionUp ?  decCurIns() :  incCurIns();
424 		}
425 		else
426 		{
427 			directionUp ? mouseWheelDecRow() : mouseWheelIncRow();
428 		}
429 
430 		return;
431 	}
432 
433 	if (mouse.y < 173)
434 	{
435 		// top screens
436 
437 		if (ui.helpScreenShown)
438 		{
439 			// help screen
440 
441 			if (directionUp)
442 			{
443 				helpScrollUp();
444 				helpScrollUp();
445 			}
446 			else
447 			{
448 				helpScrollDown();
449 				helpScrollDown();
450 			}
451 		}
452 		else if (ui.diskOpShown)
453 		{
454 			// disk op - 3x speed
455 			if (mouse.x <= 355)
456 			{
457 				if (directionUp)
458 				{
459 					pbDiskOpListUp();
460 					pbDiskOpListUp();
461 					pbDiskOpListUp();
462 				}
463 				else
464 				{
465 					pbDiskOpListDown();
466 					pbDiskOpListDown();
467 					pbDiskOpListDown();
468 				}
469 			}
470 		}
471 		else if (ui.configScreenShown)
472 		{
473 			if (editor.currConfigScreen == CONFIG_SCREEN_IO_DEVICES)
474 			{
475 				// audio device selectors
476 				if (mouse.x >= 110 && mouse.x <= 355 && mouse.y <= 173)
477 				{
478 					if (mouse.y < 87)
479 						directionUp ? scrollAudOutputDevListUp() : scrollAudOutputDevListDown();
480 					else
481 						directionUp ? scrollAudInputDevListUp() : scrollAudInputDevListDown();
482 				}
483 			}
484 #ifdef HAS_MIDI
485 			else if (editor.currConfigScreen == CONFIG_SCREEN_MIDI_INPUT)
486 			{
487 				// midi input device selector
488 				if (mouse.x >= 110 && mouse.x <= 503 && mouse.y <= 173)
489 					directionUp ? scrollMidiInputDevListUp() : scrollMidiInputDevListDown();
490 			}
491 #endif
492 		}
493 
494 		if (!ui.aboutScreenShown  && !ui.helpScreenShown &&
495 			!ui.configScreenShown && !ui.nibblesShown)
496 		{
497 			if (mouse.x >= 421 && mouse.y <= 173)
498 			{
499 				     if (mouse.y <= 93) directionUp ? decCurIns() : incCurIns();
500 				else if (mouse.y >= 94) directionUp ? decCurSmp() : incCurSmp();
501 			}
502 			else if (!ui.diskOpShown && mouse.x <= 111 && mouse.y <= 76)
503 			{
504 				directionUp ? decSongPos() : incSongPos();
505 			}
506 		}
507 	}
508 	else
509 	{
510 		// bottom screens
511 
512 		if (ui.sampleEditorShown)
513 		{
514 			if (mouse.y >= 174 && mouse.y <= 328)
515 				directionUp ? mouseZoomSampleDataIn() : mouseZoomSampleDataOut();
516 		}
517 		else if (ui.patternEditorShown)
518 		{
519 			directionUp ? mouseWheelDecRow() : mouseWheelIncRow();
520 		}
521 	}
522 }
523 
testSamplerDataMouseDown(void)524 static bool testSamplerDataMouseDown(void)
525 {
526 	if (ui.sampleEditorShown && mouse.y >= 174 && mouse.y <= 327 && ui.sampleDataOrLoopDrag == -1)
527 	{
528 		handleSampleDataMouseDown(false);
529 		return true;
530 	}
531 
532 	return false;
533 }
534 
testPatternDataMouseDown(void)535 static bool testPatternDataMouseDown(void)
536 {
537 	if (ui.patternEditorShown)
538 	{
539 		const int32_t y1 = ui.extended ? 56 : 176;
540 		const int32_t y2 = ui.pattChanScrollShown ? 382 : 396;
541 
542 		if (mouse.y >= y1 && mouse.y <= y2 && mouse.x >= 29 && mouse.x <= 602)
543 		{
544 			handlePatternDataMouseDown(false);
545 			return true;
546 		}
547 	}
548 
549 	return false;
550 }
551 
mouseButtonUpHandler(uint8_t mouseButton)552 void mouseButtonUpHandler(uint8_t mouseButton)
553 {
554 	if (mouseButton == SDL_BUTTON_LEFT)
555 	{
556 		mouse.leftButtonPressed = false;
557 		mouse.leftButtonReleased = true;
558 
559 		if (ui.leftLoopPinMoving)
560 		{
561 			setLeftLoopPinState(false);
562 			ui.leftLoopPinMoving = false;
563 		}
564 
565 		if (ui.rightLoopPinMoving)
566 		{
567 			setRightLoopPinState(false);
568 			ui.rightLoopPinMoving = false;
569 		}
570 	}
571 	else if (mouseButton == SDL_BUTTON_RIGHT)
572 	{
573 		mouse.rightButtonPressed = false;
574 		mouse.rightButtonReleased = true;
575 
576 		if (editor.editSampleFlag)
577 		{
578 			// right mouse button released after hand-editing sample data
579 			if (instr[editor.curInstr] != NULL)
580 				fixSample(&instr[editor.curInstr]->smp[editor.curSmp]);
581 
582 			resumeAudio();
583 
584 			if (ui.sampleEditorShown)
585 				writeSample(true);
586 
587 			setSongModifiedFlag();
588 
589 			editor.editSampleFlag = false;
590 		}
591 	}
592 
593 	mouse.firstTimePressingButton = false;
594 	mouse.buttonCounter = 0;
595 	editor.textCursorBlinkCounter = 0;
596 
597 	// if we used both mouse button at the same time and released *one*, don't release GUI object
598 	if ( mouse.leftButtonPressed && !mouse.rightButtonPressed) return;
599 	if (!mouse.leftButtonPressed &&  mouse.rightButtonPressed) return;
600 
601 	if (ui.sampleEditorShown)
602 		testSmpEdMouseUp();
603 
604 	mouse.lastX = 0;
605 	mouse.lastY = 0;
606 
607 	ui.sampleDataOrLoopDrag = -1;
608 
609 	// check if we released a GUI object
610 	testDiskOpMouseRelease();
611 	testPushButtonMouseRelease(true);
612 	testCheckBoxMouseRelease();
613 	testScrollBarMouseRelease();
614 	testRadioButtonMouseRelease();
615 
616 	// revert "delete/rename" mouse modes (disk op.)
617 	if (mouse.mode != MOUSE_MODE_NORMAL)
618 	{
619 		if (mouse.lastUsedObjectID != PB_DISKOP_DELETE && mouse.lastUsedObjectID != PB_DISKOP_RENAME)
620 			setMouseMode(MOUSE_MODE_NORMAL);
621 	}
622 
623 	mouse.lastUsedObjectID = OBJECT_ID_NONE;
624 	mouse.lastUsedObjectType = OBJECT_NONE;
625 }
626 
mouseButtonDownHandler(uint8_t mouseButton)627 void mouseButtonDownHandler(uint8_t mouseButton)
628 {
629 	// if already holding left button and clicking right, don't do mouse down handling
630 	if (mouseButton == SDL_BUTTON_RIGHT && mouse.leftButtonPressed)
631 	{
632 		if (ui.sampleDataOrLoopDrag == -1)
633 		{
634 			mouse.rightButtonPressed = true;
635 			mouse.rightButtonReleased = false;
636 		}
637 
638 		// kludge - we must do scope solo/unmute all here
639 		if (!ui.sysReqShown)
640 			testScopesMouseDown();
641 
642 		return;
643 	}
644 
645 	// if already holding right button and clicking left, don't do mouse down handling
646 	if (mouseButton == SDL_BUTTON_LEFT && mouse.rightButtonPressed)
647 	{
648 		if (ui.sampleDataOrLoopDrag == -1)
649 		{
650 			mouse.leftButtonPressed = true;
651 			mouse.leftButtonReleased = false;
652 		}
653 
654 		// kludge - we must do scope solo/unmute all here
655 		if (!ui.sysReqShown)
656 			testScopesMouseDown();
657 
658 		return;
659 	}
660 
661 	// mouse 0,0 = open exit dialog
662 	if (mouse.x == 0 && mouse.y == 0)
663 	{
664 		if (quitBox(false) == 1)
665 			editor.throwExit = true;
666 
667 		// release button presses from okBox()
668 		mouse.leftButtonPressed = false;
669 		mouse.rightButtonPressed = false;
670 		mouse.leftButtonReleased = false;
671 		mouse.rightButtonReleased = false;
672 
673 		return;
674 	}
675 
676 	if (mouseButton == SDL_BUTTON_LEFT)
677 		mouse.leftButtonPressed = true;
678 	else if (mouseButton == SDL_BUTTON_RIGHT)
679 		mouse.rightButtonPressed = true;
680 
681 	mouse.leftButtonReleased = false;
682 	mouse.rightButtonReleased = false;
683 
684 	// don't do mouse down testing here if we already are using an object
685 	if (mouse.lastUsedObjectType != OBJECT_NONE)
686 		return;
687 
688 	// kludge #2
689 	if (mouse.lastUsedObjectType != OBJECT_PUSHBUTTON && mouse.lastUsedObjectID != OBJECT_ID_NONE)
690 		return;
691 
692 	// kludge #3 :(
693 	if (!mouse.rightButtonPressed)
694 		mouse.lastUsedObjectID = OBJECT_ID_NONE;
695 
696 	// check if we pressed a GUI object
697 
698 	/* test objects like this - clickable things *never* overlap, so no need to test all
699 	** other objects if we clicked on one already
700 	*/
701 
702 	testInstrSwitcherMouseDown(); // kludge: allow right click to both change ins. and edit text
703 
704 	if (testTextBoxMouseDown()) return;
705 	if (testPushButtonMouseDown()) return;
706 	if (testCheckBoxMouseDown()) return;
707 	if (testScrollBarMouseDown()) return;
708 	if (testRadioButtonMouseDown()) return;
709 
710 	// at this point, we don't need to test more widgets if a system request is shown
711 	if (ui.sysReqShown)
712 		return;
713 
714 	if (testInstrVolEnvMouseDown(false)) return;
715 	if (testInstrPanEnvMouseDown(false)) return;
716 	if (testDiskOpMouseDown(false)) return;
717 	if (testPianoKeysMouseDown(false)) return;
718 	if (testSamplerDataMouseDown()) return;
719 	if (testPatternDataMouseDown()) return;
720 	if (testScopesMouseDown()) return;
721 	if (testAudioDeviceListsMouseDown()) return;
722 
723 #ifdef HAS_MIDI
724 	if (testMidiInputDeviceListMouseDown()) return;
725 #endif
726 }
727 
sendMouseButtonUpEvent(uint8_t button)728 static void sendMouseButtonUpEvent(uint8_t button)
729 {
730 	SDL_Event event;
731 
732 	memset(&event, 0, sizeof (event));
733 	event.type = SDL_MOUSEBUTTONUP;
734 	event.button.button = button;
735 
736 	SDL_PushEvent(&event);
737 }
738 
handleLastGUIObjectDown(void)739 void handleLastGUIObjectDown(void)
740 {
741 	if (mouse.lastUsedObjectType == OBJECT_NONE)
742 		return;
743 
744 	if (mouse.leftButtonPressed || mouse.rightButtonPressed)
745 	{
746 		if (mouse.lastUsedObjectID != OBJECT_ID_NONE)
747 		{
748 			switch (mouse.lastUsedObjectType)
749 			{
750 				case OBJECT_PUSHBUTTON: handlePushButtonsWhileMouseDown(); break;
751 				case OBJECT_RADIOBUTTON: handleRadioButtonsWhileMouseDown(); break;
752 				case OBJECT_CHECKBOX: handleCheckBoxesWhileMouseDown(); break;
753 				case OBJECT_SCROLLBAR: handleScrollBarsWhileMouseDown(); break;
754 				case OBJECT_TEXTBOX: handleTextBoxWhileMouseDown(); break;
755 				default: break;
756 			}
757 		}
758 		else
759 		{
760 			// test non-standard GUI elements
761 			switch (mouse.lastUsedObjectType)
762 			{
763 				case OBJECT_INSTRSWITCH: testInstrSwitcherMouseDown(); break;
764 				case OBJECT_PATTERNMARK: handlePatternDataMouseDown(true); break;
765 				case OBJECT_DISKOPLIST: testDiskOpMouseDown(true); break;
766 				case OBJECT_SMPDATA: handleSampleDataMouseDown(true); break;
767 				case OBJECT_PIANO: testPianoKeysMouseDown(true); break;
768 				case OBJECT_INSVOLENV: testInstrVolEnvMouseDown(true); break;
769 				case OBJECT_INSPANENV: testInstrPanEnvMouseDown(true); break;
770 				default: break;
771 			}
772 		}
773 
774 		/* Hack to send "mouse button up" events if we released the mouse button(s)
775 		** outside of the window...
776 		*/
777 		if (mouse.x < 0 || mouse.x >= SCREEN_W || mouse.y < 0 || mouse.y >= SCREEN_H)
778 		{
779 			if (mouse.leftButtonPressed && !(mouse.buttonState & SDL_BUTTON_LMASK))
780 				sendMouseButtonUpEvent(SDL_BUTTON_LEFT);
781 
782 			if (mouse.rightButtonPressed && !(mouse.buttonState & SDL_BUTTON_RMASK))
783 				sendMouseButtonUpEvent(SDL_BUTTON_RIGHT);
784 		}
785 	}
786 }
787 
updateMouseScaling(void)788 void updateMouseScaling(void)
789 {
790 	if (video.renderW > 0.0) video.fMouseXMul = (float)SCREEN_W / video.renderW;
791 	if (video.renderH > 0.0) video.fMouseYMul = (float)SCREEN_H / video.renderH;
792 }
793 
readMouseXY(void)794 void readMouseXY(void)
795 {
796 	int32_t mx, my, windowX, windowY;
797 
798 	if (mouse.setPosFlag)
799 	{
800 		if (!video.windowHidden)
801 			SDL_WarpMouseInWindow(video.window, mouse.setPosX, mouse.setPosY);
802 
803 		mouse.setPosFlag = false;
804 		return;
805 	}
806 
807 	if (video.useDesktopMouseCoords)
808 	{
809 		mouse.buttonState = SDL_GetGlobalMouseState(&mx, &my);
810 
811 		// convert desktop coords to window coords
812 		SDL_GetWindowPosition(video.window, &windowX, &windowY);
813 		mx -= windowX;
814 		my -= windowY;
815 	}
816 	else
817 	{
818 		// special mode for KMSDRM (XXX: Confirm that this still works...)
819 		mouse.buttonState = SDL_GetMouseState(&mx, &my);
820 	}
821 
822 	if (video.fullscreen)
823 	{
824 		// centered fullscreen mode (not stretched) needs further coord translation
825 		if (!(config.specialFlags2 & STRETCH_IMAGE))
826 		{
827 			// if software mouse is enabled, warp mouse inside render space
828 			if (!(config.specialFlags2 & HARDWARE_MOUSE))
829 			{
830 				bool warpMouse = false;
831 
832 				if (mx < video.renderX)
833 				{
834 					mx = video.renderX;
835 					warpMouse = true;
836 				}
837 				else if (mx >= video.renderX+video.renderW)
838 				{
839 					mx = (video.renderX + video.renderW) - 1;
840 					warpMouse = true;
841 				}
842 
843 				if (my < video.renderY)
844 				{
845 					my = video.renderY;
846 					warpMouse = true;
847 				}
848 				else if (my >= video.renderY+video.renderH)
849 				{
850 					my = (video.renderY + video.renderH) - 1;
851 					warpMouse = true;
852 				}
853 
854 				if (warpMouse)
855 					SDL_WarpMouseInWindow(video.window, mx, my);
856 			}
857 
858 			// convert fullscreen coords to window (centered image) coords
859 			mx -= video.renderX;
860 			my -= video.renderY;
861 		}
862 	}
863 
864 	// multiply coords by video upscaling factors (don't round)
865 	mouse.x = (int32_t)(mx * video.fMouseXMul);
866 	mouse.y = (int32_t)(my * video.fMouseYMul);
867 
868 	if (config.specialFlags2 & HARDWARE_MOUSE)
869 	{
870 		// hardware mouse mode (OS)
871 		hideSprite(SPRITE_MOUSE_POINTER);
872 	}
873 	else
874 	{
875 		// software mouse mode (FT2 mouse)
876 		setSpritePos(SPRITE_MOUSE_POINTER, mouse.x + mouse.xBias, mouse.y + mouse.yBias);
877 	}
878 
879 	changeCursorIfOverTextBoxes();
880 }
881