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