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 "MilkyApplication.h"
22 
23 #include "KeyCodeMap.h"
24 #include "MidiSetup.h"
25 #include "MilkyWindow.h"
26 #include "Screen.h"
27 #include "Tracker.h"
28 #include "MilkySettings/SettingsMessages.h"
29 
30 #include <Deskbar.h>
31 #include <Entry.h>
32 #include <File.h>
33 #include <FindDirectory.h>
34 #include <Node.h>
35 #include <NodeInfo.h>
36 #include <Path.h>
37 #include <String.h>
38 
39 #include <stdio.h>
40 #include <string.h>
41 
42 
43 const char*	kMilkySignature	= "application/x-vnd.Milky-MilkyTracker";
44 
45 
MilkyApplication()46 MilkyApplication::MilkyApplication()
47 	:
48 	BApplication(kMilkySignature),
49 	fMilkyWindow(NULL),
50 	fTracker(NULL),
51 	fTrackerLock(0),
52 	fTrackerListening(false),
53 	fLaunchOpenFile(NULL),
54 	fClockThread(0)
55 {
56 }
57 
58 
~MilkyApplication()59 MilkyApplication::~MilkyApplication()
60 {
61 
62 }
63 
64 
65 void
ReadyToRun()66 MilkyApplication::ReadyToRun()
67 {
68 	status_t status;
69 
70 	// Setup tracker ----------------------------------------------------------
71 	fTracker = new Tracker();
72 	PPSize windowSize  = fTracker->getWindowSizeFromDatabase();
73 	int32  scaleFactor = fTracker->getScreenScaleFactorFromDatabase();
74 	bool   fullScreen  = fTracker->getFullScreenFlagFromDatabase();
75 	fTrackerLock = create_sem(1, "MilkyTracker lock");
76 
77 	if (fTrackerLock < B_OK)
78 		debugger("Could not create tracker lock");
79 
80 	// Setup main window ------------------------------------------------------
81 	fMilkyWindow = new MilkyWindow(BRect(100, 100,
82 		100 + windowSize.width, 100 + windowSize.height), scaleFactor,
83 		fullScreen, fTracker, fTrackerLock);
84 	fMilkyWindow->Show();
85 
86 	// Start tracker ----------------------------------------------------------
87 	fTracker->startUp();
88 	fTrackerListening = true;
89 
90 	if (fLaunchOpenFile != NULL) {
91 		_LoadFile(fLaunchOpenFile->String());
92 		delete fLaunchOpenFile;
93 		fLaunchOpenFile = NULL;
94 	}
95 
96 	// Start main clock -------------------------------------------------------
97 	fClockThread = spawn_thread(&_ClockThread, "Milky clock",
98 		B_URGENT_DISPLAY_PRIORITY, this);
99 
100 	if (fClockThread < B_OK)
101 		debugger("Could not setup clock thread");
102 
103 	status = resume_thread(fClockThread);
104 
105 	if (status < B_OK)
106 		debugger("Could not start clock thread");
107 
108 	// Start listening for MIDI input -----------------------------------------
109 	MidiSetup::StartMidi(fTracker, fTrackerLock);
110 
111 	// Load platform-specific settings from file ------------------------------
112 	_LoadPlatformSettings();
113 }
114 
115 
116 bool
QuitRequested()117 MilkyApplication::QuitRequested()
118 {
119 	// Stop raising events to the tracker
120 	fTrackerListening = false;
121 
122 	// Stop clock
123 	kill_thread(fClockThread);
124 
125 	// Stop listening to MIDI input
126 	MidiSetup::StopMidi();
127 
128 	// Nobody should be using the tracker now anymore
129 	delete_sem(fTrackerLock);
130 	delete fTracker;
131 
132 	return true;
133 }
134 
135 
136 // #pragma mark - Platform-specific settings
137 
138 
139 void
MessageReceived(BMessage * message)140 MilkyApplication::MessageReceived(BMessage* message)
141 {
142 	switch (message->what) {
143 		case kMsg_SwitchCommandControlToggled:
144 			_SetSwapCommandControl(message->FindBool("on"));
145 			break;
146 
147 		case kMsg_ChangeFullscreenResolutionToggled:
148 			fMilkyWindow->SetSwitchFullscreenResolution(
149 				message->FindBool("on"));
150 			break;
151 
152 		case kMsg_MidiInputSelected:
153 			MidiSetup::ConnectProducer(message->FindInt32("id"));
154 			break;
155 
156 		case kMsg_MidiRecordVelocityToggled:
157 			MidiSetup::SetRecordVelocity(message->FindBool("on"));
158 			break;
159 
160 		case kMsg_MidiVelocityAmplificationChanged:
161 			MidiSetup::SetVelocityAmplify(message->FindInt32("value"));
162 			break;
163 
164 		default:
165 			BApplication::MessageReceived(message);
166 	}
167 }
168 
169 
170 void
_LoadPlatformSettings()171 MilkyApplication::_LoadPlatformSettings()
172 {
173 	// Build path to settings file and check if it exists ---------------------
174 	BPath path;
175 	find_directory(B_USER_SETTINGS_DIRECTORY, &path);
176 	path.Append("MilkyTracker/platform_settings");
177 
178 	BEntry settingsEntry(path.Path());
179 	if (!settingsEntry.Exists())
180 		return;
181 
182 	// Settings file exists, try to open and unflatten into message -----------
183 	BFile settingsFile(path.Path(), B_READ_ONLY);
184 	if (settingsFile.InitCheck() != B_OK)
185 		return;
186 
187 	BMessage settings;
188 	status_t status = settings.Unflatten(&settingsFile);
189 	if (status != B_OK)
190 		return;
191 
192 	// Look for known settings and apply them  --------------------------------
193 	bool switchCommandControl;
194 	status = settings.FindBool("switch_command_control",
195 		&switchCommandControl);
196 	if (status == B_OK)
197 		_SetSwapCommandControl(switchCommandControl);
198 
199 	bool changeFullscreenResolution;
200 	status = settings.FindBool("change_fullscreen_resolution",
201 		&changeFullscreenResolution);
202 	if (status == B_OK)
203 		fMilkyWindow->SetSwitchFullscreenResolution(
204 			changeFullscreenResolution);
205 
206 	bool recordVelocity;
207 	status = settings.FindBool("record_velocity",
208 		&recordVelocity);
209 	if (status == B_OK)
210 		MidiSetup::SetRecordVelocity(recordVelocity);
211 
212 	int16 velocityAmplify;
213 	status = settings.FindInt16("velocity_amplify",
214 		&velocityAmplify);
215 	if (status == B_OK)
216 		MidiSetup::SetVelocityAmplify(velocityAmplify);
217 
218 	BString midiInputName;
219 	status = settings.FindString("midi_input", &midiInputName);
220 	if (status == B_OK)
221 		MidiSetup::ConnectProducer(midiInputName);
222 }
223 
224 
225 void
_SetSwapCommandControl(bool swap)226 MilkyApplication::_SetSwapCommandControl(bool swap)
227 {
228 	gSwapCommandControl = swap;
229 	if (swap) {
230 		gModifierDataCommand.modifier      = B_CONTROL_KEY;
231 		gModifierDataCommand.modifierLeft  = B_LEFT_CONTROL_KEY;
232 		gModifierDataCommand.modifierRight = B_RIGHT_CONTROL_KEY;
233 		gModifierDataControl.modifier      = B_COMMAND_KEY;
234 		gModifierDataControl.modifierLeft  = B_LEFT_COMMAND_KEY;
235 		gModifierDataControl.modifierRight = B_RIGHT_COMMAND_KEY;
236 	} else {
237 		gModifierDataCommand.modifier      = B_COMMAND_KEY;
238 		gModifierDataCommand.modifierLeft  = B_LEFT_COMMAND_KEY;
239 		gModifierDataCommand.modifierRight = B_RIGHT_COMMAND_KEY;
240 		gModifierDataControl.modifier      = B_CONTROL_KEY;
241 		gModifierDataControl.modifierLeft  = B_LEFT_CONTROL_KEY;
242 		gModifierDataControl.modifierRight = B_RIGHT_CONTROL_KEY;
243 	}
244 }
245 
246 
247 // #pragma mark - Loading files from argv and icon drop
248 
249 
250 void
ArgvReceived(int32 argc,char ** argv)251 MilkyApplication::ArgvReceived(int32 argc, char** argv)
252 {
253 	// We only care for the first command line argument, which can be a file
254 	// which we're going to try to open
255 	BEntry entry(argv[1]);
256 	if (entry.InitCheck() != B_OK)
257 		return;
258 	if (!entry.Exists()) {
259 		fprintf(stderr, "ERROR: file not found '%s'\n", argv[1]);
260 		return;
261 	}
262 
263 	entry_ref ref;
264 	entry.GetRef(&ref);
265 
266 	BMessage message(B_REFS_RECEIVED);
267 	message.AddRef("refs", &ref);
268 	RefsReceived(&message);
269 }
270 
271 
272 void
RefsReceived(BMessage * message)273 MilkyApplication::RefsReceived(BMessage* message)
274 {
275 	status_t status;
276 
277 	entry_ref ref;
278 	status = message->FindRef("refs", &ref);
279 	if (status != B_OK)
280 		return;
281 
282 	BPath path(&ref);
283 	if (path.InitCheck() != B_OK)
284 		return;
285 
286 	// Check that it's a file with a MIME type of the audio group
287 	BNode node(&ref);
288 	if (node.InitCheck() != B_OK)
289 		return;
290 	BNodeInfo nodeInfo(&node);
291 	char mimeType[B_MIME_TYPE_LENGTH];
292 	nodeInfo.GetType(mimeType);
293 	BString mimeTypeString(mimeType);
294 	if (mimeTypeString.FindFirst("audio/") != 0)
295 		return;
296 
297 	// If we are just launching the application (and thus the Tracker
298 	// is not initialized yet), defer the file load
299 	if (IsLaunching())
300 		fLaunchOpenFile = new BString(path.Path());
301 	else
302 		_LoadFile(path.Path());
303 }
304 
305 
306 void
_LoadFile(const char * path)307 MilkyApplication::_LoadFile(const char* path)
308 {
309 	fDragDroppedFile = PPSystemString(path);
310 	PPSystemString* pathStringPointer = &fDragDroppedFile;
311 	PPEvent event(eFileDragDropped, &pathStringPointer,
312 		sizeof(PPSystemString*));
313 
314 	fMilkyWindow->RaiseEvent(&event);
315 }
316 
317 
318 // #pragma mark - Clock generator
319 
320 
321 void
GenerateClock()322 MilkyApplication::GenerateClock()
323 {
324 	PPEvent event(eTimer);
325 	bigtime_t beginTime;
326 
327 	// Send clock events to the tracker every 15ms
328 	for(;;) {
329 		beginTime = system_time();
330 
331 		if (fTrackerListening) {
332 			acquire_sem(fTrackerLock);
333 			fMilkyWindow->TrackerScreen()->raiseEvent(&event);
334 			release_sem(fTrackerLock);
335 		}
336 
337 		snooze_until(beginTime + 20000, B_SYSTEM_TIMEBASE);
338 	}
339 }
340 
341 
342 status_t
_ClockThread(void * data)343 MilkyApplication::_ClockThread(void* data)
344 {
345 	MilkyApplication* app = (MilkyApplication*)data;
346 	app->GenerateClock();
347 
348 	return B_OK;
349 }
350