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