1/*
2 * Copyright 2006, 2011, Stephan A��mus <superstippi@gmx.de>.
3 * Copyright 2023 Haiku, Inc. All rights reserved.
4 * Distributed under the terms of the MIT License.
5 *
6 * Authors:
7 *		Zardshard
8 */
9
10
11#include "IconEditorApp.h"
12
13#include <new>
14#include <stdio.h>
15#include <string.h>
16
17#include <Alert.h>
18#include <Catalog.h>
19#include <FilePanel.h>
20#include <FindDirectory.h>
21#include <IconEditorProtocol.h>
22#include <Locale.h>
23#include <Message.h>
24#include <Mime.h>
25#include <Path.h>
26
27#include "support_settings.h"
28
29#include "AutoLocker.h"
30#include "Defines.h"
31#include "MainWindow.h"
32#include "SavePanel.h"
33#include "ShapeListView.h"
34
35
36#undef B_TRANSLATION_CONTEXT
37#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Main"
38
39
40using std::nothrow;
41
42static const char* kAppSig = "application/x-vnd.haiku-icon_o_matic";
43
44static const float kWindowOffset = 20;
45
46
47IconEditorApp::IconEditorApp()
48	:
49	BApplication(kAppSig),
50	fWindowCount(0),
51	fLastWindowFrame(50, 50, 900, 750),
52
53	fOpenPanel(NULL),
54	fSavePanel(NULL),
55
56	fLastOpenPath(""),
57	fLastSavePath(""),
58	fLastExportPath("")
59{
60	// create file panels
61	BMessenger messenger(this, this);
62	BMessage message(B_REFS_RECEIVED);
63	fOpenPanel = new BFilePanel(B_OPEN_PANEL, &messenger, NULL, B_FILE_NODE,
64		true, &message);
65
66	message.what = MSG_SAVE_AS;
67	fSavePanel = new SavePanel("save panel", &messenger, NULL, B_FILE_NODE
68		| B_DIRECTORY_NODE | B_SYMLINK_NODE, false, &message);
69
70	_RestoreSettings();
71}
72
73
74IconEditorApp::~IconEditorApp()
75{
76	delete fOpenPanel;
77	delete fSavePanel;
78}
79
80
81// #pragma mark -
82
83
84bool
85IconEditorApp::QuitRequested()
86{
87	// Run the QuitRequested() hook in each window's own thread. Otherwise
88	// the BAlert which a window shows when an icon is not saved will not
89	// repaint the window. (BAlerts check which thread is running Go() and
90	// will repaint windows when it's a BWindow.)
91	bool quit = true;
92	for (int32 i = 0; BWindow* window = WindowAt(i); i++) {
93		if (!window->Lock())
94			continue;
95		// Try to cast the window while the pointer must be valid.
96		MainWindow* mainWindow = dynamic_cast<MainWindow*>(window);
97		window->Unlock();
98		if (mainWindow == NULL)
99			continue;
100		BMessenger messenger(window, window);
101		BMessage reply;
102		if (messenger.SendMessage(B_QUIT_REQUESTED, &reply) != B_OK)
103			continue;
104		bool result;
105		if (reply.FindBool("result", &result) == B_OK && !result)
106			quit = false;
107	}
108
109	if (!quit)
110		return false;
111
112	_StoreSettings();
113
114	return true;
115}
116
117
118void
119IconEditorApp::MessageReceived(BMessage* message)
120{
121	switch (message->what) {
122		case MSG_NEW:
123			_NewWindow()->Show();
124			break;
125		case MSG_OPEN:
126		{
127			BMessage openMessage(B_REFS_RECEIVED);
128			MainWindow* window;
129			if (message->FindPointer("window", (void**)&window) == B_OK)
130				openMessage.AddPointer("window", window);
131			bool referenceImage;
132			if (message->FindBool("reference image", &referenceImage) == B_OK)
133				openMessage.AddBool("reference image", referenceImage);
134			fOpenPanel->SetMessage(&openMessage);
135			fOpenPanel->Show();
136			break;
137		}
138		case MSG_APPEND:
139		{
140			MainWindow* window;
141			if (message->FindPointer("window", (void**)&window) != B_OK)
142				break;
143			BMessage openMessage(B_REFS_RECEIVED);
144			openMessage.AddBool("append", true);
145			openMessage.AddPointer("window", window);
146			fOpenPanel->SetMessage(&openMessage);
147			fOpenPanel->Show();
148			break;
149		}
150		case B_EDIT_ICON_DATA:
151		{
152			BMessenger messenger;
153			if (message->FindMessenger("reply to", &messenger) < B_OK) {
154				// required
155				break;
156			}
157			const uint8* data;
158			ssize_t size;
159			if (message->FindData("icon data", B_VECTOR_ICON_TYPE,
160				(const void**)&data, &size) < B_OK) {
161				// optional (new icon will be created)
162				data = NULL;
163				size = 0;
164			}
165			MainWindow* window = _NewWindow();
166			window->Open(messenger, data, size);
167			window->Show();
168			break;
169		}
170		case MSG_SAVE_AS:
171		case MSG_EXPORT_AS:
172		{
173			BMessenger messenger;
174			if (message->FindMessenger("target", &messenger) != B_OK)
175				break;
176
177			fSavePanel->SetExportMode(message->what == MSG_EXPORT_AS);
178//			fSavePanel->Refresh();
179			const char* saveText;
180			if (message->FindString("save text", &saveText) == B_OK)
181				fSavePanel->SetSaveText(saveText);
182			fSavePanel->SetTarget(messenger);
183			fSavePanel->Show();
184			break;
185		}
186
187		case MSG_WINDOW_CLOSED:
188		{
189			fWindowCount--;
190			if (fWindowCount == 0)
191				PostMessage(B_QUIT_REQUESTED);
192			BMessage settings;
193			if (message->FindMessage("settings", &settings) == B_OK)
194				fLastWindowSettings = settings;
195			BRect frame;
196			if (message->FindRect("window frame", &frame) == B_OK) {
197				fLastWindowFrame = frame;
198				fLastWindowFrame.OffsetBy(-kWindowOffset, -kWindowOffset);
199			}
200			break;
201		}
202
203		default:
204			BApplication::MessageReceived(message);
205			break;
206	}
207}
208
209
210void
211IconEditorApp::ReadyToRun()
212{
213	// create main window
214	if (fWindowCount == 0)
215		_NewWindow()->Show();
216
217	_InstallDocumentMimeType();
218}
219
220
221void
222IconEditorApp::RefsReceived(BMessage* message)
223{
224	bool append;
225	if (message->FindBool("append", &append) != B_OK)
226		append = false;
227	bool referenceImage;
228	if (message->FindBool("reference image", &referenceImage) != B_OK)
229		referenceImage = false;
230	MainWindow* window;
231	if (message->FindPointer("window", (void**)&window) != B_OK)
232		window = NULL;
233	// When appending, we need to know a window.
234	if (append && window == NULL)
235		return;
236	entry_ref ref;
237	if (append || referenceImage) {
238		if (!window->Lock())
239			return;
240		for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
241			if (append)
242				window->Open(ref, true);
243			if (referenceImage)
244				window->AddReferenceImage(ref);
245		}
246		window->Unlock();
247	} else {
248		for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) {
249			if (window != NULL && i == 0) {
250				window->Lock();
251				window->Open(ref, false);
252				window->Unlock();
253			} else {
254				window = _NewWindow();
255				window->Open(ref, false);
256				window->Show();
257			}
258		}
259	}
260
261	if (fOpenPanel != NULL && fSavePanel != NULL)
262		_SyncPanels(fOpenPanel, fSavePanel);
263}
264
265
266void
267IconEditorApp::ArgvReceived(int32 argc, char** argv)
268{
269	if (argc < 2)
270		return;
271
272	entry_ref ref;
273
274	for (int32 i = 1; i < argc; i++) {
275		if (get_ref_for_path(argv[i], &ref) == B_OK) {
276		 	MainWindow* window = _NewWindow();
277			window->Open(ref);
278			window->Show();
279		}
280	}
281}
282
283
284// #pragma mark -
285
286
287MainWindow*
288IconEditorApp::_NewWindow()
289{
290	fLastWindowFrame.OffsetBy(kWindowOffset, kWindowOffset);
291	MainWindow* window = new MainWindow(fLastWindowFrame, this,
292		&fLastWindowSettings);
293	fWindowCount++;
294	return window;
295}
296
297
298void
299IconEditorApp::_SyncPanels(BFilePanel* from, BFilePanel* to)
300{
301	if (from->Window()->Lock()) {
302		// location
303		if (to->Window()->Lock()) {
304			BRect frame = from->Window()->Frame();
305			to->Window()->MoveTo(frame.left, frame.top);
306			to->Window()->ResizeTo(frame.Width(), frame.Height());
307			to->Window()->Unlock();
308		}
309		// current folder
310		entry_ref panelDir;
311		from->GetPanelDirectory(&panelDir);
312		to->SetPanelDirectory(&panelDir);
313		from->Window()->Unlock();
314	}
315}
316
317
318const char*
319IconEditorApp::_LastFilePath(path_kind which)
320{
321	const char* path = NULL;
322
323	switch (which) {
324		case LAST_PATH_OPEN:
325			if (fLastOpenPath.Length() > 0)
326				path = fLastOpenPath.String();
327			else if (fLastSavePath.Length() > 0)
328				path = fLastSavePath.String();
329			else if (fLastExportPath.Length() > 0)
330				path = fLastExportPath.String();
331			break;
332		case LAST_PATH_SAVE:
333			if (fLastSavePath.Length() > 0)
334				path = fLastSavePath.String();
335			else if (fLastExportPath.Length() > 0)
336				path = fLastExportPath.String();
337			else if (fLastOpenPath.Length() > 0)
338				path = fLastOpenPath.String();
339			break;
340		case LAST_PATH_EXPORT:
341			if (fLastExportPath.Length() > 0)
342				path = fLastExportPath.String();
343			else if (fLastSavePath.Length() > 0)
344				path = fLastSavePath.String();
345			else if (fLastOpenPath.Length() > 0)
346				path = fLastOpenPath.String();
347			break;
348	}
349	if (path == NULL) {
350
351		BPath homePath;
352		if (find_directory(B_USER_DIRECTORY, &homePath) == B_OK)
353			path = homePath.Path();
354		else
355			path = "/boot/home";
356	}
357
358	return path;
359}
360
361
362// #pragma mark -
363
364
365void
366IconEditorApp::_StoreSettings()
367{
368	BMessage settings('stns');
369
370	settings.AddRect("window frame", fLastWindowFrame);
371	settings.AddMessage("window settings", &fLastWindowSettings);
372	settings.AddInt32("export mode", fSavePanel->ExportMode());
373
374	save_settings(&settings, "Icon-O-Matic");
375}
376
377
378void
379IconEditorApp::_RestoreSettings()
380{
381	BMessage settings('stns');
382	load_settings(&settings, "Icon-O-Matic");
383
384	BRect frame;
385	if (settings.FindRect("window frame", &frame) == B_OK) {
386		fLastWindowFrame = frame;
387		// Compensate offset for next window...
388		fLastWindowFrame.OffsetBy(-kWindowOffset, -kWindowOffset);
389	}
390	BMessage lastSettings;
391	if (settings.FindMessage("window settings", &lastSettings)
392		== B_OK) {
393		fLastWindowSettings = lastSettings;
394	}
395
396	int32 mode;
397	if (settings.FindInt32("export mode", &mode) >= B_OK)
398		fSavePanel->SetExportMode(mode);
399}
400
401
402void
403IconEditorApp::_InstallDocumentMimeType()
404{
405	// install mime type of documents
406	BMimeType mime(kNativeIconMimeType);
407	status_t ret = mime.InitCheck();
408	if (ret < B_OK) {
409		fprintf(stderr, "Could not init native document mime type (%s): %s.\n",
410			kNativeIconMimeType, strerror(ret));
411		return;
412	}
413
414	if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) {
415		// mime is already installed, and the user is not
416		// pressing the shift key to force a re-install
417		return;
418	}
419
420	ret = mime.Install();
421	if (ret < B_OK) {
422		fprintf(stderr, "Could not install native document mime type (%s): "
423			"%s.\n", kNativeIconMimeType, strerror(ret));
424		return;
425	}
426	// set preferred app
427	ret = mime.SetPreferredApp(kAppSig);
428	if (ret < B_OK)
429		fprintf(stderr, "Could not set native document preferred app: %s\n",
430			strerror(ret));
431
432	// set descriptions
433	ret = mime.SetShortDescription("Haiku Icon");
434	if (ret < B_OK)
435		fprintf(stderr, "Could not set short description of mime type: %s\n",
436			strerror(ret));
437	ret = mime.SetLongDescription("Native Haiku vector icon");
438	if (ret < B_OK)
439		fprintf(stderr, "Could not set long description of mime type: %s\n",
440			strerror(ret));
441
442	// set extensions
443	BMessage message('extn');
444	message.AddString("extensions", "icon");
445	ret = mime.SetFileExtensions(&message);
446	if (ret < B_OK)
447		fprintf(stderr, "Could not set extensions of mime type: %s\n",
448			strerror(ret));
449
450	// set sniffer rule
451	const char* snifferRule = "0.9 ('IMSG')";
452	ret = mime.SetSnifferRule(snifferRule);
453	if (ret < B_OK) {
454		BString parseError;
455		BMimeType::CheckSnifferRule(snifferRule, &parseError);
456		fprintf(stderr, "Could not set sniffer rule of mime type: %s\n",
457			parseError.String());
458	}
459}
460
461