⛏️ index : haiku.git

/*
 * Copyright 2003-2008, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Jérôme Duval
 *		Oliver Ruiz Dorantes
 *		Atsushi Takamatsu
 */


#include "HWindow.h"
#include "HEventList.h"

#include <stdio.h>

#include <Alert.h>
#include <Application.h>
#include <Beep.h>
#include <Box.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <FindDirectory.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MediaFiles.h>
#include <MenuBar.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Node.h>
#include <NodeInfo.h>
#include <Path.h>
#include <PathFinder.h>
#include <Roster.h>
#include <ScrollView.h>
#include <Sound.h>
#include <StringView.h>

#include <fs_attr.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "HWindow"

static const char kSettingsFile[] = "Sounds_Settings";
extern const char* kPlayLabel;
extern const char* kStopLabel;


HWindow::HWindow(BRect rect, const char* name)
	:
	BWindow(rect, name, B_TITLED_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS),
	fFilePanel(NULL),
	fPlayButton(NULL),
	fPlayer(NULL)
{
	_InitGUI();

	// set default path
	BPathFinder pathFinder;
	BStringList paths;
	pathFinder.FindPaths(B_FIND_PATH_SOUNDS_DIRECTORY, paths);
	for (int i = 0; i < paths.CountStrings(); ++i) {
		BEntry entry(paths.StringAt(i));
		if (entry.Exists()) {
			entry.GetRef(&fPathRef);
			break;
		}
	}

	BPath path;
	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
		path.Append(kSettingsFile);
		BFile file(path.Path(), B_READ_ONLY);

		BMessage msg;
		if (file.InitCheck() == B_OK && msg.Unflatten(&file) == B_OK) {
			if (msg.FindRect("frame", &fFrame) == B_OK) {
				MoveTo(fFrame.LeftTop());
				ResizeTo(fFrame.Width(), fFrame.Height());
			}

			entry_ref ref;
			if (msg.FindRef("last_path", &ref) == B_OK) {
				BNode node(&ref);
				if (node.InitCheck() == B_OK && node.IsDirectory())
					fPathRef = ref;
			}
		}
	}

	fFilePanel = new SoundFilePanel(this);
	fFilePanel->SetTarget(this);
	BEntry entry(&fPathRef);
	if (entry.Exists())
		fFilePanel->SetPanelDirectory(&fPathRef);

	MoveOnScreen();
}


HWindow::~HWindow()
{
	delete fFilePanel;
	delete fPlayer;

	BPath path;
	BMessage msg;
	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
		path.Append(kSettingsFile);
		BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE);

		if (file.InitCheck() == B_OK) {
			msg.AddRect("frame", fFrame);
			msg.AddRef("last_path", &fPathRef);
			msg.Flatten(&file);
		}
	}
}


void
HWindow::DispatchMessage(BMessage* message, BHandler* handler)
{
	if (message->what == B_PULSE)
		_Pulse();
	BWindow::DispatchMessage(message, handler);
}


