/*
 * Copyright 2003-2018, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Jérôme Duval
 *		François Revol
 *		Marcus Overhagen
 *		Jonas Sundström
 *		Axel Dörfler, axeld@pinc-software.de.
 *		Stephan Aßmus <superstippi@gmx.de>
 *		Puck Meerburg, puck@puckipedia.nl
 */
 
 
//! Volume control, and media shortcuts in Deskbar
 
 
#include <new>
#include <stdio.h>
 
#include <Alert.h>
#include <Bitmap.h>
#include <Catalog.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <IconUtils.h>
#include <MenuItem.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Resources.h>
#include <Roster.h>
#include <String.h>
#include <StringView.h>
#include <TextView.h>
#include <ToolTip.h>
#include <ToolTipManager.h>
 
#include "desklink.h"
#include "MixerControl.h"
#include "VolumeWindow.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "MediaReplicant"
 
 
static const uint32 kMsgOpenMediaSettings = 'mese';
static const uint32 kMsgOpenSoundSettings = 'sose';
static const uint32 kMsgOpenMediaPlayer = 'omep';
static const uint32 kMsgToggleBeep = 'tdbp';
static const uint32 kMsgVolumeWhich = 'svwh';
 
static const char* kReplicantName = "MediaReplicant";
	// R5 name needed, Media prefs manel removes by name
 
static const char* kSettingsFile = "x-vnd.Haiku-desklink";
 
 
class VolumeToolTip : public BTextToolTip {
public:
	VolumeToolTip(int32 which = VOLUME_USE_MIXER)
		:
		BTextToolTip(""),
		fWhich(which)
	{
	}
 
	virtual ~VolumeToolTip()
	{
	}
 
	virtual void AttachedToWindow()
	{
		Update();
	}
 
	void SetWhich(int32 which)
	{
		fWhich = which;
	}
 
	void Update()
	{
		if (!Lock())
			return;
 
		BTextView* view = (BTextView*)View();
 
		if (fMuteMessage.Length() != 0)
			view->SetText(fMuteMessage.String());
		else {
			MixerControl control;
			control.Connect(fWhich);
 
			BString text;
			text.SetToFormat(B_TRANSLATE("%g dB"), control.Volume());
			view->SetText(text.String());
		}
		Unlock();
	}
 
	void SetMuteMessage(const char* message)
	{
		fMuteMessage = message == NULL ? "" : message;
	}
 
private:
	int32			fWhich;
	BString			fMuteMessage;
};
 
 
class MediaReplicant : public BView {
public:
							MediaReplicant(BRect frame, const char* name,
								uint32 resizeMask = B_FOLLOW_ALL,
								uint32 flags = B_WILL_DRAW | B_NAVIGABLE
									| B_PULSE_NEEDED);
							MediaReplicant(BMessage* archive);
 
	virtual					~MediaReplicant();
 
	// archiving overrides
	static	MediaReplicant*	Instantiate(BMessage* data);
	virtual	status_t		Archive(BMessage* data, bool deep = true) const;
 
	// BView overrides
	virtual void			AttachedToWindow();
	virtual void			MouseDown(BPoint point);
	virtual void			Draw(BRect updateRect);
	virtual void			MessageReceived(BMessage* message);
 
private:
			status_t		_LaunchByPath(const char* path);
			status_t		_LaunchBySignature(const char* signature);
			void			_Launch(const char* prettyName,
								const char* signature, directory_which base,
								const char* fileName);
			void			_LoadSettings();
			void			_SaveSettings();
			void			_Init();
			BBitmap*		_LoadIcon(BResources& resources, const char* name);
 
			void			_DisconnectMixer();
			status_t		_ConnectMixer();
 
			MixerControl*	fMixerControl;
 
