xref: /haiku/src/preferences/sounds/HWindow.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
1 /*
2  * Copyright 2003-2008, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Jérôme Duval
7  *		Oliver Ruiz Dorantes
8  *		Atsushi Takamatsu
9  */
10 
11 
12 #include "HWindow.h"
13 #include "HEventList.h"
14 
15 #include <stdio.h>
16 
17 #include <Alert.h>
18 #include <Application.h>
19 #include <Beep.h>
20 #include <Box.h>
21 #include <Button.h>
22 #include <Catalog.h>
23 #include <ControlLook.h>
24 #include <FindDirectory.h>
25 #include <fs_attr.h>
26 #include <LayoutBuilder.h>
27 #include <Locale.h>
28 #include <MediaFiles.h>
29 #include <MenuBar.h>
30 #include <MenuField.h>
31 #include <MenuItem.h>
32 #include <Node.h>
33 #include <NodeInfo.h>
34 #include <Path.h>
35 #include <Roster.h>
36 #include <ScrollView.h>
37 #include <StringView.h>
38 #include <Sound.h>
39 
40 
41 #undef B_TRANSLATION_CONTEXT
42 #define B_TRANSLATION_CONTEXT "HWindow"
43 
44 static const char kSettingsFile[] = "Sounds_Settings";
45 
46 
47 HWindow::HWindow(BRect rect, const char* name)
48 	:
49 	BWindow(rect, name, B_TITLED_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS),
50 	fFilePanel(NULL),
51 	fPlayButton(NULL),
52 	fPlayer(NULL)
53 {
54 	_InitGUI();
55 
56 	fFilePanel = new BFilePanel();
57 	fFilePanel->SetTarget(this);
58 
59 	BPath path;
60 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
61 		path.Append(kSettingsFile);
62 		BFile file(path.Path(), B_READ_ONLY);
63 
64 		BMessage msg;
65 		if (file.InitCheck() == B_OK && msg.Unflatten(&file) == B_OK
66 			&& msg.FindRect("frame", &fFrame) == B_OK) {
67 			MoveTo(fFrame.LeftTop());
68 			ResizeTo(fFrame.Width(), fFrame.Height());
69 		}
70 	}
71 
72 	MoveOnScreen();
73 }
74 
75 
76 HWindow::~HWindow()
77 {
78 	delete fFilePanel;
79 	delete fPlayer;
80 
81 	BPath path;
82 	BMessage msg;
83 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
84 		path.Append(kSettingsFile);
85 		BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE);
86 
87 		if (file.InitCheck() == B_OK) {
88 			msg.AddRect("frame", fFrame);
89 			msg.Flatten(&file);
90 		}
91 	}
92 }
93 
94 
95 void
96 HWindow::DispatchMessage(BMessage* message, BHandler* handler)
97 {
98 	if (message->what == B_PULSE)
99 		_Pulse();
100 	BWindow::DispatchMessage(message, handler);
101 }
102 
103 
104 void
105 HWindow::MessageReceived(BMessage* message)
106 {
107 	switch (message->what) {
108 		case M_OTHER_MESSAGE:
109 		{
110 			BMenuField* menufield
111 				= dynamic_cast<BMenuField*>(FindView("filemenu"));
112 			if (menufield == NULL)
113 				return;
114 			BMenu* menu = menufield->Menu();
115 
116 			HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
117 			if (row != NULL) {
118 				BPath path(row->Path());
119 				if (path.InitCheck() != B_OK) {
120 					BMenuItem* item = menu->FindItem(B_TRANSLATE("<none>"));
121 					if (item != NULL)
122 						item->SetMarked(true);
123 				} else {
124 					BMenuItem* item = menu->FindItem(path.Leaf());
125 					if (item != NULL)
126 						item->SetMarked(true);
127 				}
128 			}
129 			fFilePanel->Show();
130 			break;
131 		}
132 
133 		case B_SIMPLE_DATA:
134 		case B_REFS_RECEIVED:
135 		{
136 			entry_ref ref;
137 			HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
138 			if (message->FindRef("refs", &ref) == B_OK && row != NULL) {
139 				BMenuField* menufield
140 					= dynamic_cast<BMenuField*>(FindView("filemenu"));
141 				if (menufield == NULL)
142 					return;
143 				BMenu* menu = menufield->Menu();
144 
145 				// check audio file
146 				BNode node(&ref);
147 				BNodeInfo ninfo(&node);
148 				char type[B_MIME_TYPE_LENGTH + 1];
149 				ninfo.GetType(type);
150 				BMimeType mtype(type);
151 				BMimeType superType;
152 				mtype.GetSupertype(&superType);
153 				if (superType.Type() == NULL
154 					|| strcmp(superType.Type(), "audio") != 0) {
155 					beep();
156 					BAlert* alert = new BAlert("",
157 						B_TRANSLATE("This is not an audio file."),
158 						B_TRANSLATE("OK"), NULL, NULL,
159 						B_WIDTH_AS_USUAL, B_STOP_ALERT);
160 					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
161 					alert->Go();
162 					break;
163 				}
164 
165 				// add file item
166 				BMessage* msg = new BMessage(M_ITEM_MESSAGE);
167 				BPath path(&ref);
168 				msg->AddRef("refs", &ref);
169 				BMenuItem* menuitem = menu->FindItem(path.Leaf());
170 				if (menuitem == NULL)
171 					menu->AddItem(menuitem = new BMenuItem(path.Leaf(), msg), 0);
172 				// refresh item
173 				fEventList->SetPath(BPath(&ref).Path());
174 				// check file menu
175 				if (menuitem != NULL)
176 					menuitem->SetMarked(true);
177 
178 				fPlayButton->SetEnabled(true);
179 			}
180 			break;
181 		}
182 
183 		case M_PLAY_MESSAGE:
184 		{
185 			HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
186 			if (row != NULL) {
187 				const char* path = row->Path();
188 				if (path != NULL) {
189 					entry_ref ref;
190 					::get_ref_for_path(path, &ref);
191 					delete fPlayer;
192 					fPlayer = new BFileGameSound(&ref, false);
193 					fPlayer->StartPlaying();
194 				}
195 			}
196 			break;
197 		}
198 
199 		case M_STOP_MESSAGE:
200 		{
201 			if (fPlayer == NULL)
202 				break;
203 			if (fPlayer->IsPlaying()) {
204 				fPlayer->StopPlaying();
205 				delete fPlayer;
206 				fPlayer = NULL;
207 			}
208 			break;
209 		}
210 
211 		case M_EVENT_CHANGED:
212 		{
213 			BMenuField* menufield
214 				= dynamic_cast<BMenuField*>(FindView("filemenu"));
215 			if (menufield == NULL)
216 				return;
217 
218 			menufield->SetEnabled(true);
219 
220 			const char* filePath;
221 
222 			if (message->FindString("path", &filePath) == B_OK) {
223 
224 				BMenu* menu = menufield->Menu();
225 				BPath path(filePath);
226 
227 				if (path.InitCheck() != B_OK) {
228 					BMenuItem* item = menu->FindItem(B_TRANSLATE("<none>"));
229 					if (item != NULL)
230 						item->SetMarked(true);
231 				} else {
232 					BMenuItem* item = menu->FindItem(path.Leaf());
233 					if (item != NULL)
234 						item->SetMarked(true);
235 				}
236 
237 				HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
238 				if (row != NULL) {
239 					menufield->SetEnabled(true);
240 
241 					const char* path = row->Path();
242 					fPlayButton->SetEnabled(path != NULL && strcmp(path, "") != 0);
243 				} else {
244 					menufield->SetEnabled(false);
245 					fPlayButton->SetEnabled(false);
246 				}
247 			}
248 			break;
249 		}
250 
251 		case M_ITEM_MESSAGE:
252 		{
253 			entry_ref ref;
254 			if (message->FindRef("refs", &ref) == B_OK) {
255 				fEventList->SetPath(BPath(&ref).Path());
256 				_UpdateZoomLimits();
257 
258 				HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
259 				fPlayButton->SetEnabled(row != NULL && row->Path() != NULL);
260 			}
261 			break;
262 		}
263 
264 		case M_NONE_MESSAGE:
265 		{
266 			fPlayButton->SetEnabled(false);
267 			fEventList->SetPath(NULL);
268 			break;
269 		}
270 
271 		default:
272 			BWindow::MessageReceived(message);
273 	}
274 }
275 
276 
277 bool
278 HWindow::QuitRequested()
279 {
280 	fFrame = Frame();
281 
282 	fEventList->RemoveAll();
283 	be_app->PostMessage(B_QUIT_REQUESTED);
284 	return true;
285 }
286 
287 
288 void
289 HWindow::_InitGUI()
290 {
291 	fEventList = new HEventList();
292 	fEventList->SetType(BMediaFiles::B_SOUNDS);
293 	fEventList->SetSelectionMode(B_SINGLE_SELECTION_LIST);
294 
295 	BMenu* menu = new BMenu("file");
296 	menu->SetRadioMode(true);
297 	menu->SetLabelFromMarked(true);
298 	menu->AddSeparatorItem();
299 	menu->AddItem(new BMenuItem(B_TRANSLATE("<none>"),
300 		new BMessage(M_NONE_MESSAGE)));
301 	menu->AddItem(new BMenuItem(B_TRANSLATE("Other" B_UTF8_ELLIPSIS),
302 		new BMessage(M_OTHER_MESSAGE)));
303 
304 	BString label(B_TRANSLATE("Sound file:"));
305 	BMenuField* menuField = new BMenuField("filemenu", label, menu);
306 	menuField->SetDivider(menuField->StringWidth(label) + 10);
307 
308 	BSize buttonsSize(be_plain_font->Size() * 2.5, be_plain_font->Size() * 2.5);
309 
310 	BButton* stopbutton = new BButton("stop", "\xE2\x96\xA0",
311 		new BMessage(M_STOP_MESSAGE));
312 	stopbutton->SetEnabled(false);
313 	stopbutton->SetExplicitSize(buttonsSize);
314 
315 	// We need at least one view to trigger B_PULSE_NEEDED events which we will
316 	// intercept in DispatchMessage to trigger the buttons enabling or disabling.
317 	stopbutton->SetFlags(stopbutton->Flags() | B_PULSE_NEEDED);
318 
319 	fPlayButton = new BButton("play", "\xE2\x96\xB6",
320 		new BMessage(M_PLAY_MESSAGE));
321 	fPlayButton->SetEnabled(false);
322 	fPlayButton->SetExplicitSize(buttonsSize);
323 
324 	BLayoutBuilder::Group<>(this, B_VERTICAL)
325 		.SetInsets(B_USE_WINDOW_SPACING)
326 		.Add(fEventList)
327 		.AddGroup(B_HORIZONTAL)
328 			.Add(menuField)
329 			.AddGroup(B_HORIZONTAL, 0)
330 				.Add(fPlayButton)
331 				.Add(stopbutton)
332 			.End()
333 		.End();
334 
335 	// setup file menu
336 	_SetupMenuField();
337 	BMenuItem* noneItem = menu->FindItem(B_TRANSLATE("<none>"));
338 	if (noneItem != NULL)
339 		noneItem->SetMarked(true);
340 
341 	menuField->SetEnabled(false);
342 
343 	_UpdateZoomLimits();
344 }
345 
346 
347 void
348 HWindow::_Pulse()
349 {
350 	BButton* stop = dynamic_cast<BButton*>(FindView("stop"));
351 
352 	if (stop == NULL)
353 		return;
354 
355 	if (fPlayer != NULL) {
356 		if (fPlayer->IsPlaying())
357 			stop->SetEnabled(true);
358 		else
359 			stop->SetEnabled(false);
360 	} else
361 		stop->SetEnabled(false);
362 }
363 
364 
365 void
366 HWindow::_SetupMenuField()
367 {
368 	BMenuField* menufield = dynamic_cast<BMenuField*>(FindView("filemenu"));
369 	if (menufield == NULL)
370 		return;
371 	BMenu* menu = menufield->Menu();
372 	int32 count = fEventList->CountRows();
373 	for (int32 i = 0; i < count; i++) {
374 		HEventRow* row = (HEventRow*)fEventList->RowAt(i);
375 		if (row == NULL)
376 			continue;
377 
378 		BPath path(row->Path());
379 		if (path.InitCheck() != B_OK)
380 			continue;
381 		if (menu->FindItem(path.Leaf()))
382 			continue;
383 
384 		BMessage* msg = new BMessage(M_ITEM_MESSAGE);
385 		entry_ref ref;
386 		::get_ref_for_path(path.Path(), &ref);
387 		msg->AddRef("refs", &ref);
388 		menu->AddItem(new BMenuItem(path.Leaf(), msg), 0);
389 	}
390 
391 	directory_which whichDirectories[] = {
392 		B_SYSTEM_SOUNDS_DIRECTORY,
393 		B_SYSTEM_NONPACKAGED_SOUNDS_DIRECTORY,
394 		B_USER_SOUNDS_DIRECTORY,
395 		B_USER_NONPACKAGED_SOUNDS_DIRECTORY,
396 	};
397 
398 	for (size_t i = 0;
399 		i < sizeof(whichDirectories) / sizeof(whichDirectories[0]); i++) {
400 		BPath path;
401 		BDirectory dir;
402 		BEntry entry;
403 		BPath item_path;
404 
405 		status_t err = find_directory(whichDirectories[i], &path);
406 		if (err == B_OK)
407 			err = dir.SetTo(path.Path());
408 		while (err == B_OK) {
409 			err = dir.GetNextEntry(&entry, true);
410 			if (entry.InitCheck() != B_NO_ERROR)
411 				break;
412 
413 			entry.GetPath(&item_path);
414 
415 			if (menu->FindItem(item_path.Leaf()))
416 				continue;
417 
418 			BMessage* msg = new BMessage(M_ITEM_MESSAGE);
419 			entry_ref ref;
420 			::get_ref_for_path(item_path.Path(), &ref);
421 			msg->AddRef("refs", &ref);
422 			menu->AddItem(new BMenuItem(item_path.Leaf(), msg), 0);
423 		}
424 	}
425 }
426 
427 
428 void
429 HWindow::_UpdateZoomLimits()
430 {
431 	const float kInset = be_control_look->DefaultItemSpacing();
432 
433 	BSize size = fEventList->PreferredSize();
434 	SetZoomLimits(size.width + 2 * kInset + B_V_SCROLL_BAR_WIDTH,
435 		size.height + 5 * kInset + 2 * B_H_SCROLL_BAR_HEIGHT
436 			+ 2 * be_plain_font->Size() * 2.5);
437 }
438