void
HWindow::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case M_OTHER_MESSAGE:
		{
			BMenuField* menufield
				= dynamic_cast<BMenuField*>(FindView("filemenu"));
			if (menufield == NULL)
				return;
			BMenu* menu = menufield->Menu();

			HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
			if (row != NULL) {
				BPath path(row->Path());
				if (path.InitCheck() != B_OK) {
					BMenuItem* item = menu->FindItem(B_TRANSLATE("<none>"));
					if (item != NULL)
						item->SetMarked(true);
				} else {
					BMenuItem* item = menu->FindItem(path.Leaf());
					if (item != NULL)
						item->SetMarked(true);
				}
			}
			fFilePanel->Show();
			break;
		}

		case B_CANCEL:
		{
			// reset file panel location
			fFilePanel->SetPanelDirectory(&fPathRef);
			break;
		}
		case B_SIMPLE_DATA:
		case B_REFS_RECEIVED:
		{
			entry_ref ref;
			HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
			if (message->FindRef("refs", &ref) == B_OK && row != NULL) {
				BMenuField* menufield
					= dynamic_cast<BMenuField*>(FindView("filemenu"));
				if (menufield == NULL)
					return;
				BMenu* menu = menufield->Menu();

				// add file item
				BMessage* msg = new BMessage(M_ITEM_MESSAGE);
				BPath path(&ref);
				msg->AddRef("refs", &ref);
				BMenuItem* menuitem = menu->FindItem(path.Leaf());
				if (menuitem == NULL)
					menu->AddItem(menuitem = new BMenuItem(path.Leaf(), msg), 0);
				// refresh item
				fEventList->SetPath(BPath(&ref).Path());
				// check file menu
				if (menuitem != NULL)
					menuitem->SetMarked(true);

				// save as last used path and set as file panel location
				path.GetParent(&path);
				get_ref_for_path(path.Path(), &fPathRef);
				fFilePanel->SetPanelDirectory(&fPathRef);
				fPlayButton->SetEnabled(true);
			}
			break;
		}

		case M_PLAY_MESSAGE:
		{
			HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
			if (row != NULL) {
				const char* path = row->Path();
				if (path != NULL) {
					entry_ref ref;
					::get_ref_for_path(path, &ref);
					delete fPlayer;
					fPlayer = new BFileGameSound(&ref, false);
					fPlayer->StartPlaying();
				}
			}
			break;
		}

		case M_STOP_MESSAGE:
		{
			if (fPlayer == NULL)
				break;
			if (fPlayer->IsPlaying()) {
				fPlayer->StopPlaying();
				delete fPlayer;
				fPlayer = NULL;
			}
			break;
		}

		case M_EVENT_CHANGED:
		{
			BMenuField* menufield
				= dynamic_cast<BMenuField*>(FindView("filemenu"));
			if (menufield == NULL)
				return;

			menufield->SetEnabled(true);

			const char* filePath;

			if (message->FindString("path", &filePath) == B_OK) {

				BMenu* menu = menufield->Menu();
				BPath path(filePath);

				if (path.InitCheck() != B_OK) {
					BMenuItem* item = menu->FindItem(B_TRANSLATE("<none>"));
					if (item != NULL)
						item->SetMarked(true);
				} else {
					BMenuItem* item = menu->FindItem(path.Leaf());
					if (item != NULL)
						item->SetMarked(true);
				}

				HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
				if (row != NULL) {
					menufield->SetEnabled(true);

					const char* path = row->Path();
					fPlayButton->SetEnabled(path != NULL && strcmp(path, "") != 0);
				} else {
					menufield->SetEnabled(false);
					fPlayButton->SetEnabled(false);
				}
			}
			break;
		}

		case M_ITEM_MESSAGE:
		{
			entry_ref ref;
			if (message->FindRef("refs", &ref) == B_OK) {
				fEventList->SetPath(BPath(&ref).Path());
				_UpdateZoomLimits();

				HEventRow* row = (HEventRow*)fEventList->CurrentSelection();
				fPlayButton->SetEnabled(row != NULL && row->Path() != NULL);
			}
			break;
		}

		case M_NONE_MESSAGE:
		{
			fPlayButton->SetEnabled(false);
			fEventList->SetPath(NULL);
			break;
		}

		default:
			BWindow::MessageReceived(message);
	}
}


bool
HWindow::QuitRequested()
{
	fFrame = Frame();

	fEventList->RemoveAll();
	be_app->PostMessage(B_QUIT_REQUESTED);
	return true;
}


