⛏️ index : haiku.git

/*
 * Copyright 2006, 2011, Stephan Aßmus <superstippi@gmx.de>.
 * Copyright 2023 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Zardshard
 */


#include "IconEditorApp.h"

#include <new>
#include <stdio.h>
#include <string.h>

#include <Alert.h>
#include <Catalog.h>
#include <FilePanel.h>
#include <FindDirectory.h>
#include <IconEditorProtocol.h>
#include <Locale.h>
#include <Message.h>
#include <Mime.h>
#include <Path.h>

#include "support_settings.h"

#include "AutoLocker.h"
#include "Defines.h"
#include "MainWindow.h"
#include "SavePanel.h"
#include "ShapeListView.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Main"


using std::nothrow;

static const char* kAppSig = "application/x-vnd.haiku-icon_o_matic";

static const float kWindowOffset = 20;


IconEditorApp::IconEditorApp()
	:
	BApplication(kAppSig),
	fWindowCount(0),
	fLastWindowFrame(50, 50, 900, 750),

	fOpenPanel(NULL),
	fSavePanel(NULL),

	fLastOpenPath(""),
	fLastSavePath(""),
	fLastExportPath("")
{
	// create file panels
	BMessenger messenger(this, this);
	BMessage message(B_REFS_RECEIVED);
	fOpenPanel = new BFilePanel(B_OPEN_PANEL, &messenger, NULL, B_FILE_NODE,
		true, &message);

	message.what = MSG_SAVE_AS;
	fSavePanel = new SavePanel("save panel", &messenger, NULL, B_FILE_NODE
		| B_DIRECTORY_NODE | B_SYMLINK_NODE, false, &message);

	_RestoreSettings();
}


IconEditorApp::~IconEditorApp()
{
	delete fOpenPanel;
	delete fSavePanel;
}


// #pragma mark -


bool
IconEditorApp::QuitRequested()
{
	// Run the QuitRequested() hook in each window's own thread. Otherwise
	// the BAlert which a window shows when an icon is not saved will not
	// repaint the window. (BAlerts check which thread is running Go() and
	// will repaint windows when it's a BWindow.)
	bool quit = true;
	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
		if (!window->Lock())
			continue;
		// Try to cast the window while the pointer must be valid.
		MainWindow* mainWindow = dynamic_cast<MainWindow*>(window);
		window->Unlock();
		if (mainWindow == NULL)
			continue;
		BMessenger messenger(window, window);
		BMessage reply;
		if (messenger.SendMessage(B_QUIT_REQUESTED, &reply) != B_OK)
			continue;
		bool result;
		if (reply.FindBool("result", &result) == B_OK && !result)
			quit = false;
	}

	if (!quit)
		return false;

	_StoreSettings();

	return true;
}


