xref: /haiku/src/apps/icon-o-matic/IconEditorApp.cpp (revision b9a5b9a6ee494261f2882bfc0ee9fde92282bef6)
1 /*
2  * Copyright 2006, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  */
8 
9 #include "IconEditorApp.h"
10 
11 #include <new>
12 #include <stdio.h>
13 #include <string.h>
14 
15 #include <Alert.h>
16 #include <Directory.h>
17 #include <Entry.h>
18 #include <File.h>
19 #include <FilePanel.h>
20 #include <IconEditorProtocol.h>
21 #include <Message.h>
22 #include <Mime.h>
23 
24 #include "support_settings.h"
25 
26 #include "AttributeSaver.h"
27 #include "AutoLocker.h"
28 #include "BitmapExporter.h"
29 #include "BitmapSetSaver.h"
30 #include "CommandStack.h"
31 #include "Defines.h"
32 #include "Document.h"
33 #include "FlatIconExporter.h"
34 #include "FlatIconFormat.h"
35 #include "FlatIconImporter.h"
36 #include "Icon.h"
37 #include "MainWindow.h"
38 #include "MessageExporter.h"
39 #include "MessageImporter.h"
40 #include "MessengerSaver.h"
41 #include "NativeSaver.h"
42 #include "PathContainer.h"
43 #include "RDefExporter.h"
44 #include "SavePanel.h"
45 #include "ShapeContainer.h"
46 #include "SimpleFileSaver.h"
47 #include "SVGExporter.h"
48 #include "SVGImporter.h"
49 
50 using std::nothrow;
51 
52 static const char* kAppSig = "application/x-vnd.haiku-icon_o_matic";
53 
54 // constructor
55 IconEditorApp::IconEditorApp()
56 	: BApplication(kAppSig),
57 	  fMainWindow(NULL),
58 	  fDocument(new Document("test")),
59 
60 	  fOpenPanel(NULL),
61 	  fSavePanel(NULL),
62 
63 	  fLastOpenPath(""),
64 	  fLastSavePath(""),
65 	  fLastExportPath("")
66 {
67 }
68 
69 // destructor
70 IconEditorApp::~IconEditorApp()
71 {
72 	// NOTE: it is important that the GUI has been deleted
73 	// at this point, so that all the listener/observer
74 	// stuff is properly detached
75 	delete fDocument;
76 
77 	delete fOpenPanel;
78 	delete fSavePanel;
79 }
80 
81 // #pragma mark -
82 
83 // QuitRequested
84 bool
85 IconEditorApp::QuitRequested()
86 {
87 	// TODO: ask main window if quitting is ok
88 	_StoreSettings();
89 
90 	fMainWindow->Lock();
91 	fMainWindow->Quit();
92 	fMainWindow = NULL;
93 
94 	return true;
95 }
96 
97 // MessageReceived
98 void
99 IconEditorApp::MessageReceived(BMessage* message)
100 {
101 	switch (message->what) {
102 		case MSG_NEW:
103 			_MakeIconEmpty();
104 			break;
105 		case MSG_OPEN: {
106 //			fOpenPanel->Refresh();
107 			BMessage openMessage(B_REFS_RECEIVED);
108 			fOpenPanel->SetMessage(&openMessage);
109 			fOpenPanel->Show();
110 			break;
111 		}
112 		case MSG_APPEND: {
113 //			fOpenPanel->Refresh();
114 			BMessage openMessage(B_REFS_RECEIVED);
115 			openMessage.AddBool("append", true);
116 			fOpenPanel->SetMessage(&openMessage);
117 			fOpenPanel->Show();
118 			break;
119 		}
120 		case MSG_SAVE:
121 		case MSG_EXPORT: {
122 			DocumentSaver* saver;
123 			if (message->what == MSG_SAVE)
124 				saver = fDocument->NativeSaver();
125 			else
126 				saver = fDocument->ExportSaver();
127 			if (saver) {
128 				saver->Save(fDocument);
129 				break;
130 			} // else fall through
131 		}
132 		case MSG_SAVE_AS:
133 		case MSG_EXPORT_AS: {
134 			int32 exportMode;
135 			if (message->FindInt32("export mode", &exportMode) < B_OK)
136 				exportMode = EXPORT_MODE_MESSAGE;
137 			entry_ref ref;
138 			const char* name;
139 			if (message->FindRef("directory", &ref) == B_OK
140 				&& message->FindString("name", &name) == B_OK) {
141 				// this message comes from the file panel
142 				BDirectory dir(&ref);
143 				BEntry entry;
144 				if (dir.InitCheck() >= B_OK
145 					&& entry.SetTo(&dir, name, true) >= B_OK
146 					&& entry.GetRef(&ref) >= B_OK) {
147 
148 					// create the document saver and remember it for later
149 					DocumentSaver* saver = _CreateSaver(ref, exportMode);
150 					if (saver) {
151 						if (exportMode == EXPORT_MODE_MESSAGE)
152 							fDocument->SetNativeSaver(saver);
153 						else
154 							fDocument->SetExportSaver(saver);
155 						saver->Save(fDocument);
156 					}
157 				}
158 				_SyncPanels(fSavePanel, fOpenPanel);
159 			} else {
160 				// configure the file panel
161 				const char* saveText = NULL;
162 				FileSaver* saver = dynamic_cast<FileSaver*>(
163 					fDocument->NativeSaver());
164 
165 				bool exportMode = message->what == MSG_EXPORT_AS
166 									|| message->what == MSG_EXPORT;
167 				if (exportMode) {
168 					saver = dynamic_cast<FileSaver*>(
169 						fDocument->ExportSaver());
170 				}
171 
172 				if (saver)
173 					saveText = saver->Ref()->name;
174 
175 				fSavePanel->SetExportMode(exportMode);
176 //				fSavePanel->Refresh();
177 				if (saveText)
178 					fSavePanel->SetSaveText(saveText);
179 				fSavePanel->Show();
180 			}
181 			break;
182 		}
183 		case B_EDIT_ICON_DATA: {
184 			BMessenger messenger;
185 			if (message->FindMessenger("reply to", &messenger) < B_OK) {
186 				// required
187 				break;
188 			}
189 			const uint8* data;
190 			ssize_t size;
191 			if (message->FindData("icon data", B_VECTOR_ICON_TYPE,
192 				(const void**)&data, &size) < B_OK) {
193 				// optional (new icon will be created)
194 				data = NULL;
195 				size = 0;
196 			}
197 			_Open(messenger, data, size);
198 			break;
199 		}
200 
201 		default:
202 			BApplication::MessageReceived(message);
203 			break;
204 	}
205 }
206 
207 // ReadyToRun
208 void
209 IconEditorApp::ReadyToRun()
210 {
211 	// create file panels
212 	BMessenger messenger(this, this);
213 	fOpenPanel = new BFilePanel(B_OPEN_PANEL,
214 								&messenger,
215 								NULL,
216 								B_FILE_NODE,
217 								true,
218 								new BMessage(B_REFS_RECEIVED));
219 
220 	fSavePanel = new SavePanel("save panel",
221 							   &messenger,
222 								NULL,
223 								B_FILE_NODE
224 									| B_DIRECTORY_NODE
225 									| B_SYMLINK_NODE,
226 								false,
227 								new BMessage(MSG_SAVE_AS));
228 
229 	// create main window
230 	fMainWindow = new MainWindow(this, fDocument);
231 
232 	_RestoreSettings();
233 
234 	fMainWindow->Show();
235 
236 	_InstallDocumentMimeType();
237 }
238 
239 // RefsReceived
240 void
241 IconEditorApp::RefsReceived(BMessage* message)
242 {
243 	// TODO: multiple documents (iterate over refs)
244 	bool append;
245 	if (message->FindBool("append", &append) < B_OK)
246 		append = false;
247 	entry_ref ref;
248 	if (message->FindRef("refs", &ref) == B_OK)
249 		_Open(ref, append);
250 
251 	if (fOpenPanel && fSavePanel)
252 		_SyncPanels(fOpenPanel, fSavePanel);
253 }
254 
255 // ArgvReceived
256 void
257 IconEditorApp::ArgvReceived(int32 argc, char** argv)
258 {
259 	if (argc < 2)
260 		return;
261 
262 	// TODO: multiple documents (iterate over argv)
263 	entry_ref ref;
264 	if (get_ref_for_path(argv[1], &ref) == B_OK)
265 		_Open(ref);
266 }
267 
268 // #pragma mark -
269 
270 void
271 IconEditorApp::_MakeIconEmpty()
272 {
273 	bool mainWindowLocked = fMainWindow && fMainWindow->Lock();
274 
275 	AutoWriteLocker locker(fDocument);
276 
277 	if (fMainWindow)
278 		fMainWindow->MakeEmpty();
279 
280 	fDocument->MakeEmpty();
281 
282 	locker.Unlock();
283 
284 	if (mainWindowLocked)
285 		fMainWindow->Unlock();
286 }
287 
288 // _Open
289 void
290 IconEditorApp::_Open(const entry_ref& ref, bool append)
291 {
292 	BFile file(&ref, B_READ_ONLY);
293 	if (file.InitCheck() < B_OK)
294 		return;
295 
296 	Icon* icon;
297 	if (append)
298 		icon = new (nothrow) Icon(*fDocument->Icon());
299 	else
300 		icon = new (nothrow) Icon();
301 
302 	if (!icon)
303 		return;
304 
305 	enum {
306 		REF_NONE = 0,
307 		REF_MESSAGE,
308 		REF_FLAT,
309 		REF_SVG
310 	};
311 	uint32 refMode = REF_NONE;
312 
313 	// try different file types
314 	FlatIconImporter flatImporter;
315 	status_t ret = flatImporter.Import(icon, &file);
316 	if (ret >= B_OK) {
317 		refMode = REF_FLAT;
318 	} else {
319 		file.Seek(0, SEEK_SET);
320 		MessageImporter msgImporter;
321 		ret = msgImporter.Import(icon, &file);
322 		if (ret >= B_OK) {
323 			refMode = REF_MESSAGE;
324 		} else {
325 			file.Seek(0, SEEK_SET);
326 			SVGImporter svgImporter;
327 			ret = svgImporter.Import(icon, &ref);
328 			if (ret >= B_OK)
329 				refMode = REF_SVG;
330 		}
331 	}
332 
333 	if (ret < B_OK) {
334 		// inform user of failure at this point
335 		BString helper("Opening the document failed!");
336 		helper << "\n\n" << "Error: " << strerror(ret);
337 		BAlert* alert = new BAlert("bad news", helper.String(),
338 								   "Bummer", NULL, NULL);
339 		// launch alert asynchronously
340 		alert->Go(NULL);
341 
342 		delete icon;
343 		return;
344 	}
345 
346 	// keep the mainwindow locked while switching icons
347 	bool mainWindowLocked = fMainWindow && fMainWindow->Lock();
348 
349 	AutoWriteLocker locker(fDocument);
350 
351 	if (mainWindowLocked)
352 		fMainWindow->SetIcon(NULL);
353 
354 	// incorporate the loaded icon into the document
355 	// (either replace it or append to it)
356 	fDocument->MakeEmpty(!append);
357 		// if append, the document savers are preserved
358 	fDocument->SetIcon(icon);
359 	if (!append) {
360 		// document got replaced, but we have at
361 		// least one ref already
362 		switch (refMode) {
363 			case REF_MESSAGE:
364 				fDocument->SetNativeSaver(
365 					new NativeSaver(ref));
366 				break;
367 			case REF_FLAT:
368 				fDocument->SetExportSaver(
369 					new SimpleFileSaver(new FlatIconExporter(), ref));
370 				break;
371 			case REF_SVG:
372 				fDocument->SetExportSaver(
373 					new SimpleFileSaver(new SVGExporter(), ref));
374 				break;
375 		}
376 	}
377 
378 	locker.Unlock();
379 
380 	if (mainWindowLocked) {
381 		fMainWindow->Unlock();
382 		// cause the mainwindow to adopt icon in
383 		// it's own thread
384 		fMainWindow->PostMessage(MSG_SET_ICON);
385 	}
386 }
387 
388 // _Open
389 void
390 IconEditorApp::_Open(const BMessenger& externalObserver,
391 					 const uint8* data, size_t size)
392 {
393 	if (!externalObserver.IsValid())
394 		return;
395 
396 	Icon* icon = new (nothrow) Icon();
397 	if (!icon)
398 		return;
399 
400 	if (data && size > 0) {
401 		// try to open the icon from the provided data
402 		FlatIconImporter flatImporter;
403 		status_t ret = flatImporter.Import(icon, const_cast<uint8*>(data), size);
404 			// NOTE: the const_cast is a bit ugly, but no harm is done
405 			// the reason is that the LittleEndianBuffer knows read and write
406 			// mode, in this case it is used read-only, and it does not assume
407 			// ownership of the buffer
408 
409 		if (ret < B_OK) {
410 			// inform user of failure at this point
411 			BString helper("Opening the icon failed!");
412 			helper << "\n\n" << "Error: " << strerror(ret);
413 			BAlert* alert = new BAlert("bad news", helper.String(),
414 									   "Bummer", NULL, NULL);
415 			// launch alert asynchronously
416 			alert->Go(NULL);
417 
418 			delete icon;
419 			return;
420 		}
421 	}
422 
423 	// keep the mainwindow locked while switching icons
424 	bool mainWindowLocked = fMainWindow && fMainWindow->Lock();
425 
426 	AutoWriteLocker locker(fDocument);
427 
428 	if (mainWindowLocked)
429 		fMainWindow->SetIcon(NULL);
430 
431 	// incorporate the loaded icon into the document
432 	// (either replace it or append to it)
433 	fDocument->MakeEmpty();
434 	fDocument->SetIcon(icon);
435 
436 	fDocument->SetNativeSaver(new MessengerSaver(externalObserver));
437 
438 	locker.Unlock();
439 
440 	if (mainWindowLocked) {
441 		fMainWindow->Unlock();
442 		// cause the mainwindow to adopt icon in
443 		// it's own thread
444 		fMainWindow->PostMessage(MSG_SET_ICON);
445 	}
446 }
447 
448 // _CreateSaver
449 DocumentSaver*
450 IconEditorApp::_CreateSaver(const entry_ref& ref, uint32 exportMode)
451 {
452 	DocumentSaver* saver;
453 
454 	switch (exportMode) {
455 		case EXPORT_MODE_FLAT_ICON:
456 			saver = new SimpleFileSaver(new FlatIconExporter(), ref);
457 			break;
458 
459 		case EXPORT_MODE_ICON_ATTR:
460 		case EXPORT_MODE_ICON_MIME_ATTR: {
461 			const char* attrName
462 				= exportMode == EXPORT_MODE_ICON_ATTR ?
463 					kVectorAttrNodeName : kVectorAttrMimeName;
464 			saver = new AttributeSaver(ref, attrName);
465 			break;
466 		}
467 
468 		case EXPORT_MODE_ICON_RDEF:
469 			saver = new SimpleFileSaver(new RDefExporter(), ref);
470 			break;
471 
472 		case EXPORT_MODE_BITMAP:
473 			saver = new SimpleFileSaver(new BitmapExporter(64), ref);
474 			break;
475 
476 		case EXPORT_MODE_BITMAP_SET:
477 			saver = new BitmapSetSaver(ref);
478 			break;
479 
480 		case EXPORT_MODE_SVG:
481 			saver = new SimpleFileSaver(new SVGExporter(), ref);
482 			break;
483 
484 		case EXPORT_MODE_MESSAGE:
485 		default:
486 			saver = new NativeSaver(ref);
487 			break;
488 	}
489 
490 	return saver;
491 }
492 
493 // _SyncPanels
494 void
495 IconEditorApp::_SyncPanels(BFilePanel* from, BFilePanel* to)
496 {
497 	if (from->Window()->Lock()) {
498 		// location
499 		if (to->Window()->Lock()) {
500 			BRect frame = from->Window()->Frame();
501 			to->Window()->MoveTo(frame.left, frame.top);
502 			to->Window()->ResizeTo(frame.Width(), frame.Height());
503 			to->Window()->Unlock();
504 		}
505 		// current folder
506 		entry_ref panelDir;
507 		from->GetPanelDirectory(&panelDir);
508 		to->SetPanelDirectory(&panelDir);
509 		from->Window()->Unlock();
510 	}
511 }
512 
513 // _LastFilePath
514 const char*
515 IconEditorApp::_LastFilePath(path_kind which)
516 {
517 	const char* path = NULL;
518 
519 	switch (which) {
520 		case LAST_PATH_OPEN:
521 			if (fLastOpenPath.Length() > 0)
522 				path = fLastOpenPath.String();
523 			else if (fLastSavePath.Length() > 0)
524 				path = fLastSavePath.String();
525 			else if (fLastExportPath.Length() > 0)
526 				path = fLastExportPath.String();
527 			break;
528 		case LAST_PATH_SAVE:
529 			if (fLastSavePath.Length() > 0)
530 				path = fLastSavePath.String();
531 			else if (fLastExportPath.Length() > 0)
532 				path = fLastExportPath.String();
533 			else if (fLastOpenPath.Length() > 0)
534 				path = fLastOpenPath.String();
535 			break;
536 		case LAST_PATH_EXPORT:
537 			if (fLastExportPath.Length() > 0)
538 				path = fLastExportPath.String();
539 			else if (fLastSavePath.Length() > 0)
540 				path = fLastSavePath.String();
541 			else if (fLastOpenPath.Length() > 0)
542 				path = fLastOpenPath.String();
543 			break;
544 	}
545 	if (!path)
546 		path = "/boot/home";
547 
548 	return path;
549 }
550 
551 // #pragma mark -
552 
553 // _StoreSettings
554 void
555 IconEditorApp::_StoreSettings()
556 {
557 	BMessage settings('stns');
558 
559 	fMainWindow->StoreSettings(&settings);
560 
561 	if (settings.ReplaceInt32("export mode", fSavePanel->ExportMode()) < B_OK)
562 		settings.AddInt32("export mode", fSavePanel->ExportMode());
563 
564 	save_settings(&settings, "Icon-O-Matic");
565 }
566 
567 // _RestoreSettings
568 void
569 IconEditorApp::_RestoreSettings()
570 {
571 	BMessage settings('stns');
572 	load_settings(&settings, "Icon-O-Matic");
573 
574 	int32 mode;
575 	if (settings.FindInt32("export mode", &mode) >= B_OK)
576 		fSavePanel->SetExportMode(mode);
577 
578 	fMainWindow->RestoreSettings(&settings);
579 }
580 
581 // _InstallDocumentMimeType
582 void
583 IconEditorApp::_InstallDocumentMimeType()
584 {
585 	// install mime type of documents
586 	BMimeType mime(kNativeIconMimeType);
587 	status_t ret = mime.InitCheck();
588 	if (ret < B_OK) {
589 		fprintf(stderr, "Could not init native document mime type (%s): %s.\n",
590 			kNativeIconMimeType, strerror(ret));
591 		return;
592 	}
593 
594 	if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) {
595 		// mime is already installed, and the user is not
596 		// pressing the shift key to force a re-install
597 		return;
598 	}
599 
600 	ret = mime.Install();
601 	if (ret < B_OK) {
602 		fprintf(stderr, "Could not install native document mime type (%s): %s.\n",
603 			kNativeIconMimeType, strerror(ret));
604 		return;
605 	}
606 	// set preferred app
607 	ret = mime.SetPreferredApp(kAppSig);
608 	if (ret < B_OK)
609 		fprintf(stderr, "Could not set native document preferred app: %s\n",
610 			strerror(ret));
611 
612 	// set descriptions
613 	ret = mime.SetShortDescription("Haiku Icon");
614 	if (ret < B_OK)
615 		fprintf(stderr, "Could not set short description of mime type: %s\n",
616 			strerror(ret));
617 	ret = mime.SetLongDescription("Native Haiku vector icon");
618 	if (ret < B_OK)
619 		fprintf(stderr, "Could not set long description of mime type: %s\n",
620 			strerror(ret));
621 
622 	// set extensions
623 	BMessage message('extn');
624 	message.AddString("extensions", "icon");
625 	ret = mime.SetFileExtensions(&message);
626 	if (ret < B_OK)
627 		fprintf(stderr, "Could not set extensions of mime type: %s\n",
628 			strerror(ret));
629 
630 	// set sniffer rule
631 	const char* snifferRule = "0.9 ('GSMI')";
632 	ret = mime.SetSnifferRule(snifferRule);
633 	if (ret < B_OK) {
634 		BString parseError;
635 		BMimeType::CheckSnifferRule(snifferRule, &parseError);
636 		fprintf(stderr, "Could not set sniffer rule of mime type: %s\n",
637 			parseError.String());
638 	}
639 
640 // NOTE: Icon-O-Matic writes the icon being saved as icon of the file anyways
641 // therefor, the following code is not needed, it is also not tested and I am
642 // spotting an error with SetIcon()
643 //	// set document icon
644 //	BResources* resources = AppResources();
645 //		// does not need to be freed (belongs to BApplication base)
646 //	if (resources) {
647 //		size_t size;
648 //		const void* iconData = resources->LoadResource('VICN', "IOM:DOC_ICON", &size);
649 //		if (iconData && size > 0) {
650 //			memcpy(largeIcon.Bits(), iconData, size);
651 //			if (mime.SetIcon(&largeIcon, B_LARGE_ICON) < B_OK)
652 //				fprintf(stderr, "Could not set large icon of mime type.\n");
653 //		} else
654 //			fprintf(stderr, "Could not find icon in app resources (data: %p, size: %ld).\n", iconData, size);
655 //	} else
656 //		fprintf(stderr, "Could not find app resources.\n");
657 }
658