void
HWindow::_InitGUI()
{
	fEventList = new HEventList();
	fEventList->SetType(BMediaFiles::B_SOUNDS);
	fEventList->SetSelectionMode(B_SINGLE_SELECTION_LIST);

	BMenu* menu = new BMenu("file");
	menu->SetRadioMode(true);
	menu->SetLabelFromMarked(true);
	menu->AddSeparatorItem();
	menu->AddItem(new BMenuItem(B_TRANSLATE("<none>"),
		new BMessage(M_NONE_MESSAGE)));
	menu->AddItem(new BMenuItem(B_TRANSLATE("Other" B_UTF8_ELLIPSIS),
		new BMessage(M_OTHER_MESSAGE)));

	BString label(B_TRANSLATE("Sound file:"));
	BMenuField* menuField = new BMenuField("filemenu", label, menu);
	menuField->SetDivider(menuField->StringWidth(label) + 10);

	BSize buttonsSize(be_plain_font->Size() * 2.5, be_plain_font->Size() * 2.5);

	BButton* stopbutton = new BButton("stop", kStopLabel, new BMessage(M_STOP_MESSAGE));
	stopbutton->SetEnabled(false);
	stopbutton->SetExplicitSize(buttonsSize);

	// We need at least one view to trigger B_PULSE_NEEDED events which we will
	// intercept in DispatchMessage to trigger the buttons enabling or disabling.
	stopbutton->SetFlags(stopbutton->Flags() | B_PULSE_NEEDED);

	fPlayButton = new BButton("play", kPlayLabel, new BMessage(M_PLAY_MESSAGE));
	fPlayButton->SetEnabled(false);
	fPlayButton->SetExplicitSize(buttonsSize);

	BLayoutBuilder::Group<>(this, B_VERTICAL)
		.SetInsets(B_USE_WINDOW_SPACING)
		.Add(fEventList)
		.AddGroup(B_HORIZONTAL)
			.Add(menuField)
			.AddGroup(B_HORIZONTAL, 0)
				.Add(fPlayButton)
				.Add(stopbutton)
			.End()
		.End();

	// setup file menu
	_SetupMenuField();
	BMenuItem* noneItem = menu->FindItem(B_TRANSLATE("<none>"));
	if (noneItem != NULL)
		noneItem->SetMarked(true);

	menuField->SetEnabled(false);

	_UpdateZoomLimits();
}


void
HWindow::_Pulse()
{
	BButton* stop = dynamic_cast<BButton*>(FindView("stop"));

	if (stop == NULL)
		return;

	if (fPlayer != NULL) {
		if (fPlayer->IsPlaying())
			stop->SetEnabled(true);
		else
			stop->SetEnabled(false);
	} else
		stop->SetEnabled(false);
}


void
HWindow::_SetupMenuField()
{
	BMenuField* menufield = dynamic_cast<BMenuField*>(FindView("filemenu"));
	if (menufield == NULL)
		return;
	BMenu* menu = menufield->Menu();
	int32 count = fEventList->CountRows();
	for (int32 i = 0; i < count; i++) {
		HEventRow* row = (HEventRow*)fEventList->RowAt(i);
		if (row == NULL)
			continue;

		BPath path(row->Path());
		if (path.InitCheck() != B_OK)
			continue;
		if (menu->FindItem(path.Leaf()))
			continue;

		BMessage* msg = new BMessage(M_ITEM_MESSAGE);
		entry_ref ref;
		::get_ref_for_path(path.Path(), &ref);
		msg->AddRef("refs", &ref);
		menu->AddItem(new BMenuItem(path.Leaf(), msg), 0);
	}

	directory_which whichDirectories[] = {
		B_SYSTEM_SOUNDS_DIRECTORY,
		B_SYSTEM_NONPACKAGED_SOUNDS_DIRECTORY,
		B_USER_SOUNDS_DIRECTORY,
		B_USER_NONPACKAGED_SOUNDS_DIRECTORY,
	};

	for (size_t i = 0;
		i < sizeof(whichDirectories) / sizeof(whichDirectories[0]); i++) {
		BPath path;
		BDirectory dir;
		BEntry entry;
		BPath item_path;

		status_t err = find_directory(whichDirectories[i], &path);
		if (err == B_OK)
			err = dir.SetTo(path.Path());
		while (err == B_OK) {
			err = dir.GetNextEntry(&entry, true);
			if (entry.InitCheck() != B_NO_ERROR)
				break;

			if (entry.IsDirectory())
				continue;

			entry.GetPath(&item_path);

			if (menu->FindItem(item_path.Leaf()))
				continue;

			BMessage* msg = new BMessage(M_ITEM_MESSAGE);
			entry_ref ref;
			::get_ref_for_path(item_path.Path(), &ref);
			msg->AddRef("refs", &ref);
			menu->AddItem(new BMenuItem(item_path.Leaf(), msg), 0);
		}
	}
}


void
HWindow::_UpdateZoomLimits()
{
	const float kInset = be_control_look->DefaultItemSpacing();

	BSize size = fEventList->PreferredSize();
	SetZoomLimits(size.width + 2 * kInset + B_V_SCROLL_BAR_WIDTH,
		size.height + 5 * kInset + 2 * B_H_SCROLL_BAR_HEIGHT
			+ 2 * be_plain_font->Size() * 2.5);
}