			BBitmap*		fIcon;
			BBitmap*		fMutedIcon;
			VolumeWindow*	fVolumeSlider;
			bool 			fDontBeep;
				// don't beep on volume change
			int32 			fVolumeWhich;
				// which volume parameter to act on (Mixer/Phys.Output)
			bool				fMuted;
};
 
 
status_t
our_image(image_info& image)
{
	int32 cookie = 0;
	while (get_next_image_info(B_CURRENT_TEAM, &cookie, &image) == B_OK) {
		if ((char*)our_image >= (char*)image.text
			&& (char*)our_image <= (char*)image.text + image.text_size)
			return B_OK;
	}
 
	return B_ERROR;
}
 
 
//	#pragma mark -
 
 
MediaReplicant::MediaReplicant(BRect frame, const char* name,
		uint32 resizeMask, uint32 flags)
	:
	BView(frame, name, resizeMask, flags),
	fVolumeSlider(NULL),
	fMuted(false)
{
	_Init();
}
 
 
MediaReplicant::MediaReplicant(BMessage* message)
	:
	BView(message),
	fVolumeSlider(NULL),
	fMuted(false)
{
	_Init();
}
 
 
MediaReplicant::~MediaReplicant()
{
	delete fIcon;
	_SaveSettings();
	_DisconnectMixer();
}
 
 
MediaReplicant*
MediaReplicant::Instantiate(BMessage* data)
{
	if (!validate_instantiation(data, kReplicantName))
		return NULL;
 
	return new(std::nothrow) MediaReplicant(data);
}
 
 
status_t
MediaReplicant::Archive(BMessage* data, bool deep) const
{
	status_t status = BView::Archive(data, deep);
	if (status < B_OK)
		return status;
 
	return data->AddString("add_on", kAppSignature);
}
 
 
void
MediaReplicant::AttachedToWindow()
{
	AdoptParentColors();
 
	_ConnectMixer();
 
	BView::AttachedToWindow();
}
 
 
void
MediaReplicant::Draw(BRect rect)
{
	SetDrawingMode(B_OP_OVER);
	DrawBitmap(fMuted ? fMutedIcon : fIcon);
}
 
 
void
MediaReplicant::MouseDown(BPoint point)
{
	int32 buttons = B_PRIMARY_MOUSE_BUTTON;
	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
 
	BPoint where = ConvertToScreen(point);
 
	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
		BPopUpMenu* menu = new BPopUpMenu("", false, false);
		menu->SetFont(be_plain_font);
 
		menu->AddItem(new BMenuItem(
			B_TRANSLATE("Media preferences" B_UTF8_ELLIPSIS),
			new BMessage(kMsgOpenMediaSettings)));
		menu->AddItem(new BMenuItem(
			B_TRANSLATE("Sound preferences" B_UTF8_ELLIPSIS),
			new BMessage(kMsgOpenSoundSettings)));
 
		menu->AddSeparatorItem();
 
		menu->AddItem(new BMenuItem(B_TRANSLATE("Open MediaPlayer"),
			new BMessage(kMsgOpenMediaPlayer)));
 
		menu->AddSeparatorItem();
 
		BMenu* subMenu = new BMenu(B_TRANSLATE("Options"));
		menu->AddItem(subMenu);
 
		BMenuItem* item = new BMenuItem(B_TRANSLATE("Control physical output"),
			new BMessage(kMsgVolumeWhich));
		item->SetMarked(fVolumeWhich == VOLUME_USE_PHYS_OUTPUT);
		subMenu->AddItem(item);
 
		item = new BMenuItem(B_TRANSLATE("Beep"),
			new BMessage(kMsgToggleBeep));
		item->SetMarked(!fDontBeep);
		subMenu->AddItem(item);
 
		menu->SetTargetForItems(this);
		subMenu->SetTargetForItems(this);
 
		menu->Go(where, true, true, BRect(where - BPoint(4, 4),
			where + BPoint(4, 4)));
 
	} else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
		if (fMixerControl != NULL) {
			fMixerControl->SetMute(!fMuted);
			fMuted = fMixerControl->Mute();
			VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
			if (tip != NULL) {
				tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted"): NULL);
				tip->Update();
				ShowToolTip(tip);
			}
			Invalidate();
		}
 
	} else {
		// Show VolumeWindow
		fVolumeSlider = new VolumeWindow(BRect(where.x, where.y,
			where.x + 207, where.y + 19), fDontBeep, fVolumeWhich);
		fVolumeSlider->Show();
	}
}
 
 
void
MediaReplicant::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case kMsgOpenMediaPlayer:
			_Launch("MediaPlayer", "application/x-vnd.Haiku-MediaPlayer",
				B_SYSTEM_APPS_DIRECTORY, "MediaPlayer");
			break;
 
		case kMsgOpenMediaSettings:
			_Launch("Media Preferences", "application/x-vnd.Haiku-Media",
				B_SYSTEM_PREFERENCES_DIRECTORY, "Media");
			break;
 
		case kMsgOpenSoundSettings:
			_Launch("Sounds Preferences", "application/x-vnd.Haiku-Sounds",
				B_SYSTEM_PREFERENCES_DIRECTORY, "Sounds");
			break;
 
		case kMsgToggleBeep:
		{
			BMenuItem* item;
			if (message->FindPointer("source", (void**)&item) != B_OK)
				return;
 
			item->SetMarked(!item->IsMarked());
			fDontBeep = !item->IsMarked();
			break;
		}
 
		case kMsgVolumeWhich:
		{
			BMenuItem* item;
			if (message->FindPointer("source", (void**)&item) != B_OK)
				return;
 
			item->SetMarked(!item->IsMarked());
			fVolumeWhich = item->IsMarked()
				? VOLUME_USE_PHYS_OUTPUT : VOLUME_USE_MIXER;
 
			if (_ConnectMixer() != B_OK
				&& fVolumeWhich == VOLUME_USE_PHYS_OUTPUT) {
				// unable to switch to physical output
				item->SetMarked(false);
				fVolumeWhich = VOLUME_USE_MIXER;
				_ConnectMixer();
			}
 
			if (VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip())) {
				tip->SetWhich(fVolumeWhich);
				tip->Update();
			}
			break;
		}
 
		case B_MOUSE_WHEEL_CHANGED:
		{
			float deltaY;
			if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK
				&& deltaY != 0.0 && fMixerControl != NULL) {
				fMixerControl->ChangeVolumeBy(deltaY < 0 ? 6 : -6);
 
				VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
				if (tip != NULL) {
					tip->Update();
					ShowToolTip(tip);
				}
			}
			break;
		}
 
		case B_MEDIA_NEW_PARAMETER_VALUE:
		{
			if (fMixerControl != NULL && !fMixerControl->Connected())
				return;
 
			bool setMuted = fMixerControl->Mute();
			if (setMuted != fMuted) {
				fMuted = setMuted;
				VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
				if (tip != NULL) {
					tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted") : NULL);
					tip->Update();
				}
				Invalidate();
			}
			break;
		}
 
		case B_MEDIA_SERVER_STARTED:
			_ConnectMixer();
			break;
 
		case B_MEDIA_NODE_CREATED:
		{
			// It's not enough to wait for B_MEDIA_SERVER_STARTED message, as
			// the mixer will still be getting loaded by the media server
			media_node mixerNode;
			media_node_id mixerNodeID;
			BMediaRoster* roster = BMediaRoster::CurrentRoster();
			if (roster != NULL
				&& message->FindInt32("media_node_id",&mixerNodeID) == B_OK
				&& roster->GetNodeFor(mixerNodeID, &mixerNode) == B_OK) {
				if (mixerNode.kind == B_SYSTEM_MIXER) {
					_ConnectMixer();
					roster->ReleaseNode(mixerNode);
				}
			}
			break;
		}
 
		default:
			BView::MessageReceived(message);
			break;
	}
}
 
 
status_t
MediaReplicant::_LaunchByPath(const char* path)
{
	entry_ref ref;
	status_t status = get_ref_for_path(path, &ref);
	if (status != B_OK)
		return status;
 
	status = be_roster->Launch(&ref);
	if (status != B_ALREADY_RUNNING)
		return status;
 
	// The application runs already, bring it to front
 
	app_info appInfo;
	status = be_roster->GetAppInfo(&ref, &appInfo);
	if (status != B_OK)
		return status;
 
	return be_roster->ActivateApp(appInfo.team);
}
 
 
status_t
MediaReplicant::_LaunchBySignature(const char* signature)
{
	status_t status = be_roster->Launch(signature);
	if (status != B_ALREADY_RUNNING)
		return status;
 
	// The application runs already, bring it to front
 
	app_info appInfo;
	status = be_roster->GetAppInfo(signature, &appInfo);
	if (status != B_OK)
		return status;
 
	return be_roster->ActivateApp(appInfo.team);
}
 
 
void
MediaReplicant::_Launch(const char* prettyName, const char* signature,
	directory_which base, const char* fileName)
{
	BPath path;
	status_t status = find_directory(base, &path);
	if (status == B_OK)
		path.Append(fileName);
 
	// launch the application
	if (_LaunchBySignature(signature) != B_OK
		&& _LaunchByPath(path.Path()) != B_OK) {
		BString message = B_TRANSLATE("Couldn't launch ");
		message << prettyName;
 
		BAlert* alert = new BAlert(
			B_TRANSLATE_COMMENT("desklink", "Title of an alert box"), 
			message.String(), B_TRANSLATE("OK"));
		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
		alert->Go();
	}
}
 
 
void
MediaReplicant::_LoadSettings()
{
	fDontBeep = false;
	fVolumeWhich = VOLUME_USE_MIXER;
 
	BPath path;
	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK)
		return;
 
	path.Append(kSettingsFile);
 
	BFile settings(path.Path(), B_READ_ONLY);
	if (settings.InitCheck() < B_OK)
		return;
 
	BMessage msg;
	if (msg.Unflatten(&settings) < B_OK)
		return;
 
	msg.FindInt32("volwhich", &fVolumeWhich);
	msg.FindBool("dontbeep", &fDontBeep);
}
 
 
void
MediaReplicant::_SaveSettings()
{
	BPath path;
	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK)
		return;
 
	path.Append(kSettingsFile);
 
	BFile settings(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
	if (settings.InitCheck() < B_OK)
		return;
 
	BMessage msg('CNFG');
	msg.AddInt32("volwhich", fVolumeWhich);
	msg.AddBool("dontbeep", fDontBeep);
 
	ssize_t size = 0;
	msg.Flatten(&settings, &size);
}
 
 
void
MediaReplicant::_Init()
{
	image_info info;
	if (our_image(info) != B_OK)
		return;
 
	BFile file(info.name, B_READ_ONLY);
	if (file.InitCheck() != B_OK)
		return;
 
	BResources resources(&file);
	if (resources.InitCheck() != B_OK)
		return;
 
	fIcon = _LoadIcon(resources, "Speaker");
	fMutedIcon = _LoadIcon(resources, "SpeakerMuted");
 
	_LoadSettings();
 
	SetToolTip(new VolumeToolTip(fVolumeWhich));
}
 
 
BBitmap*
MediaReplicant::_LoadIcon(BResources& resources, const char* name)
{
	size_t size;
	const void* data = resources.LoadResource(B_VECTOR_ICON_TYPE, name, &size);
	if (data == NULL)
		return NULL;
 
	// Scale tray icon
	BBitmap* icon = new BBitmap(Bounds(), B_RGBA32);
	if (icon->InitCheck() != B_OK
		|| BIconUtils::GetVectorIcon((const uint8*)data, size, icon) != B_OK) {
		delete icon;
		return NULL;
	}
	return icon;
}
 
 
void
MediaReplicant::_DisconnectMixer()
{
	BMediaRoster* roster = BMediaRoster::CurrentRoster();
	if (roster == NULL)
		return;
 
	roster->StopWatching(this, B_MEDIA_SERVER_STARTED | B_MEDIA_NODE_CREATED);
 
	if (fMixerControl->MuteNode() != media_node::null) {
		roster->StopWatching(this, fMixerControl->MuteNode(),
			B_MEDIA_NEW_PARAMETER_VALUE);
	}
 
	delete fMixerControl;
	fMixerControl = NULL;
}
 
 
status_t
MediaReplicant::_ConnectMixer()
{
	_DisconnectMixer();
 
	BMediaRoster* roster = BMediaRoster::Roster();
	if (roster == NULL)
		return B_ERROR;
 
	roster->StartWatching(this, B_MEDIA_SERVER_STARTED | B_MEDIA_NODE_CREATED);
 
	fMixerControl = new MixerControl(fVolumeWhich);
 
	const char* errorString = NULL;
	float volume = 0.0;
	fMixerControl->Connect(fVolumeWhich, &volume, &errorString);
 
	if (errorString != NULL) {
		SetToolTip(errorString);
		return B_ERROR;
	}
 
	if (fMixerControl->MuteNode() != media_node::null) {
		roster->StartWatching(this, fMixerControl->MuteNode(),
			B_MEDIA_NEW_PARAMETER_VALUE);
		fMuted = fMixerControl->Mute();
	}
 
	return B_OK;
}
 
 
//	#pragma mark -
 
 
extern "C" BView*
instantiate_deskbar_item(float maxWidth, float maxHeight)
{
	return new MediaReplicant(BRect(0, 0, maxHeight - 1, maxHeight - 1),
		kReplicantName);
}
 

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.

V773 Visibility scope of the 'menu' pointer was exited without releasing the memory. A memory leak is possible.