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