⛏️ index : haiku.git

/*
 * Copyright 2002-2016, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Mattias Sundblad
 *		Andrew Bachmann
 *		Jonas Sundström
 */


#include "Constants.h"
#include "StyledEditApp.h"
#include "StyledEditWindow.h"

#include <Alert.h>
#include <Autolock.h>
#include <Catalog.h>
#include <Locale.h>
#include <MenuBar.h>
#include <CharacterSet.h>
#include <CharacterSetRoster.h>
#include <FilePanel.h>
#include <MenuItem.h>
#include <Message.h>
#include <Path.h>
#include <Screen.h>

#include <stdio.h>


using namespace BPrivate;


static BRect sWindowRect(7, 26, 507, 426);
static float sCascadeOffset = 15;
static BPoint sTopLeft = BPoint(7, 26);


namespace
{
	void
	cascade()
	{
		BScreen screen;
		BRect screenBorder = screen.Frame();
		float left = sWindowRect.left + sCascadeOffset;
		if (left + sWindowRect.Width() > screenBorder.right)
			left = sTopLeft.x;

		float top = sWindowRect.top + sCascadeOffset;
		if (top + sWindowRect.Height() > screenBorder.bottom)
			top = sTopLeft.y;

		sWindowRect.OffsetTo(BPoint(left, top));
	}


	void
	uncascade()
	{
		BScreen screen;
		BRect screenBorder = screen.Frame();

		float left = sWindowRect.left - sCascadeOffset;
		if (left < sTopLeft.x) {
			left = screenBorder.right - sWindowRect.Width() - sTopLeft.x;
			left = left - ((int)left % (int)sCascadeOffset) + sTopLeft.x;
		}

		float top = sWindowRect.top - sCascadeOffset;
		if (top < sTopLeft.y) {
			top = screenBorder.bottom - sWindowRect.Height() - sTopLeft.y;
			top = top - ((int)left % (int)sCascadeOffset) + sTopLeft.y;
		}

		sWindowRect.OffsetTo(BPoint(left, top));
	}
}


//	#pragma mark -


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Open_and_SaveAsPanel"


StyledEditApp::StyledEditApp()
	:
	BApplication(APP_SIGNATURE),
	fOpenPanel(NULL)
{
	B_TRANSLATE_MARK_SYSTEM_NAME_VOID("StyledEdit");

	fOpenPanel = new BFilePanel();
	fOpenAsEncoding = 0;

	BMenuBar* menuBar
		= dynamic_cast<BMenuBar*>(fOpenPanel->Window()->FindView("MenuBar"));
	if (menuBar != NULL) {
		fOpenPanelEncodingMenu = new BMenu(B_TRANSLATE("Encoding"));
		fOpenPanelEncodingMenu->SetRadioMode(true);

		menuBar->AddItem(fOpenPanelEncodingMenu);

		BCharacterSetRoster roster;
		BCharacterSet charset;
		while (roster.GetNextCharacterSet(&charset) == B_NO_ERROR) {
			BString name;
			if (charset.GetFontID() == B_UNICODE_UTF8)
				name = B_TRANSLATE("Default");
			else
				name = charset.GetPrintName();

			const char* mime = charset.GetMIMEName();
			if (mime != NULL) {
				name.Append(" (");
				name.Append(mime);
				name.Append(")");
			}
			BMenuItem* item
				= new BMenuItem(name.String(), new BMessage(OPEN_AS_ENCODING));
			item->SetTarget(this);
			fOpenPanelEncodingMenu->AddItem(item);
			if (charset.GetFontID() == fOpenAsEncoding)
				item->SetMarked(true);
		}
	} else
		fOpenPanelEncodingMenu = NULL;

	fWindowCount = 0;
	fNextUntitledWindow = 1;
	fBadArguments = false;

	float factor = be_plain_font->Size() / 12.0f;
	sCascadeOffset *= factor;
	sTopLeft.x *= factor;
	sTopLeft.y *= factor;
	sWindowRect.left *= factor;
	sWindowRect.top *= factor;
	sWindowRect.right *= factor;
	sWindowRect.bottom *= factor;
}


StyledEditApp::~StyledEditApp()
{
	delete fOpenPanel;
}


