/*
 * Copyright 2005-2010, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		Robert Polic
 *		Stephan Aßmus <superstippi@gmx.de>
 *
 * Copyright 1999, Be Incorporated.   All Rights Reserved.
 * This file may be used under the terms of the Be Sample Code License.
 */
 
#include "PersonWindow.h"
 
#include <stdio.h>
#include <string.h>
 
#include <Alert.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <ControlLook.h>
#include <FilePanel.h>
#include <FindDirectory.h>
#include <Font.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <NodeInfo.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <ScrollView.h>
#include <String.h>
#include <TextView.h>
#include <Volume.h>
 
#include "PeopleApp.h"
#include "PersonView.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "People"
 
 
PersonWindow::PersonWindow(BRect frame, const char* title,
		const char* nameAttribute, const char* categoryAttribute,
		const entry_ref* ref)
	:
	BWindow(frame, title, B_TITLED_WINDOW, B_NOT_ZOOMABLE
		| B_AUTO_UPDATE_SIZE_LIMITS),
	fRef(NULL),
	fPanel(NULL),
	fNameAttribute(nameAttribute)
{
	BMenu* menu;
	BMenuItem* item;
 
	BMenuBar* menuBar = new BMenuBar("");
	menu = new BMenu(B_TRANSLATE("File"));
	menu->AddItem(item = new BMenuItem(
		B_TRANSLATE("New person" B_UTF8_ELLIPSIS),
		new BMessage(M_NEW), 'N'));
	item->SetTarget(be_app);
	menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
		new BMessage(B_QUIT_REQUESTED), 'W'));
	menu->AddSeparatorItem();
	menu->AddItem(fSave = new BMenuItem(B_TRANSLATE("Save"),
		new BMessage(M_SAVE), 'S'));
	fSave->SetEnabled(FALSE);
	menu->AddItem(new BMenuItem(
		B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
		new BMessage(M_SAVE_AS)));
	menu->AddItem(fRevert = new BMenuItem(B_TRANSLATE("Revert"),
		new BMessage(M_REVERT), 'R'));
	fRevert->SetEnabled(FALSE);
	menu->AddSeparatorItem();
	item = new BMenuItem(B_TRANSLATE("Quit"),
		new BMessage(B_QUIT_REQUESTED), 'Q');
	item->SetTarget(be_app);
	menu->AddItem(item);
	menuBar->AddItem(menu);
 
	menu = new BMenu(B_TRANSLATE("Edit"));
	menu->AddItem(fUndo = new BMenuItem(B_TRANSLATE("Undo"),
		new BMessage(B_UNDO), 'Z'));
	fUndo->SetEnabled(false);
	menu->AddSeparatorItem();
	menu->AddItem(fCut = new BMenuItem(B_TRANSLATE("Cut"),
		new BMessage(B_CUT), 'X'));
	menu->AddItem(fCopy = new BMenuItem(B_TRANSLATE("Copy"),
		new BMessage(B_COPY), 'C'));
	menu->AddItem(fPaste = new BMenuItem(B_TRANSLATE("Paste"),
		new BMessage(B_PASTE), 'V'));
	BMenuItem* selectAllItem = new BMenuItem(B_TRANSLATE("Select all"),
		new BMessage(M_SELECT), 'A');
	menu->AddItem(selectAllItem);
	menu->AddSeparatorItem();
	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Configure attributes"),
		new BMessage(M_CONFIGURE_ATTRIBUTES), 'F'));
	item->SetTarget(be_app);
	menuBar->AddItem(menu);
 
	if (ref != NULL) {
		SetTitle(ref->name);
		_SetToRef(new entry_ref(*ref));
	} else
		_SetToRef(NULL);
 
	fView = new PersonView("PeopleView", categoryAttribute, fRef);
 
	BScrollView* scrollView = new BScrollView("PeopleScrollView", fView, 0,
		false, true, B_NO_BORDER);
	scrollView->SetExplicitMinSize(BSize(scrollView->MinSize().width, 0));
 
	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
		.SetInsets(0, 0, -1, 0)
		.Add(menuBar)
		.Add(scrollView);
 
	fRevert->SetTarget(fView);
	selectAllItem->SetTarget(fView);
}
 
 
PersonWindow::~PersonWindow()
{
	_SetToRef(NULL);
}
 
 
void
PersonWindow::MenusBeginning()
{
	bool enabled = !fView->IsSaved();
	fSave->SetEnabled(enabled);
	fRevert->SetEnabled(enabled);
 
	bool isRedo = false;
	bool undoEnabled = false;
	bool cutAndCopyEnabled = false;
 
	BTextView* textView = dynamic_cast<BTextView*>(CurrentFocus());
	if (textView != NULL) {
		undo_state state = textView->UndoState(&isRedo);
		undoEnabled = state != B_UNDO_UNAVAILABLE;
 
		cutAndCopyEnabled = fView->IsTextSelected();
	}
 
	if (isRedo)
		fUndo->SetLabel(B_TRANSLATE("Redo"));
	else
		fUndo->SetLabel(B_TRANSLATE("Undo"));
	fUndo->SetEnabled(undoEnabled);
	fCut->SetEnabled(cutAndCopyEnabled);
	fCopy->SetEnabled(cutAndCopyEnabled);
 
	be_clipboard->Lock();
	fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", B_MIME_TYPE));
	be_clipboard->Unlock();
 
	fView->BuildGroupMenu();
}
 
 
void
PersonWindow::MessageReceived(BMessage* msg)
{
	char			str[256];
	BDirectory		directory;
	BEntry			entry;
	BFile			file;
	BNodeInfo		*node;
 
	switch (msg->what) {
		case M_SAVE:
			if (!fRef) {
				SaveAs();
				break;
			}
			// supposed to fall through
		case M_REVERT:
		case M_SELECT:
			fView->MessageReceived(msg);
			break;
 
		case M_SAVE_AS:
			SaveAs();
			break;
 
		case B_UNDO: // fall through
		case B_CUT:
		case B_COPY:
		case B_PASTE:
		{
			BView* view = CurrentFocus();
			if (view != NULL)
				view->MessageReceived(msg);
			break;
		}
 
		case B_SAVE_REQUESTED:
		{
			entry_ref dir;
			if (msg->FindRef("directory", &dir) == B_OK) {
				const char* name = NULL;
				msg->FindString("name", &name);
				directory.SetTo(&dir);
				if (directory.InitCheck() == B_NO_ERROR) {
					directory.CreateFile(name, &file);
					if (file.InitCheck() == B_NO_ERROR) {
						node = new BNodeInfo(&file);
						node->SetType("application/x-person");
						delete node;
 
						directory.FindEntry(name, &entry);
						entry.GetRef(&dir);
						_SetToRef(new entry_ref(dir));
						SetTitle(fRef->name);
						fView->CreateFile(fRef);
					}
					else {
						sprintf(str, B_TRANSLATE("Could not create %s."), name);
						BAlert* alert = new BAlert("", str, B_TRANSLATE("Sorry"));
						alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
						alert->Go();
					}
				}
			}
			break;
		}
 
		case B_NODE_MONITOR:
		{
			int32 opcode;
			if (msg->FindInt32("opcode", &opcode) == B_OK) {
				switch (opcode) {
					case B_ENTRY_REMOVED:
						// We lost our file. Close the window.
						PostMessage(B_QUIT_REQUESTED);
						break;
 
					case B_ENTRY_MOVED:
					{
						// We may have renamed our entry. Obtain relevant data
						// from message.
						BString name;
						msg->FindString("name", &name);
 
						int64 directory;
						msg->FindInt64("to directory", &directory);
 
						int32 device;
						msg->FindInt32("device", &device);
 
						// Update our ref.
						delete fRef;
						fRef = new entry_ref(device, directory, name.String());
 
						// And our window title.
						SetTitle(name);
 
						// If moved to Trash, close window.
						BVolume volume(device);
						BPath trash;
						find_directory(B_TRASH_DIRECTORY, &trash, false,
							&volume);
						BPath folder(fRef);
						folder.GetParent(&folder);
						if (folder == trash)
							PostMessage(B_QUIT_REQUESTED);
 
						break;
					}
 
					case B_ATTR_CHANGED:
					{
						// An attribute was updated.
						BString attr;
						if (msg->FindString("attr", &attr) == B_OK)
							fView->SetAttribute(attr.String(), true);
						break;
					}
					case B_STAT_CHANGED:
						fView->UpdatePicture(fRef);
						break;
				}
			}
			break;
		}
 
		default:
			BWindow::MessageReceived(msg);
	}
}
 
 
bool
PersonWindow::QuitRequested()
{
	status_t result;
 
	if (!fView->IsSaved()) {
		BAlert* alert = new BAlert("",
			B_TRANSLATE("Save changes before closing?"), B_TRANSLATE("Cancel"),
			B_TRANSLATE("Don't save"), B_TRANSLATE("Save"),
			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
		alert->SetShortcut(0, B_ESCAPE);
		alert->SetShortcut(1, 'd');
		alert->SetShortcut(2, 's');
		result = alert->Go();
 
		if (result == 2) {
			if (fRef)
				fView->Save();
			else {
				SaveAs();
				return false;
			}
		} else if (result == 0)
			return false;
	}
 
	delete fPanel;
 
	BMessage message(M_WINDOW_QUITS);
	message.AddRect("frame", Frame());
	if (be_app->Lock()) {
		be_app->PostMessage(&message);
		be_app->Unlock();
	}
 
	return true;
}
 
 
void
PersonWindow::Show()
{
	fView->MakeFocus();
	BWindow::Show();
}
 
 
void
PersonWindow::AddAttribute(const char* label, const char* attribute)
{
	fView->AddAttribute(label, attribute);
}
 
 
void
PersonWindow::SaveAs()
{
	char name[B_FILE_NAME_LENGTH];
	_GetDefaultFileName(name);
 
	if (fPanel == NULL) {
		BMessenger target(this);
		fPanel = new BFilePanel(B_SAVE_PANEL, &target);
 
		BPath path;
		find_directory(B_USER_DIRECTORY, &path, true);
 
		BDirectory dir;
		dir.SetTo(path.Path());
 
		BEntry entry;
		if (dir.FindEntry("people", &entry) == B_OK
			|| (dir.CreateDirectory("people", &dir) == B_OK
					&& dir.GetEntry(&entry) == B_OK)) {
			fPanel->SetPanelDirectory(&entry);
		}
	}
 
	if (fPanel->Window()->Lock()) {
		fPanel->SetSaveText(name);
		if (fPanel->Window()->IsHidden())
			fPanel->Window()->Show();
		else
			fPanel->Window()->Activate();
		fPanel->Window()->Unlock();
	}
}
 
 
bool
PersonWindow::RefersPersonFile(const entry_ref& ref) const
{
	if (fRef == NULL)
		return false;
	return *fRef == ref;
}
 
 
void
PersonWindow::_GetDefaultFileName(char* name)
{
	strncpy(name, fView->AttributeValue(fNameAttribute), B_FILE_NAME_LENGTH);
	while (*name) {
		if (*name == '/')
			*name = '-';
		name++;
	}
}
 
 
void
PersonWindow::_SetToRef(entry_ref* ref)
{
	if (fRef != NULL) {
		_WatchChanges(false);
		delete fRef;
	}
 
	fRef = ref;
 
	_WatchChanges(true);
}
 
 
void
PersonWindow::_WatchChanges(bool enable)
{
	if (fRef == NULL)
		return;
 
	node_ref nodeRef;
 
	BNode node(fRef);
	node.GetNodeRef(&nodeRef);
 
	uint32 flags;
	BString action;
 
	if (enable) {
		// Start watching.
		flags = B_WATCH_ALL;
		action = "starting";
	} else {
		// Stop watching.
		flags = B_STOP_WATCHING;
		action = "stoping";
	}
 
	if (watch_node(&nodeRef, flags, this) != B_OK) {
		printf("Error %s node monitor.\n", action.String());
	}
}

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

V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.