void
IconEditorApp::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MSG_NEW:
			_NewWindow()->Show();
			break;
		case MSG_OPEN:
		{
			BMessage openMessage(B_REFS_RECEIVED);
			MainWindow* window;
			if (message->FindPointer("window", (void**)&window) == B_OK)
				openMessage.AddPointer("window", window);
			bool referenceImage;
			if (message->FindBool("reference image", &referenceImage) == B_OK)
				openMessage.AddBool("reference image", referenceImage);
			fOpenPanel->SetMessage(&openMessage);
			fOpenPanel->Show();
			break;
		}
		case MSG_APPEND:
		{
			MainWindow* window;
			if (message->FindPointer("window", (void**)&window) != B_OK)
				break;
			BMessage openMessage(B_REFS_RECEIVED);
			openMessage.AddBool("append", true);
			openMessage.AddPointer("window", window);
			fOpenPanel->SetMessage(&openMessage);
			fOpenPanel->Show();
			break;
		}
		case B_EDIT_ICON_DATA:
		{
			BMessenger messenger;
			if (message->FindMessenger("reply to", &messenger) < B_OK) {
				// required
				break;
			}
			const uint8* data;
			ssize_t size;
			if (message->FindData("icon data", B_VECTOR_ICON_TYPE,
				(const void**)&data, &size) < B_OK) {
				// optional (new icon will be created)
				data = NULL;
				size = 0;
			}
			MainWindow* window = _NewWindow();
			window->Open(messenger, data, size);
			window->Show();
			break;
		}
		case MSG_SAVE_AS:
		case MSG_EXPORT_AS:
		{
			BMessenger messenger;
			if (message->FindMessenger("target", &messenger) != B_OK)
				break;

			fSavePanel->SetExportMode(message->what == MSG_EXPORT_AS);
//			fSavePanel->Refresh();
			const char* saveText;
			if (message->FindString("save text", &saveText) == B_OK)
				fSavePanel->SetSaveText(saveText);
			entry_ref saveDirectory;
			if (message->FindRef("save directory", &saveDirectory) == B_OK)
				fSavePanel->SetPanelDirectory(&saveDirectory);
			fSavePanel->SetTarget(messenger);
			fSavePanel->Show();
			break;
		}

		case MSG_WINDOW_CLOSED:
		{
			fWindowCount--;
			if (fWindowCount == 0)
				PostMessage(B_QUIT_REQUESTED);
			BMessage settings;
			if (message->FindMessage("settings", &settings) == B_OK)
				fLastWindowSettings = settings;
			BRect frame;
			if (message->FindRect("window frame", &frame) == B_OK) {
				fLastWindowFrame = frame;
				fLastWindowFrame.OffsetBy(-kWindowOffset, -kWindowOffset);
			}
			break;
		}

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


void
IconEditorApp::ReadyToRun()
{
	// create main window
	if (fWindowCount == 0)
		_NewWindow()->Show();

	_InstallDocumentMimeType();
}


void
IconEditorApp::RefsReceived(BMessage* message)
{
	bool append;
	if (message->FindBool("append", &append) != B_OK)
		append = false;
	bool referenceImage;
	if (message->FindBool("reference image", &referenceImage) != B_OK)
		referenceImage = false;
	MainWindow* window;
	if (message->FindPointer("window", (void**)&window) != B_OK)
		window = NULL;
	// When appending, we need to know a window.
	if (append && window == NULL)
		return;
	entry_ref ref;
	if (append || referenceImage) {
		if (!window->Lock())
			return;
		for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
			if (append)
				window->Open(ref, true);
			if (referenceImage)
				window->AddReferenceImage(ref);
		}
		window->Unlock();
	} else {
		for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
			if (window != NULL && i == 0) {
				window->Lock();
				window->Open(ref, false);
				window->Unlock();
			} else {
				window = _NewWindow();
				window->Open(ref, false);
				window->Show();
			}
		}
	}

	if (fOpenPanel != NULL && fSavePanel != NULL)
		_SyncPanels(fOpenPanel, fSavePanel);
}


void
IconEditorApp::ArgvReceived(int32 argc, char** argv)
{
	if (argc < 2)
		return;

	entry_ref ref;

	for (int32 i = 1; i < argc; i++) {
		if (get_ref_for_path(argv[i], &ref) == B_OK) {
		 	MainWindow* window = _NewWindow();
			window->Open(ref);
			window->Show();
		}
	}
}


// #pragma mark -


MainWindow*
IconEditorApp::_NewWindow()
{
	fLastWindowFrame.OffsetBy(kWindowOffset, kWindowOffset);
	MainWindow* window = new MainWindow(fLastWindowFrame, this,
		&fLastWindowSettings);
	fWindowCount++;

	window->MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN);

	return window;
}


void
IconEditorApp::_SyncPanels(BFilePanel* from, BFilePanel* to)
{
	if (from->Window()->Lock()) {
		// location
		if (to->Window()->Lock()) {
			BRect frame = from->Window()->Frame();
			to->Window()->MoveTo(frame.left, frame.top);
			to->Window()->ResizeTo(frame.Width(), frame.Height());
			to->Window()->Unlock();
		}
		// current folder
		entry_ref panelDir;
		from->GetPanelDirectory(&panelDir);
		to->SetPanelDirectory(&panelDir);
		from->Window()->Unlock();
	}
}