void
StyledEditApp::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MENU_NEW:
			OpenDocument();
			break;
		case MENU_OPEN:
			fOpenPanel->Show();
			break;
		case B_SILENT_RELAUNCH:
			OpenDocument();
			break;
		case OPEN_AS_ENCODING:
			void* ptr;
			if (message->FindPointer("source", &ptr) == B_OK
				&& fOpenPanelEncodingMenu != NULL) {
				fOpenAsEncoding = (uint32)fOpenPanelEncodingMenu->IndexOf(
					(BMenuItem*)ptr);
			}
			break;

		default:
			BApplication::MessageReceived(message);
			break;
	}
}


void
StyledEditApp::OpenDocument()
{
	new StyledEditWindow(sWindowRect, fNextUntitledWindow++, fOpenAsEncoding);
	cascade();
	fWindowCount++;
}


status_t
StyledEditApp::OpenDocument(entry_ref* ref, BMessage* message)
{
	// traverse eventual symlink
	BEntry entry(ref, true);
	entry.GetRef(ref);

	if (entry.IsDirectory()) {
		BPath path(&entry);
		fprintf(stderr,
			"Can't open directory \"%s\" for editing.\n",
			path.Path());
		return B_ERROR;
	}

	BEntry parent;
	entry.GetParent(&parent);

	if (!entry.Exists() && !parent.Exists()) {
		fprintf(stderr,
			"Can't create file. Missing parent directory.\n");
		return B_ERROR;
	}

	BWindow* window = NULL;
	StyledEditWindow* document = NULL;

	for (int32 index = 0; ; index++) {
		window = WindowAt(index);
		if (window == NULL)
			break;

		document = dynamic_cast<StyledEditWindow*>(window);
		if (document == NULL)
			continue;

		if (document->IsDocumentEntryRef(ref)) {
			if (document->Lock()) {
				document->Activate();
				document->Unlock();
				if (message != NULL)
					document->PostMessage(message);
				return B_OK;
			}
		}
	}

	document = new StyledEditWindow(sWindowRect, ref, fOpenAsEncoding);
	cascade();

	if (message != NULL)
		document->PostMessage(message);

	fWindowCount++;

	return B_OK;
}


void
StyledEditApp::CloseDocument()
{
	uncascade();
	fWindowCount--;
	if (fWindowCount == 0) {
		BAutolock lock(this);
		Quit();
	}
}


void
StyledEditApp::RefsReceived(BMessage* message)
{
	int32 index = 0;
	entry_ref ref;

	while (message->FindRef("refs", index, &ref) == B_OK) {
		int32 line;
		if (message->FindInt32("be:line", index, &line) != B_OK)
			line = -1;
		int32 start, length;
		if (message->FindInt32("be:selection_length", index, &length) != B_OK
			|| message->FindInt32("be:selection_offset", index, &start) != B_OK)
		{
			start = -1;
			length = -1;
		}

		BMessage* selection = NULL;
		if (line >= 0 || (start >= 0 && length >= 0)) {
			selection = new BMessage(UPDATE_LINE_SELECTION);
			if (line >= 0)
				selection->AddInt32("be:line", line);
			if (start >= 0) {
				selection->AddInt32("be:selection_offset", start);
				selection->AddInt32("be:selection_length", max_c(0, length));
			}
		}

		OpenDocument(&ref, selection);
		index++;
	}
}


void
StyledEditApp::ArgvReceived(int32 argc, char* argv[])
{
	// If StyledEdit is already running and gets invoked again
	// we need to account for a possible mismatch in current
	// working directory. The paths of the new arguments are
	// relative to the cwd of the invocation, if they are not
	// absolute. This cwd we find as a string named "cwd" in
	// the BLooper's current message.

	const char* cwd = "";
	BMessage* message = CurrentMessage();

	if (message != NULL) {
		if (message->FindString("cwd", &cwd) != B_OK)
			cwd = "";
	}

	for (int i = 1 ; (i < argc) ; i++) {
		BPath path;
		if (argv[i][0] == '/') {
			path.SetTo(argv[i]);
		} else {
			path.SetTo(cwd, argv[i]);
				// patch relative paths only
		}

		entry_ref ref;
		get_ref_for_path(path.Path(), &ref);

		status_t status;
		status = OpenDocument(&ref);

		if (status != B_OK && IsLaunching())
			fBadArguments = true;
	}
}


void
StyledEditApp::ReadyToRun()
{
	if (fWindowCount > 0)
		return;

	if (fBadArguments)
		Quit();
	else
		OpenDocument();
}


int32
StyledEditApp::NumberOfWindows()
{
	return fWindowCount;
}


//	#pragma mark -


int
main(int argc, char** argv)
{
	StyledEditApp styledEdit;
	styledEdit.Run();
	return 0;
}