1 /*
2  *  Copyright 2012 Julian Harnath
3  *
4  *  This file is part of Milkytracker.
5  *
6  *  Milkytracker is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  Milkytracker is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include "MilkyWindow.h"
22 
23 #include "DisplayDevice_Haiku.h"
24 #include "MilkyApplication.h"
25 #include "MilkyView.h"
26 #include "Screen.h"
27 #include "Tracker.h"
28 
29 #include <Application.h>
30 #include <Bitmap.h>
31 #include <Screen.h>
32 #include <String.h>
33 #include <limits.h>
34 
35 
MilkyWindow(BRect frame,int32 scaleFactor,bool fullScreen,Tracker * tracker,sem_id trackerLock)36 MilkyWindow::MilkyWindow(BRect frame, int32 scaleFactor, bool fullScreen,
37 	Tracker* tracker, sem_id trackerLock)
38 	:
39 	BWindow(frame, "", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE),
40 	fTracker(tracker),
41 	fTrackerLock(trackerLock),
42 	fEventThread(0),
43 	fEventPort(0),
44 	fSwitchFullscreenResolution(false)
45 {
46 	SetPulseRate(0);
47 
48 	status_t status	;
49 
50 	// Setup view and connect it to the tracker screen ------------------------
51 	fMilkyView = new MilkyView(Bounds(), scaleFactor, trackerLock);
52 	fDisplayDevice = new DisplayDevice_Haiku(fMilkyView, scaleFactor);
53 	// Important to add it _after_ creating the display device to prevent an
54 	// inter-task deadlock on fBitmapLock and the window's looper
55 	AddChild(fMilkyView);
56 
57 	fTrackerScreen = new PPScreen(fDisplayDevice, fTracker);
58 	fTracker->setScreen(fTrackerScreen);
59 
60 	// Enter fullscreen mode if requested -------------------------------------
61 	BScreen screen;
62 	screen.GetMode(&fOriginalMode);
63 
64 	if (fullScreen)
65 		SetFullScreen(true);
66 
67 	// Start event forwarding -------------------------------------------------
68 	fEventPort = create_port(64, "Milky event port");
69 
70 	fEventThread = spawn_thread(&_EventThread, "Milky event forwarder",
71 		B_DISPLAY_PRIORITY, this);
72 
73 	if (fEventPort < B_OK || fEventThread < B_OK)
74 		debugger("Could not setup event thread");
75 
76 	status = resume_thread(fEventThread);
77 
78 	if (status < B_OK)
79 		debugger("Could not start event thread");
80 }
81 
82 
~MilkyWindow()83 MilkyWindow::~MilkyWindow()
84 {
85 	delete fTrackerScreen;
86 	delete fDisplayDevice;
87 }
88 
89 
90 bool
QuitRequested()91 MilkyWindow::QuitRequested()
92 {
93 	// Do not allow quit while the tracker is still starting up
94 	// (that would crash it)
95 	if (be_app->IsLaunching())
96 		return false;
97 
98 	// Ask tracker to shutdown
99 	if (!fTracker->shutDown())
100 		return false;
101 
102 	PPEvent event(eAppQuit);
103 	RaiseEvent(&event);
104 
105 	MilkyApplication* milkyApplication = (MilkyApplication*)be_app;
106 	milkyApplication->TrackerListening() = false;
107 
108 	// Kill event forwarder
109 	kill_thread(fEventThread);
110 	close_port(fEventPort);
111 
112 	be_app->PostMessage(B_QUIT_REQUESTED);
113 	return true;
114 }
115 
116 
117 // #pragma mark - Event forwarding
118 
119 
120 void
MessageReceived(BMessage * message)121 MilkyWindow::MessageReceived(BMessage* message)
122 {
123 	switch (message->what) {
124 		case B_MOUSE_WHEEL_CHANGED: {
125 			float deltaX = message->FindFloat("be:wheel_delta_x");
126 			float deltaY = message->FindFloat("be:wheel_delta_y");
127 			fMilkyView->MouseWheelChanged(deltaX, deltaY);
128 		}	break;
129 
130 		case B_MODIFIERS_CHANGED: {
131 			int32 modifiers = message->FindInt32("modifiers");
132 			int32 oldModifiers = message->FindInt32("be:old_modifiers");
133 			fMilkyView->ModifiersChanged(oldModifiers, modifiers);
134 			break;
135 		}
136 
137 		default:
138 			BWindow::MessageReceived(message);
139 			break;
140 	}
141 }
142 
143 
144 void
DispatchMessage(BMessage * message,BHandler * target)145 MilkyWindow::DispatchMessage(BMessage* message, BHandler* target)
146 {
147 	// Special treatment for B_KEY_DOWN messages when command or control
148 	// are pressed: forward them to the Tracker which handles shortcuts itself
149 	if (message->what == B_KEY_DOWN) {
150 		int32 modifiers = message->FindInt32("modifiers");
151 		if (modifiers & (B_COMMAND_KEY | B_CONTROL_KEY)) {
152 			int32 rawChar = message->FindInt32("raw_char");
153 			fMilkyView->KeyDown((const char*)&rawChar, 1);
154 			return;
155 		}
156 	}
157 
158 	BWindow::DispatchMessage(message, target);
159 }
160 
161 
162 void
ForwardEvents()163 MilkyWindow::ForwardEvents()
164 {
165 	int32 messageCode;
166 	PPEvent event;
167 	ssize_t size;
168 	MilkyApplication* milkyApplication = (MilkyApplication*)be_app;
169 
170 	// Forward events received through the event port to the tracker
171 	for (;;) {
172 		size = read_port(fEventPort, &messageCode, &event, sizeof(PPEvent));
173 		if (size <= 0)
174 			debugger("Reading event port failed");
175 
176 		if (milkyApplication->TrackerListening()) {
177 			acquire_sem(fTrackerLock);
178 			fTrackerScreen->raiseEvent(&event);
179 			release_sem(fTrackerLock);
180 		}
181 	}
182 }
183 
184 
185 status_t
_EventThread(void * data)186 MilkyWindow::_EventThread(void* data)
187 {
188 	MilkyWindow* milkyWindow = (MilkyWindow*)data;
189 	milkyWindow->ForwardEvents();
190 
191 	return B_OK;
192 }
193 
194 
195 // #pragma mark - Fullscreen mode setting
196 
197 
198 bool
SetFullScreen(bool fullscreen)199 MilkyWindow::SetFullScreen(bool fullscreen)
200 {
201 	BScreen screen;
202 
203 	int milkyWidth   = fMilkyView->Bitmap()->Bounds().IntegerWidth();
204 	int milkyHeight  = fMilkyView->Bitmap()->Bounds().IntegerHeight();
205 
206 	if (fullscreen) {
207 		// Switch to fullscreen mode ------------------------------------------
208 		screen.GetMode(&fOriginalMode);
209 
210 		if (   fOriginalMode.virtual_width  == milkyWidth
211 		    && fOriginalMode.virtual_height == milkyHeight) {
212 			MoveTo(0, 0);
213 			ResizeTo(milkyWidth, milkyHeight);
214 			return true;
215 		}
216 
217 		display_mode* bestMatch = NULL;
218 		display_mode* modeList = NULL;
219 
220 		if (fSwitchFullscreenResolution) {
221 			// MilkyPlayer's screen resolution is different from the physical
222 			// screen resolution. Try to find a fitting mode.
223 			uint32 modeCount;
224 			screen.GetModeList(&modeList, &modeCount);
225 
226 			int tempWidth  = INT_MAX;
227 			int tempHeight = INT_MAX;
228 			for (int i = 0; i < modeCount; i++) {
229 				display_mode* mode = &modeList[i];
230 
231 				// Choose a colour space that is either identical to the
232 				// current one or our preferred one, 32 bit
233 				if (   mode->space != fOriginalMode.space
234 				    && mode->space != B_RGBA32)
235 					continue;
236 
237 				// Choose a mode with identical refresh rate than current mode
238 				if (_ModeRefreshRate(&fOriginalMode) != _ModeRefreshRate(mode))
239 					continue;
240 
241 				// If the mode is an exact match in resolution, take it
242 				if (   mode->virtual_width  == milkyWidth
243 				    && mode->virtual_height == milkyHeight) {
244 				    bestMatch = mode;
245 				    break;
246 				}
247 
248 				// Second best choice: a mode which is larger than necessary
249 				// Out of all larger modes, we want the smallest
250 				// (i.e. the tightest fit)
251 				if (   mode->virtual_width  > milkyWidth
252 				    && mode->virtual_height > milkyHeight
253 				    && mode->virtual_width  < tempWidth
254 				    && mode->virtual_height < tempHeight) {
255 				    bestMatch = mode;
256 				    tempWidth = mode->virtual_width;
257 				    tempHeight = mode->virtual_height;
258 				}
259 			}
260 
261 			if (bestMatch == NULL) {
262 				free(modeList);
263 				return false;
264 			}
265 
266 			screen.SetMode(bestMatch);
267 		} else
268 			bestMatch = &fOriginalMode;
269 
270 		// If no exact match was found, center milkyview on screen with a
271 		// black border around it
272 		if (   bestMatch->virtual_width  > milkyWidth
273 		    || bestMatch->virtual_height > milkyHeight) {
274 			fMilkyView->SetTopLeft(
275 				(bestMatch->virtual_width  - milkyWidth) / 2,
276 				(bestMatch->virtual_height - milkyHeight) / 2);
277 		}
278 
279 		if (modeList != NULL)
280 			free(modeList);
281 
282 		MoveTo(0, 0);
283 		ResizeTo(bestMatch->virtual_width, bestMatch->virtual_height);
284 		fMilkyView->Invalidate();
285 
286 		return true;
287 	} else {
288 		// Switch to window mode ----------------------------------------------
289 		if (fSwitchFullscreenResolution)
290 			screen.SetMode(&fOriginalMode);
291 		MoveTo(100, 100);
292 		ResizeTo(milkyWidth, milkyHeight);
293 		fMilkyView->SetTopLeft(0, 0);
294 		fMilkyView->Invalidate();
295 	}
296 }
297 
298 
299 uint32
_ModeRefreshRate(display_mode * mode)300 MilkyWindow::_ModeRefreshRate(display_mode* mode)
301 {
302 	return (1000 * (uint32)mode->timing.pixel_clock) /
303 		((uint32)mode->timing.h_total * (uint32)mode->timing.v_total);
304 }
305