/*
 * Copyright 2006-2009, Stephan Aßmus <superstippi@gmx.de>.
 * All rights reserved. Distributed under the terms of the MIT License.
 */
 
#include "PadView.h"
 
#include <stdio.h>
 
#include <Directory.h>
#include <File.h>
#include <Application.h>
#include <Catalog.h>
#include <GroupLayout.h>
#include <MenuItem.h>
#include <Message.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <Screen.h>
#include <SpaceLayoutItem.h>
 
#include "LaunchButton.h"
#include "MainWindow.h"
#include "App.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "LaunchBox"
 
 
static bigtime_t sActivationDelay = 40000;
static const uint32 kIconSizes[] = { 16, 20, 24, 32, 40, 48, 64 };
 
 
enum {
	MSG_TOGGLE_LAYOUT			= 'tgll',
	MSG_SET_ICON_SIZE			= 'stis',
	MSG_SET_IGNORE_DOUBLECLICK	= 'strd'
};
 
 
PadView::PadView(const char* name)
	: BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE, NULL),
	  fDragging(false),
	  fClickTime(0),
	  fButtonLayout(new BGroupLayout(B_VERTICAL, 4)),
	  fIconSize(DEFAULT_ICON_SIZE)
{
	SetViewColor(B_TRANSPARENT_32_BIT);
	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
	get_click_speed(&sActivationDelay);
 
	fButtonLayout->SetInsets(2, 7, 2, 2);
	SetLayout(fButtonLayout);
}
 
 
PadView::~PadView()
{
}
 
 
void
PadView::Draw(BRect updateRect)
{
	rgb_color background = LowColor();
	rgb_color light = tint_color(background, B_LIGHTEN_MAX_TINT);
	rgb_color shadow = tint_color(background, B_DARKEN_2_TINT);
	BRect r(Bounds());
	BeginLineArray(4);
		AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), light);
		AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), light);
		AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), shadow);
		AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), shadow);
	EndLineArray();
	r.InsetBy(1.0, 1.0);
	StrokeRect(r, B_SOLID_LOW);
	r.InsetBy(1.0, 1.0);
	// dots along top
	BPoint dot = r.LeftTop();
	int32 current;
	int32 stop;
	BPoint offset;
	BPoint next;
	if (Orientation() == B_VERTICAL) {
		current = (int32)dot.x;
		stop = (int32)r.right;
		offset = BPoint(0, 1);
		next = BPoint(1, -4);
		r.top += 5.0;
	} else {
		current = (int32)dot.y;
		stop = (int32)r.bottom;
		offset = BPoint(1, 0);
		next = BPoint(-4, 1);
		r.left += 5.0;
	}
	int32 num = 1;
	while (current <= stop) {
		rgb_color col1;
		rgb_color col2;
		if (num == 1) {
			col1 = shadow;
			col2 = background;
		} else if (num == 2) {
			col1 = background;
			col2 = light;
		} else {
			col1 = background;
			col2 = background;
			num = 0;
		}
		SetHighColor(col1);
		StrokeLine(dot, dot, B_SOLID_HIGH);
		SetHighColor(col2);
		dot += offset;
		StrokeLine(dot, dot, B_SOLID_HIGH);
		dot += offset;
		StrokeLine(dot, dot, B_SOLID_LOW);
		dot += offset;
		SetHighColor(col1);
		StrokeLine(dot, dot, B_SOLID_HIGH);
		dot += offset;
		SetHighColor(col2);
		StrokeLine(dot, dot, B_SOLID_HIGH);
		// next pixel
		num++;
		dot += next;
		current++;
	}
	FillRect(r, B_SOLID_LOW);
}
 
 
void
PadView::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MSG_TOGGLE_LAYOUT:
			if (fButtonLayout->Orientation() == B_HORIZONTAL) {
				fButtonLayout->SetInsets(2, 7, 2, 2);
				fButtonLayout->SetOrientation(B_VERTICAL);
			} else {
				fButtonLayout->SetInsets(7, 2, 2, 2);
				fButtonLayout->SetOrientation(B_HORIZONTAL);
			}
			break;
 
		case MSG_SET_ICON_SIZE:
			uint32 size;
			if (message->FindInt32("size", (int32*)&size) == B_OK)
				SetIconSize(size);
			break;
 
		case MSG_SET_IGNORE_DOUBLECLICK:
			SetIgnoreDoubleClick(!IgnoreDoubleClick());
			break;
 
		default:
			BView::MessageReceived(message);
			break;
	}
}
 
 
void
PadView::MouseDown(BPoint where)
{
	BWindow* window = Window();
	if (window == NULL)
		return;
 
	BRegion region;
	GetClippingRegion(&region);
	if (!region.Contains(where))
		return;
 
	bool handle = true;
	for (int32 i = 0; BView* child = ChildAt(i); i++) {
		if (child->Frame().Contains(where)) {
			handle = false;
			break;
		}
	}
	if (!handle)
		return;
 
	BMessage* message = window->CurrentMessage();
	if (message == NULL)
		return;
 
	uint32 buttons;
	message->FindInt32("buttons", (int32*)&buttons);
	if (buttons & B_SECONDARY_MOUSE_BUTTON) {
		BRect r = Bounds();
		r.InsetBy(2.0, 2.0);
		r.top += 6.0;
		if (r.Contains(where)) {
			DisplayMenu(where);
		} else {
			// sends the window to the back
			window->Activate(false);
		}
	} else {
		if (system_time() - fClickTime < sActivationDelay) {
			window->Minimize(true);
			fClickTime = 0;
		} else {
			window->Activate();
			fDragOffset = ConvertToScreen(where) - window->Frame().LeftTop();
			fDragging = true;
			SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
			fClickTime = system_time();
		}
	}
}
 
 
void
PadView::MouseUp(BPoint where)
{
	if (BWindow* window = Window()) {
		uint32 buttons;
		window->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
		if (buttons & B_PRIMARY_MOUSE_BUTTON
			&& system_time() - fClickTime < sActivationDelay
			&& window->IsActive())
			window->Activate();
	}
	fDragging = false;
}
 
 
void
PadView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
{
	MainWindow* window = dynamic_cast<MainWindow*>(Window());
	if (window == NULL)
		return;
 
	if (fDragging) {
		window->MoveTo(ConvertToScreen(where) - fDragOffset);
	} else if (window->AutoRaise()) {
		where = ConvertToScreen(where);
		BScreen screen(window);
		BRect frame = screen.Frame();
		BRect windowFrame = window->Frame();
		if (where.x == frame.left || where.x == frame.right
			|| where.y == frame.top || where.y == frame.bottom) {
			BPoint position = window->ScreenPosition();
			bool raise = false;
			if (fabs(0.5 - position.x) > fabs(0.5 - position.y)) {
				// left or right border
				if (where.y >= windowFrame.top
					&& where.y <= windowFrame.bottom) {
					if (position.x < 0.5 && where.x == frame.left)
						raise = true;
					else if (position.x > 0.5 && where.x == frame.right)
						raise = true;
				}
			} else {
				// top or bottom border
				if (where.x >= windowFrame.left && where.x <= windowFrame.right) {
					if (position.y < 0.5 && where.y == frame.top)
						raise = true;
					else if (position.y > 0.5 && where.y == frame.bottom)
						raise = true;
				}
			}
			if (raise)
				window->Activate();
		}
	}
}
 
 
void
PadView::AddButton(LaunchButton* button, LaunchButton* beforeButton)
{
	button->SetIconSize(fIconSize);
 
	if (beforeButton)
		fButtonLayout->AddView(fButtonLayout->IndexOfView(beforeButton), button);
	else
		fButtonLayout->AddView(button);
 
	_NotifySettingsChanged();
}
 
 
bool
PadView::RemoveButton(LaunchButton* button)
{
	bool result = fButtonLayout->RemoveView(button);
	if (result)
		_NotifySettingsChanged();
	return result;
}
 
 
LaunchButton*
PadView::ButtonAt(int32 index) const
{
	BLayoutItem* item = fButtonLayout->ItemAt(index);
	if (item == NULL)
		return NULL;
	return dynamic_cast<LaunchButton*>(item->View());
}
 
 
void
PadView::DisplayMenu(BPoint where, LaunchButton* button) const
{
	MainWindow* window = dynamic_cast<MainWindow*>(Window());
	if (window == NULL)
		return;
 
	LaunchButton* nearestButton = button;
	if (!nearestButton) {
		// find the nearest button
		for (int32 i = 0; (nearestButton = ButtonAt(i)); i++) {
			if (nearestButton->Frame().top > where.y)
				break;
		}
	}
	BPopUpMenu* menu = new BPopUpMenu(B_TRANSLATE("launch popup"), false, false);
	// add button
	BMessage* message = new BMessage(MSG_ADD_SLOT);
	message->AddPointer("be:source", (void*)nearestButton);
	BMenuItem* item = new BMenuItem(B_TRANSLATE("Add button here"), message);
	item->SetTarget(window);
	menu->AddItem(item);
	// button options
	if (button) {
		// clear button
		message = new BMessage(MSG_CLEAR_SLOT);
		message->AddPointer("be:source", (void*)button);
		item = new BMenuItem(B_TRANSLATE("Clear button"), message);
		item->SetTarget(window);
		menu->AddItem(item);
		// remove button
		message = new BMessage(MSG_REMOVE_SLOT);
		message->AddPointer("be:source", (void*)button);
		item = new BMenuItem(B_TRANSLATE("Remove button"), message);
		item->SetTarget(window);
		menu->AddItem(item);
		// Open containing folder button
		if (button->Ref() != NULL) {	
			message = new BMessage(MSG_OPEN_CONTAINING_FOLDER);
			message->AddPointer("be:source", (void*)button);
			item = new BMenuItem(B_TRANSLATE("Open containing folder"), message);
			item->SetTarget(window);
			menu->AddItem(item);
		}
		// set button description
		if (button->Ref()) {
			message = new BMessage(MSG_SET_DESCRIPTION);
			message->AddPointer("be:source", (void*)button);
			item = new BMenuItem(B_TRANSLATE("Set description" B_UTF8_ELLIPSIS),
				message);
			item->SetTarget(window);
			menu->AddItem(item);
		}
	}
	menu->AddSeparatorItem();
	// window settings
	BMenu* settingsM = new BMenu(B_TRANSLATE("Settings"));
	settingsM->SetFont(be_plain_font);
 
	const char* toggleLayoutLabel;
	if (fButtonLayout->Orientation() == B_HORIZONTAL)
		toggleLayoutLabel = B_TRANSLATE("Vertical layout");
	else
		toggleLayoutLabel = B_TRANSLATE("Horizontal layout");
	item = new BMenuItem(toggleLayoutLabel, new BMessage(MSG_TOGGLE_LAYOUT));
	item->SetTarget(this);
	settingsM->AddItem(item);
 
	BMenu* iconSizeM = new BMenu(B_TRANSLATE("Icon size"));
	for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); i++) {
		uint32 iconSize = kIconSizes[i];
		message = new BMessage(MSG_SET_ICON_SIZE);
		message->AddInt32("size", iconSize);
		BString label;
		label << iconSize << " x " << iconSize;
		item = new BMenuItem(label.String(), message);
		item->SetTarget(this);
		item->SetMarked(IconSize() == iconSize);
		iconSizeM->AddItem(item);
	}
	settingsM->AddItem(iconSizeM);
 
	item = new BMenuItem(B_TRANSLATE("Ignore double-click"),
		new BMessage(MSG_SET_IGNORE_DOUBLECLICK));
	item->SetTarget(this);
	item->SetMarked(IgnoreDoubleClick());
	settingsM->AddItem(item);
 
	uint32 what = window->Look() == B_BORDERED_WINDOW_LOOK ? MSG_SHOW_BORDER : MSG_HIDE_BORDER;
	item = new BMenuItem(B_TRANSLATE("Show window border"), new BMessage(what));
	item->SetTarget(window);
	item->SetMarked(what == MSG_HIDE_BORDER);
	settingsM->AddItem(item);
 
	item = new BMenuItem(B_TRANSLATE("Autostart"), new BMessage(MSG_TOGGLE_AUTOSTART));
	item->SetTarget(be_app);
	item->SetMarked(((App*)be_app)->AutoStart());
	settingsM->AddItem(item);
 
	item = new BMenuItem(B_TRANSLATE("Auto-raise"), new BMessage(MSG_TOGGLE_AUTORAISE));
	item->SetTarget(window);
	item->SetMarked(window->AutoRaise());
	settingsM->AddItem(item);
 
	item = new BMenuItem(B_TRANSLATE("Show on all workspaces"), new BMessage(MSG_SHOW_ON_ALL_WORKSPACES));
	item->SetTarget(window);
	item->SetMarked(window->ShowOnAllWorkspaces());
	settingsM->AddItem(item);
 
	menu->AddItem(settingsM);
 
	menu->AddSeparatorItem();
 
	// pad commands
	BMenu* padM = new BMenu(B_TRANSLATE("Pad"));
	padM->SetFont(be_plain_font);
	// new pad
	item = new BMenuItem(B_TRANSLATE("New"), new BMessage(MSG_ADD_WINDOW));
	item->SetTarget(be_app);
	padM->AddItem(item);
	// new pad
	item = new BMenuItem(B_TRANSLATE("Clone"), new BMessage(MSG_ADD_WINDOW));
	item->SetTarget(window);
	padM->AddItem(item);
	padM->AddSeparatorItem();
	// close
	item = new BMenuItem(B_TRANSLATE("Close"), new BMessage(B_QUIT_REQUESTED));
	item->SetTarget(window);
	padM->AddItem(item);
	menu->AddItem(padM);
	// app commands
	BMenu* appM = new BMenu(B_TRANSLATE_SYSTEM_NAME("LaunchBox"));
	appM->SetFont(be_plain_font);
	// quit
	item = new BMenuItem(B_TRANSLATE("Quit"), new BMessage(B_QUIT_REQUESTED));
	item->SetTarget(be_app);
	appM->AddItem(item);
	menu->AddItem(appM);
	// finish popup
	menu->SetAsyncAutoDestruct(true);
	menu->SetFont(be_plain_font);
	where = ConvertToScreen(where);
	BRect mouseRect(where, where);
	mouseRect.InsetBy(-4.0, -4.0);
	menu->Go(where, true, false, mouseRect, true);
}
 
 
void
PadView::SetOrientation(enum orientation orientation)
{
	if (orientation == B_VERTICAL) {
		fButtonLayout->SetInsets(2, 7, 2, 2);
		fButtonLayout->SetOrientation(B_VERTICAL);
	} else {
		fButtonLayout->SetInsets(7, 2, 2, 2);
		fButtonLayout->SetOrientation(B_HORIZONTAL);
	}
	_NotifySettingsChanged();
}
 
 
enum orientation
PadView::Orientation() const
{
	return fButtonLayout->Orientation();
}
 
 
void
PadView::SetIconSize(uint32 size)
{
	if (size == fIconSize)
		return;
 
	fIconSize = size;
 
	for (int32 i = 0; LaunchButton* button = ButtonAt(i); i++)
		button->SetIconSize(fIconSize);
 
	_NotifySettingsChanged();
}
 
 
uint32
PadView::IconSize() const
{
	return fIconSize;
}
 
 
void
PadView::SetIgnoreDoubleClick(bool refuse)
{
	LaunchButton::SetIgnoreDoubleClick(refuse);
 
	_NotifySettingsChanged();
}
 
 
bool
PadView::IgnoreDoubleClick() const
{
	return LaunchButton::IgnoreDoubleClick();
}
 
 
void
PadView::_NotifySettingsChanged()
{
	be_app->PostMessage(MSG_SETTINGS_CHANGED);
}

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