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