const char*
IconEditorApp::_LastFilePath(path_kind which)
{
	const char* path = NULL;

	switch (which) {
		case LAST_PATH_OPEN:
			if (fLastOpenPath.Length() > 0)
				path = fLastOpenPath.String();
			else if (fLastSavePath.Length() > 0)
				path = fLastSavePath.String();
			else if (fLastExportPath.Length() > 0)
				path = fLastExportPath.String();
			break;
		case LAST_PATH_SAVE:
			if (fLastSavePath.Length() > 0)
				path = fLastSavePath.String();
			else if (fLastExportPath.Length() > 0)
				path = fLastExportPath.String();
			else if (fLastOpenPath.Length() > 0)
				path = fLastOpenPath.String();
			break;
		case LAST_PATH_EXPORT:
			if (fLastExportPath.Length() > 0)
				path = fLastExportPath.String();
			else if (fLastSavePath.Length() > 0)
				path = fLastSavePath.String();
			else if (fLastOpenPath.Length() > 0)
				path = fLastOpenPath.String();
			break;
	}
	if (path == NULL) {

		BPath homePath;
		if (find_directory(B_USER_DIRECTORY, &homePath) == B_OK)
			path = homePath.Path();
		else 
			path = "/boot/home";
	}

	return path;
}


// #pragma mark -


void
IconEditorApp::_StoreSettings()
{
	BMessage settings('stns');

	settings.AddRect("window frame", fLastWindowFrame);
	settings.AddMessage("window settings", &fLastWindowSettings);
	settings.AddInt32("export mode", fSavePanel->ExportMode());

	save_settings(&settings, "Icon-O-Matic");
}


void
IconEditorApp::_RestoreSettings()
{
	BMessage settings('stns');
	load_settings(&settings, "Icon-O-Matic");

	BRect frame;
	if (settings.FindRect("window frame", &frame) == B_OK) {
		fLastWindowFrame = frame;
		// Compensate offset for next window...
		fLastWindowFrame.OffsetBy(-kWindowOffset, -kWindowOffset);
	}
	BMessage lastSettings;
	if (settings.FindMessage("window settings", &lastSettings)
		== B_OK) {
		fLastWindowSettings = lastSettings;
	}

	int32 mode;
	if (settings.FindInt32("export mode", &mode) >= B_OK)
		fSavePanel->SetExportMode(mode);
}


void
IconEditorApp::_InstallDocumentMimeType()
{
	// install mime type of documents
	BMimeType mime(kNativeIconMimeType);
	status_t ret = mime.InitCheck();
	if (ret < B_OK) {
		fprintf(stderr, "Could not init native document mime type (%s): %s.\n",
			kNativeIconMimeType, strerror(ret));
		return;
	}

	if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) {
		// mime is already installed, and the user is not
		// pressing the shift key to force a re-install
		return;
	}

	ret = mime.Install();
	if (ret < B_OK) {
		fprintf(stderr, "Could not install native document mime type (%s): "
			"%s.\n", kNativeIconMimeType, strerror(ret));
		return;
	}
	// set preferred app
	ret = mime.SetPreferredApp(kAppSig);
	if (ret < B_OK)
		fprintf(stderr, "Could not set native document preferred app: %s\n",
			strerror(ret));

	// set descriptions
	ret = mime.SetShortDescription("Haiku Icon");
	if (ret < B_OK)
		fprintf(stderr, "Could not set short description of mime type: %s\n",
			strerror(ret));
	ret = mime.SetLongDescription("Native Haiku vector icon");
	if (ret < B_OK)
		fprintf(stderr, "Could not set long description of mime type: %s\n",
			strerror(ret));

	// set extensions
	BMessage message('extn');
	message.AddString("extensions", "icon");
	ret = mime.SetFileExtensions(&message);
	if (ret < B_OK)
		fprintf(stderr, "Could not set extensions of mime type: %s\n",
			strerror(ret));

	// set sniffer rule
	const char* snifferRule = "0.9 ('IMSG')";
	ret = mime.SetSnifferRule(snifferRule);
	if (ret < B_OK) {
		BString parseError;
		BMimeType::CheckSnifferRule(snifferRule, &parseError);
		fprintf(stderr, "Could not set sniffer rule of mime type: %s\n",
			parseError.String());
	}
}