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
44static const char kSettingsFile[] = "Sounds_Settings";
45
46
47HWindow::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
76HWindow::~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
95void
96HWindow::DispatchMessage(BMessage* message, BHandler* handler)
97{
98	if (message->what == B_PULSE)
99		_Pulse();
100	BWindow::DispatchMessage(message, handler);
101}
102
103
104void
105HWindow::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
277bool
278HWindow::QuitRequested()
279{
280	fFrame = Frame();
281
282	fEventList->RemoveAll();
283	be_app->PostMessage(B_QUIT_REQUESTED);
284	return true;
285}
286
287
288void
289HWindow::_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
347void
348HWindow::_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
365void
366HWindow::_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
428void
429HWindow::_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