xref: /haiku/src/apps/icon-o-matic/MainWindow.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
1 /*
2  * Copyright 2006-2011, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2023, Haiku, Inc.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  *
6  * Authors:
7  *             Zardshard
8  */
9 
10 #include "MainWindow.h"
11 
12 #include <new>
13 #include <stdio.h>
14 
15 #include <Alert.h>
16 #include <Bitmap.h>
17 #include <Catalog.h>
18 #include <Clipboard.h>
19 #include <GridLayout.h>
20 #include <GroupLayout.h>
21 #include <GroupView.h>
22 #include <Directory.h>
23 #include <Entry.h>
24 #include <File.h>
25 #include <fs_attr.h>
26 #include <LayoutBuilder.h>
27 #include <Locale.h>
28 #include <Menu.h>
29 #include <MenuBar.h>
30 #include <MenuField.h>
31 #include <MenuItem.h>
32 #include <Message.h>
33 #include <MimeType.h>
34 #include <Screen.h>
35 #include <ScrollView.h>
36 #include <TranslationUtils.h>
37 
38 #include "support_ui.h"
39 
40 #include "AddPathsCommand.h"
41 #include "AddShapesCommand.h"
42 #include "AddStylesCommand.h"
43 #include "AttributeSaver.h"
44 #include "BitmapExporter.h"
45 #include "BitmapSetSaver.h"
46 #include "CanvasView.h"
47 #include "CommandStack.h"
48 #include "CompoundCommand.h"
49 #include "CurrentColor.h"
50 #include "Document.h"
51 #include "FlatIconExporter.h"
52 #include "FlatIconFormat.h"
53 #include "FlatIconImporter.h"
54 #include "IconObjectListView.h"
55 #include "IconEditorApp.h"
56 #include "IconView.h"
57 #include "MessageExporter.h"
58 #include "MessageImporter.h"
59 #include "MessengerSaver.h"
60 #include "NativeSaver.h"
61 #include "PathListView.h"
62 #include "PerspectiveBox.h"
63 #include "PerspectiveTransformer.h"
64 #include "RDefExporter.h"
65 #include "ScrollView.h"
66 #include "SimpleFileSaver.h"
67 #include "ShapeListView.h"
68 #include "SourceExporter.h"
69 #include "StyleListView.h"
70 #include "StyleView.h"
71 #include "SVGExporter.h"
72 #include "SVGImporter.h"
73 #include "SwatchGroup.h"
74 #include "TransformerListView.h"
75 #include "TransformGradientBox.h"
76 #include "TransformShapesBox.h"
77 #include "Util.h"
78 
79 // TODO: just for testing
80 #include "AffineTransformer.h"
81 #include "GradientTransformable.h"
82 #include "Icon.h"
83 #include "MultipleManipulatorState.h"
84 #include "PathManipulator.h"
85 #include "PathSourceShape.h"
86 #include "ReferenceImage.h"
87 #include "Shape.h"
88 #include "ShapeListView.h"
89 #include "StrokeTransformer.h"
90 #include "Style.h"
91 #include "VectorPath.h"
92 
93 #include "StyledTextImporter.h"
94 
95 
96 #undef B_TRANSLATION_CONTEXT
97 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-Main"
98 
99 
100 using std::nothrow;
101 
102 enum {
103 	MSG_UNDO						= 'undo',
104 	MSG_REDO						= 'redo',
105 	MSG_UNDO_STACK_CHANGED			= 'usch',
106 
107 	MSG_PATH_SELECTED				= 'vpsl',
108 	MSG_STYLE_SELECTED				= 'stsl',
109 	MSG_SHAPE_SELECTED				= 'spsl',
110 	MSG_TRANSFORMER_SELECTED		= 'trsl',
111 
112 	MSG_SHAPE_RESET_TRANSFORMATION	= 'rtsh',
113 	MSG_STYLE_RESET_TRANSFORMATION	= 'rtst',
114 
115 	MSG_MOUSE_FILTER_MODE			= 'mfmd',
116 
117 	MSG_RENAME_OBJECT				= 'rnam',
118 };
119 
120 
121 MainWindow::MainWindow(BRect frame, IconEditorApp* app,
122 		const BMessage* settings)
123 	:
124 	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Icon-O-Matic"),
125 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
126 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
127 	fApp(app),
128 	fDocument(new Document(B_TRANSLATE("Untitled"))),
129 	fCurrentColor(new CurrentColor()),
130 	fIcon(NULL),
131 	fMessageAfterSave(NULL)
132 {
133 	_Init();
134 
135 	RestoreSettings(settings);
136 }
137 
138 
139 MainWindow::~MainWindow()
140 {
141 	SetIcon(NULL);
142 
143 	delete fState;
144 
145 	// Make sure there are no listeners attached to the document anymore.
146 	while (BView* child = ChildAt(0L)) {
147 		child->RemoveSelf();
148 		delete child;
149 	}
150 
151 	fDocument->CommandStack()->RemoveObserver(this);
152 
153 	// NOTE: it is important that the GUI has been deleted
154 	// at this point, so that all the listener/observer
155 	// stuff is properly detached
156 	delete fDocument;
157 
158 	delete fCurrentColor;
159 	delete fMessageAfterSave;
160 }
161 
162 
163 // #pragma mark -
164 
165 
166 void
167 MainWindow::MessageReceived(BMessage* message)
168 {
169 	bool discard = false;
170 
171 	// Figure out if we need the write lock on the Document. For most
172 	// messages we do, but exporting takes place in another thread and
173 	// locking is taken care of there.
174 	bool requiresWriteLock = true;
175 	switch (message->what) {
176 		case MSG_SAVE:
177 		case MSG_EXPORT:
178 		case MSG_SAVE_AS:
179 		case MSG_EXPORT_AS:
180 			requiresWriteLock = false;
181 			break;
182 		default:
183 			break;
184 	}
185 	if (requiresWriteLock && !fDocument->WriteLock()) {
186 		BWindow::MessageReceived(message);
187 		return;
188 	}
189 
190 	if (message->WasDropped()) {
191 		const rgb_color* color;
192 		ssize_t length;
193 		// create styles from dropped colors
194 		for (int32 i = 0; message->FindData("RGBColor", B_RGB_COLOR_TYPE, i,
195 			(const void**)&color, &length) == B_OK; i++) {
196 			if (length != sizeof(rgb_color))
197 				continue;
198 			char name[30];
199 			sprintf(name,
200 				B_TRANSLATE_COMMENT("Color (#%02x%02x%02x)",
201 					"Style name after dropping a color"),
202 				color->red, color->green, color->blue);
203 			Style* style = new (nothrow) Style(*color);
204 			style->SetName(name);
205 			Style* styles[1] = { style };
206 			AddCommand<Style>* styleCommand = new (nothrow) AddCommand<Style>(
207 				fDocument->Icon()->Styles(), styles, 1, true,
208 				fDocument->Icon()->Styles()->CountItems());
209 			fDocument->CommandStack()->Perform(styleCommand);
210 			// don't handle anything else,
211 			// or we might paste the clipboard on B_PASTE
212 			discard = true;
213 		}
214 	}
215 
216 	switch (message->what) {
217 
218 		case B_REFS_RECEIVED:
219 		case B_SIMPLE_DATA:
220 		{
221 			entry_ref ref;
222 			if (message->FindRef("refs", &ref) != B_OK)
223 				break;
224 
225 			// Check if this is best represented by a ReferenceImage
226 			BMimeType type;
227 			if (BMimeType::GuessMimeType(&ref, &type) == B_OK) {
228 				BMimeType superType;
229 				if (type.GetSupertype(&superType) == B_OK
230 					&& superType == BMimeType("image")
231 					&& !(type == BMimeType("image/svg+xml"))
232 					&& !(type == BMimeType("image/x-hvif"))) {
233 					AddReferenceImage(ref);
234 					break;
235 				}
236 			}
237 
238 			// If our icon is empty, open the file in this window,
239 			// otherwise forward to the application which will open
240 			// it in another window, unless we append.
241 			message->what = B_REFS_RECEIVED;
242 			if (fDocument->Icon()->Styles()->CountItems() == 0
243 				&& fDocument->Icon()->Paths()->CountItems() == 0
244 				&& fDocument->Icon()->Shapes()->CountItems() == 0) {
245 				Open(ref);
246 				break;
247 			}
248 			if (modifiers() & B_SHIFT_KEY) {
249 				// We want the icon appended to this window.
250 				message->AddBool("append", true);
251 				message->AddPointer("window", this);
252 			}
253 			be_app->PostMessage(message);
254 			break;
255 		}
256 
257 		case B_PASTE:
258 		{
259 			if (discard)
260 				break;
261 
262 			if (!be_clipboard->Lock())
263 				break;
264 
265 			BMessage* clip = be_clipboard->Data();
266 
267 			if (!clip) {
268 				be_clipboard->Unlock();
269 				break;
270 			}
271 
272 			if (clip->HasData("text/plain", B_MIME_TYPE)) {
273 				AddStyledText(clip);
274 			} else if (clip->HasData(
275 					"application/x-vnd.icon_o_matic-listview-message", B_MIME_TYPE)) {
276 				ssize_t length;
277 				const char* data = NULL;
278 				if (clip->FindData("application/x-vnd.icon_o_matic-listview-message",
279 						B_MIME_TYPE, (const void**)&data, &length) != B_OK)
280 					break;
281 
282 				BMessage archive;
283 				archive.Unflatten(data);
284 
285 				if (archive.what == PathListView::kSelectionArchiveCode)
286 					fPathListView->HandlePaste(&archive);
287 				if (archive.what == ShapeListView::kSelectionArchiveCode)
288 					fShapeListView->HandlePaste(&archive);
289 				if (archive.what == StyleListView::kSelectionArchiveCode)
290 					fStyleListView->HandlePaste(&archive);
291 				if (archive.what == TransformerListView::kSelectionArchiveCode)
292 					fTransformerListView->HandlePaste(&archive);
293 			}
294 
295 			be_clipboard->Unlock();
296 			break;
297 		}
298 		case B_MIME_DATA:
299 			AddStyledText(message);
300 			break;
301 
302 		case MSG_OPEN:
303 		{
304 			// If our icon is empty, we want the icon to open in this
305 			// window.
306 			bool emptyDocument = fDocument->Icon()->Styles()->CountItems() == 0
307 				&& fDocument->Icon()->Paths()->CountItems() == 0
308 				&& fDocument->Icon()->Shapes()->CountItems() == 0;
309 
310 			bool openingReferenceImage;
311 			if (message->FindBool("reference image", &openingReferenceImage) != B_OK)
312 				openingReferenceImage = false;
313 
314 			if (emptyDocument || openingReferenceImage)
315 				message->AddPointer("window", this);
316 
317 			be_app->PostMessage(message);
318 			break;
319 		}
320 
321 		case MSG_SAVE:
322 		case MSG_EXPORT:
323 		{
324 			DocumentSaver* saver;
325 			if (message->what == MSG_SAVE)
326 				saver = fDocument->NativeSaver();
327 			else
328 				saver = fDocument->ExportSaver();
329 			if (saver != NULL) {
330 				saver->Save(fDocument);
331 				_PickUpActionBeforeSave();
332 				break;
333 			} // else fall through
334 		}
335 		case MSG_SAVE_AS:
336 		case MSG_EXPORT_AS:
337 		{
338 			int32 exportMode;
339 			if (message->FindInt32("export mode", &exportMode) < B_OK)
340 				exportMode = EXPORT_MODE_MESSAGE;
341 			entry_ref ref;
342 			const char* name;
343 			if (message->FindRef("directory", &ref) == B_OK
344 				&& message->FindString("name", &name) == B_OK) {
345 				// this message comes from the file panel
346 				BDirectory dir(&ref);
347 				BEntry entry;
348 				if (dir.InitCheck() >= B_OK
349 					&& entry.SetTo(&dir, name, true) >= B_OK
350 					&& entry.GetRef(&ref) >= B_OK) {
351 
352 					// create the document saver and remember it for later
353 					DocumentSaver* saver = _CreateSaver(ref, exportMode);
354 					if (saver != NULL) {
355 						if (fDocument->WriteLock()) {
356 							if (exportMode == EXPORT_MODE_MESSAGE)
357 								fDocument->SetNativeSaver(saver);
358 							else
359 								fDocument->SetExportSaver(saver);
360 							_UpdateWindowTitle();
361 							fDocument->WriteUnlock();
362 						}
363 						saver->Save(fDocument);
364 						_PickUpActionBeforeSave();
365 					}
366 				}
367 // TODO: ...
368 //				_SyncPanels(fSavePanel, fOpenPanel);
369 			} else {
370 				// configure the file panel
371 				uint32 requestRefWhat = MSG_SAVE_AS;
372 				bool isExportMode = message->what == MSG_EXPORT_AS
373 					|| message->what == MSG_EXPORT;
374 				if (isExportMode)
375 					requestRefWhat = MSG_EXPORT_AS;
376 				const entry_ref* fileRef = _FileRef(isExportMode);
377 				const char* saveText = NULL;
378 
379 				BMessage requestRef(requestRefWhat);
380 				if (fileRef != NULL) {
381 					saveText = fileRef->name;
382 					BEntry saveDirectory;
383 					if (BEntry(fileRef).GetParent(&saveDirectory) == B_OK) {
384 						entry_ref saveDirectoryRef;
385 						if (saveDirectory.GetRef(&saveDirectoryRef) == B_OK)
386 							requestRef.AddRef("save directory", &saveDirectoryRef);
387 					}
388 				}
389 				if (saveText != NULL)
390 					requestRef.AddString("save text", saveText);
391 				requestRef.AddMessenger("target", BMessenger(this, this));
392 				be_app->PostMessage(&requestRef);
393 			}
394 			break;
395 		}
396 		case B_CANCEL:
397 			// FilePanel was canceled, do not execute the fMessageAfterSave
398 			// next time a file panel is used, in case it was set!
399 			delete fMessageAfterSave;
400 			fMessageAfterSave = NULL;
401 			break;
402 
403 		case MSG_UNDO:
404 			fDocument->CommandStack()->Undo();
405 			break;
406 		case MSG_REDO:
407 			fDocument->CommandStack()->Redo();
408 			break;
409 		case MSG_UNDO_STACK_CHANGED:
410 		{
411 			// relable Undo item and update enabled status
412 			BString label(B_TRANSLATE("Undo: %action%"));
413 			BString temp;
414 			fUndoMI->SetEnabled(fDocument->CommandStack()->GetUndoName(temp));
415 			label.ReplaceFirst("%action%", temp);
416 			if (fUndoMI->IsEnabled())
417 				fUndoMI->SetLabel(label.String());
418 			else {
419 				fUndoMI->SetLabel(B_TRANSLATE_CONTEXT("<nothing to undo>",
420 					"Icon-O-Matic-Menu-Edit"));
421 			}
422 
423 			// relable Redo item and update enabled status
424 			label.SetTo(B_TRANSLATE("Redo: %action%"));
425 			temp.SetTo("");
426 			fRedoMI->SetEnabled(fDocument->CommandStack()->GetRedoName(temp));
427 			label.ReplaceFirst("%action%", temp);
428 			if (fRedoMI->IsEnabled())
429 				fRedoMI->SetLabel(label.String());
430 			else {
431 				fRedoMI->SetLabel(B_TRANSLATE_CONTEXT("<nothing to redo>",
432 					"Icon-O-Matic-Menu-Edit"));
433 			}
434 			break;
435 		}
436 
437 		case MSG_MOUSE_FILTER_MODE:
438 		{
439 			uint32 mode;
440 			if (message->FindInt32("mode", (int32*)&mode) == B_OK)
441 				fCanvasView->SetMouseFilterMode(mode);
442 			break;
443 		}
444 
445 		case MSG_ADD_SHAPE: {
446 			AddStylesCommand* styleCommand = NULL;
447 			Style* style = NULL;
448 			if (message->HasBool("style")) {
449 				new_style(fCurrentColor->Color(),
450 					fDocument->Icon()->Styles(), &style, &styleCommand);
451 			}
452 
453 			AddPathsCommand* pathCommand = NULL;
454 			VectorPath* path = NULL;
455 			if (message->HasBool("path")) {
456 				new_path(fDocument->Icon()->Paths(), &path, &pathCommand);
457 			}
458 
459 			if (!style) {
460 				// use current or first style
461 				int32 currentStyle = fStyleListView->CurrentSelection(0);
462 				style = fDocument->Icon()->Styles()->ItemAt(currentStyle);
463 				if (!style)
464 					style = fDocument->Icon()->Styles()->ItemAt(0);
465 			}
466 
467 			PathSourceShape* shape = new (nothrow) PathSourceShape(style);
468 			AddShapesCommand* shapeCommand = new (nothrow) AddShapesCommand(
469 				fDocument->Icon()->Shapes(), (Shape**) &shape, 1,
470 				fDocument->Icon()->Shapes()->CountItems());
471 
472 			if (path && shape)
473 				shape->Paths()->AddItem(path);
474 
475 			::Command* command = NULL;
476 			if (styleCommand || pathCommand) {
477 				if (styleCommand && pathCommand) {
478 					Command** commands = new Command*[3];
479 					commands[0] = styleCommand;
480 					commands[1] = pathCommand;
481 					commands[2] = shapeCommand;
482 					command = new CompoundCommand(commands, 3,
483 						B_TRANSLATE_CONTEXT("Add shape with path & style",
484 							"Icon-O-Matic-Menu-Shape"),
485 						0);
486 				} else if (styleCommand) {
487 					Command** commands = new Command*[2];
488 					commands[0] = styleCommand;
489 					commands[1] = shapeCommand;
490 					command = new CompoundCommand(commands, 2,
491 						B_TRANSLATE_CONTEXT("Add shape with style",
492 							"Icon-O-Matic-Menu-Shape"),
493 						0);
494 				} else {
495 					Command** commands = new Command*[2];
496 					commands[0] = pathCommand;
497 					commands[1] = shapeCommand;
498 					command = new CompoundCommand(commands, 2,
499 						B_TRANSLATE_CONTEXT("Add shape with path",
500 							"Icon-O-Matic-Menu-Shape"),
501 						0);
502 				}
503 			} else {
504 				command = shapeCommand;
505 			}
506 			fDocument->CommandStack()->Perform(command);
507 			break;
508 		}
509 
510 // TODO: listen to selection in CanvasView to add a manipulator
511 case MSG_PATH_SELECTED: {
512 	VectorPath* path;
513 	if (message->FindPointer("path", (void**)&path) < B_OK)
514 		path = NULL;
515 
516 	fPathListView->SetCurrentShape(NULL);
517 	fStyleListView->SetCurrentShape(NULL);
518 	fTransformerListView->SetShape(NULL);
519 
520 	fState->DeleteManipulators();
521 	if (fDocument->Icon()->Paths()->HasItem(path)) {
522 		PathManipulator* pathManipulator = new (nothrow) PathManipulator(path);
523 		fState->AddManipulator(pathManipulator);
524 	}
525 	break;
526 }
527 case MSG_STYLE_SELECTED:
528 case MSG_STYLE_TYPE_CHANGED: {
529 	Style* style;
530 	if (message->FindPointer("style", (void**)&style) < B_OK)
531 		style = NULL;
532 	if (!fDocument->Icon()->Styles()->HasItem(style))
533 		style = NULL;
534 
535 	fStyleView->SetStyle(style);
536 	fPathListView->SetCurrentShape(NULL);
537 	fStyleListView->SetCurrentShape(NULL);
538 	fTransformerListView->SetShape(NULL);
539 
540 	fState->DeleteManipulators();
541 	Gradient* gradient = style ? style->Gradient() : NULL;
542 	if (gradient != NULL) {
543 		TransformGradientBox* transformBox
544 			= new (nothrow) TransformGradientBox(fCanvasView, gradient, NULL);
545 		fState->AddManipulator(transformBox);
546 	}
547 	break;
548 }
549 case MSG_SHAPE_SELECTED: {
550 	Shape* shape;
551 	if (message->FindPointer("shape", (void**)&shape) < B_OK)
552 		shape = NULL;
553 	if (!fIcon || !fIcon->Shapes()->HasItem(shape))
554 		shape = NULL;
555 
556 	fPathListView->SetCurrentShape(shape);
557 	fStyleListView->SetCurrentShape(shape);
558 	fTransformerListView->SetShape(shape);
559 
560 	BList selectedShapes;
561 	Container<Shape>* shapes = fDocument->Icon()->Shapes();
562 	int32 count = shapes->CountItems();
563 	for (int32 i = 0; i < count; i++) {
564 		shape = shapes->ItemAtFast(i);
565 		if (shape->IsSelected()) {
566 			selectedShapes.AddItem((void*)shape);
567 		}
568 	}
569 
570 	fState->DeleteManipulators();
571 	if (selectedShapes.CountItems() > 0) {
572 		TransformShapesBox* transformBox = new (nothrow) TransformShapesBox(
573 			fCanvasView,
574 			(const Shape**)selectedShapes.Items(),
575 			selectedShapes.CountItems());
576 		fState->AddManipulator(transformBox);
577 	}
578 	break;
579 }
580 case MSG_TRANSFORMER_SELECTED: {
581 	Transformer* transformer;
582 	if (message->FindPointer("transformer", (void**)&transformer) < B_OK)
583 		transformer = NULL;
584 
585 	fState->DeleteManipulators();
586 	PerspectiveTransformer* perspectiveTransformer =
587 		dynamic_cast<PerspectiveTransformer*>(transformer);
588 	if (perspectiveTransformer != NULL) {
589 		PerspectiveBox* transformBox = new (nothrow) PerspectiveBox(
590 			fCanvasView, perspectiveTransformer);
591 		fState->AddManipulator(transformBox);
592 	}
593 }
594 		case MSG_RENAME_OBJECT:
595 			fPropertyListView->FocusNameProperty();
596 			break;
597 
598 		default:
599 			BWindow::MessageReceived(message);
600 	}
601 
602 	if (requiresWriteLock)
603 		fDocument->WriteUnlock();
604 }
605 
606 
607 void
608 MainWindow::Show()
609 {
610 	BWindow::Show();
611 	BMenuBar* bar = static_cast<BMenuBar*>(FindView("main menu"));
612 	SetKeyMenuBar(bar);
613 }
614 
615 
616 bool
617 MainWindow::QuitRequested()
618 {
619 	if (!_CheckSaveIcon(CurrentMessage()))
620 		return false;
621 
622 	BMessage message(MSG_WINDOW_CLOSED);
623 
624 	BMessage settings;
625 	StoreSettings(&settings);
626 	message.AddMessage("settings", &settings);
627 	message.AddRect("window frame", Frame());
628 
629 	be_app->PostMessage(&message);
630 
631 	return true;
632 }
633 
634 
635 void
636 MainWindow::WorkspaceActivated(int32 workspace, bool active)
637 {
638 	BWindow::WorkspaceActivated(workspace, active);
639 
640 	if (active)
641 		_WorkspaceEntered();
642 }
643 
644 
645 void
646 MainWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces)
647 {
648 	BWindow::WorkspacesChanged(oldWorkspaces, newWorkspaces);
649 
650 	if((1 << current_workspace() & newWorkspaces) != 0)
651 		_WorkspaceEntered();
652 }
653 
654 
655 // #pragma mark -
656 
657 
658 void
659 MainWindow::ObjectChanged(const Observable* object)
660 {
661 	if (!fDocument || !fDocument->ReadLock())
662 		return;
663 
664 	if (object == fDocument->CommandStack())
665 		PostMessage(MSG_UNDO_STACK_CHANGED);
666 
667 	fDocument->ReadUnlock();
668 }
669 
670 
671 // #pragma mark -
672 
673 
674 void
675 MainWindow::MakeEmpty()
676 {
677 	fPathListView->SetCurrentShape(NULL);
678 	fStyleListView->SetCurrentShape(NULL);
679 	fStyleView->SetStyle(NULL);
680 
681 	fTransformerListView->SetShape(NULL);
682 
683 	fState->DeleteManipulators();
684 }
685 
686 
687 void
688 MainWindow::Open(const entry_ref& ref, bool append)
689 {
690 	BFile file(&ref, B_READ_ONLY);
691 	if (file.InitCheck() < B_OK)
692 		return;
693 
694 	Icon* icon;
695 	if (append)
696 		icon = new (nothrow) Icon(*fDocument->Icon());
697 	else
698 		icon = new (nothrow) Icon();
699 
700 	if (icon == NULL) {
701 		// TODO: Report error to user.
702 		return;
703 	}
704 
705 	enum {
706 		REF_NONE = 0,
707 		REF_MESSAGE,
708 		REF_FLAT,
709 		REF_FLAT_ATTR,
710 		REF_SVG
711 	};
712 	uint32 refMode = REF_NONE;
713 
714 	// try different file types
715 	FlatIconImporter flatImporter;
716 	status_t ret = flatImporter.Import(icon, &file);
717 	if (ret >= B_OK) {
718 		refMode = REF_FLAT;
719 	} else {
720 		file.Seek(0, SEEK_SET);
721 		MessageImporter msgImporter;
722 		ret = msgImporter.Import(icon, &file);
723 		if (ret >= B_OK) {
724 			refMode = REF_MESSAGE;
725 		} else {
726 			file.Seek(0, SEEK_SET);
727 			SVGImporter svgImporter;
728 			ret = svgImporter.Import(icon, &ref);
729 			if (ret >= B_OK) {
730 				refMode = REF_SVG;
731 			} else {
732 				// fall back to flat icon format but use the icon attribute
733 				ret = B_OK;
734 				attr_info attrInfo;
735 				if (file.GetAttrInfo(kVectorAttrNodeName, &attrInfo) == B_OK) {
736 					if (attrInfo.type != B_VECTOR_ICON_TYPE)
737 						ret = B_ERROR;
738 					// If the attribute is there, we must succeed in reading
739 					// an icon! Otherwise we may overwrite an existing icon
740 					// when the user saves.
741 					uint8* buffer = NULL;
742 					if (ret == B_OK) {
743 						buffer = new(nothrow) uint8[attrInfo.size];
744 						if (buffer == NULL)
745 							ret = B_NO_MEMORY;
746 					}
747 					if (ret == B_OK) {
748 						ssize_t bytesRead = file.ReadAttr(kVectorAttrNodeName,
749 							B_VECTOR_ICON_TYPE, 0, buffer, attrInfo.size);
750 						if (bytesRead != (ssize_t)attrInfo.size) {
751 							ret = bytesRead < 0 ? (status_t)bytesRead
752 								: B_IO_ERROR;
753 						}
754 					}
755 					if (ret == B_OK) {
756 						ret = flatImporter.Import(icon, buffer, attrInfo.size);
757 						if (ret == B_OK)
758 							refMode = REF_FLAT_ATTR;
759 					}
760 
761 					delete[] buffer;
762 				} else {
763 					// If there is no icon attribute, simply fall back
764 					// to creating an icon for this file. TODO: We may or may
765 					// not want to display an alert asking the user if that is
766 					// what he wants to do.
767 					refMode = REF_FLAT_ATTR;
768 				}
769 			}
770 		}
771 	}
772 
773 	if (ret < B_OK) {
774 		// inform user of failure at this point
775 		BString helper(B_TRANSLATE("Opening the document failed!"));
776 		helper << "\n\n" << B_TRANSLATE("Error: ") << strerror(ret);
777 		BAlert* alert = new BAlert(
778 			B_TRANSLATE_CONTEXT("Bad news", "Title of error alert"),
779 			helper.String(),
780 			B_TRANSLATE_COMMENT("Bummer",
781 				"Cancel button - error alert"),
782 			NULL, NULL);
783 		// launch alert asynchronously
784 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
785 		alert->Go(NULL);
786 
787 		delete icon;
788 		return;
789 	}
790 
791 	AutoWriteLocker locker(fDocument);
792 
793 	// incorporate the loaded icon into the document
794 	// (either replace it or append to it)
795 	fDocument->MakeEmpty(!append);
796 		// if append, the document savers are preserved
797 	fDocument->SetIcon(icon);
798 	if (!append) {
799 		// document got replaced, but we have at
800 		// least one ref already
801 		switch (refMode) {
802 			case REF_MESSAGE:
803 				fDocument->SetNativeSaver(new NativeSaver(ref));
804 				break;
805 			case REF_FLAT:
806 				fDocument->SetExportSaver(
807 					new SimpleFileSaver(new FlatIconExporter(), ref));
808 				break;
809 			case REF_FLAT_ATTR:
810 				fDocument->SetNativeSaver(
811 					new AttributeSaver(ref, kVectorAttrNodeName));
812 				break;
813 			case REF_SVG:
814 				fDocument->SetExportSaver(
815 					new SimpleFileSaver(new SVGExporter(), ref));
816 				break;
817 		}
818 	}
819 
820 	locker.Unlock();
821 
822 	SetIcon(icon);
823 
824 	_UpdateWindowTitle();
825 }
826 
827 
828 void
829 MainWindow::Open(const BMessenger& externalObserver, const uint8* data,
830 	size_t size)
831 {
832 	if (!_CheckSaveIcon(CurrentMessage()))
833 		return;
834 
835 	if (!externalObserver.IsValid())
836 		return;
837 
838 	Icon* icon = new (nothrow) Icon();
839 	if (!icon)
840 		return;
841 
842 	if (data && size > 0) {
843 		// try to open the icon from the provided data
844 		FlatIconImporter flatImporter;
845 		status_t ret = flatImporter.Import(icon, const_cast<uint8*>(data),
846 			size);
847 			// NOTE: the const_cast is a bit ugly, but no harm is done
848 			// the reason is that the LittleEndianBuffer knows read and write
849 			// mode, in this case it is used read-only, and it does not assume
850 			// ownership of the buffer
851 
852 		if (ret < B_OK) {
853 			// inform user of failure at this point
854 			BString helper(B_TRANSLATE("Opening the icon failed!"));
855 			helper << "\n\n" << B_TRANSLATE("Error: ") << strerror(ret);
856 			BAlert* alert = new BAlert(
857 				B_TRANSLATE_CONTEXT("Bad news", "Title of error alert"),
858 				helper.String(),
859 				B_TRANSLATE_COMMENT("Bummer",
860 					"Cancel button - error alert"),
861 				NULL, NULL);
862 			// launch alert asynchronously
863 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
864 			alert->Go(NULL);
865 
866 			delete icon;
867 			return;
868 		}
869 	}
870 
871 	AutoWriteLocker locker(fDocument);
872 
873 	SetIcon(NULL);
874 
875 	// incorporate the loaded icon into the document
876 	// (either replace it or append to it)
877 	fDocument->MakeEmpty();
878 	fDocument->SetIcon(icon);
879 
880 	fDocument->SetNativeSaver(new MessengerSaver(externalObserver));
881 
882 	locker.Unlock();
883 
884 	SetIcon(icon);
885 }
886 
887 
888 void
889 MainWindow::AddReferenceImage(const entry_ref& ref)
890 {
891 	BBitmap* image = BTranslationUtils::GetBitmap(&ref);
892 	if (image == NULL)
893 		return;
894 	Shape* shape = new (nothrow) ReferenceImage(image);
895 	if (shape == NULL)
896 		return;
897 
898 	AddShapesCommand* shapeCommand = new (nothrow) AddShapesCommand(
899 		fDocument->Icon()->Shapes(), &shape, 1,
900 		fDocument->Icon()->Shapes()->CountItems());
901 	if (shapeCommand == NULL) {
902 		delete shape;
903 		return;
904 	}
905 
906 	fDocument->CommandStack()->Perform(shapeCommand);
907 }
908 
909 
910 void
911 MainWindow::AddStyledText(BMessage* message)
912 {
913 	Icon* icon = new (std::nothrow) Icon(*fDocument->Icon());
914 	if (icon != NULL) {
915 		StyledTextImporter importer;
916 		status_t err = importer.Import(icon, message);
917 		if (err >= B_OK) {
918 			AutoWriteLocker locker(fDocument);
919 
920 			SetIcon(NULL);
921 
922 			// incorporate the loaded icon into the document
923 			// (either replace it or append to it)
924 			fDocument->MakeEmpty(false);
925 				// if append, the document savers are preserved
926 			fDocument->SetIcon(icon);
927 			SetIcon(icon);
928 		}
929 	}
930 }
931 
932 
933 void
934 MainWindow::SetIcon(Icon* icon)
935 {
936 	if (fIcon == icon)
937 		return;
938 
939 	Icon* oldIcon = fIcon;
940 
941 	fIcon = icon;
942 
943 	if (fIcon != NULL)
944 		fIcon->AcquireReference();
945 	else
946 		MakeEmpty();
947 
948 	fCanvasView->SetIcon(fIcon);
949 
950 	fPathListView->SetPathContainer(fIcon != NULL ? fIcon->Paths() : NULL);
951 	fPathListView->SetShapeContainer(fIcon != NULL ? fIcon->Shapes() : NULL);
952 
953 	fStyleListView->SetStyleContainer(fIcon != NULL ? fIcon->Styles() : NULL);
954 	fStyleListView->SetShapeContainer(fIcon != NULL ? fIcon->Shapes() : NULL);
955 
956 	fShapeListView->SetShapeContainer(fIcon != NULL ? fIcon->Shapes() : NULL);
957 	fShapeListView->SetStyleContainer(fIcon != NULL ? fIcon->Styles() : NULL);
958 	fShapeListView->SetPathContainer(fIcon != NULL ? fIcon->Paths() : NULL);
959 
960 	// icon previews
961 	fIconPreview16Folder->SetIcon(fIcon);
962 	fIconPreview16Menu->SetIcon(fIcon);
963 	fIconPreview32Folder->SetIcon(fIcon);
964 	fIconPreview32Desktop->SetIcon(fIcon);
965 //	fIconPreview48->SetIcon(fIcon);
966 	fIconPreview64->SetIcon(fIcon);
967 
968 	// keep this last
969 	if (oldIcon != NULL)
970 		oldIcon->ReleaseReference();
971 }
972 
973 
974 // #pragma mark -
975 
976 
977 void
978 MainWindow::StoreSettings(BMessage* archive)
979 {
980 	if (archive->ReplaceUInt32("mouse filter mode",
981 			fCanvasView->MouseFilterMode()) != B_OK) {
982 		archive->AddUInt32("mouse filter mode",
983 			fCanvasView->MouseFilterMode());
984 	}
985 }
986 
987 
988 void
989 MainWindow::RestoreSettings(const BMessage* archive)
990 {
991 	uint32 mouseFilterMode;
992 	if (archive->FindUInt32("mouse filter mode", &mouseFilterMode) == B_OK) {
993 		fCanvasView->SetMouseFilterMode(mouseFilterMode);
994 		fMouseFilterOffMI->SetMarked(mouseFilterMode == SNAPPING_OFF);
995 		fMouseFilter64MI->SetMarked(mouseFilterMode == SNAPPING_64);
996 		fMouseFilter32MI->SetMarked(mouseFilterMode == SNAPPING_32);
997 		fMouseFilter16MI->SetMarked(mouseFilterMode == SNAPPING_16);
998 	}
999 }
1000 
1001 
1002 // #pragma mark -
1003 
1004 
1005 void
1006 MainWindow::_Init()
1007 {
1008 	// create the GUI
1009 	_CreateGUI();
1010 
1011 	// fix up scrollbar layout in listviews
1012 	_ImproveScrollBarLayout(fPathListView);
1013 	_ImproveScrollBarLayout(fStyleListView);
1014 	_ImproveScrollBarLayout(fShapeListView);
1015 	_ImproveScrollBarLayout(fTransformerListView);
1016 
1017 	// TODO: move this to CanvasView?
1018 	fState = new MultipleManipulatorState(fCanvasView);
1019 	fCanvasView->SetState(fState);
1020 
1021 	fCanvasView->SetCatchAllEvents(true);
1022 	fCanvasView->SetCommandStack(fDocument->CommandStack());
1023 	fCanvasView->SetMouseFilterMode(SNAPPING_64);
1024 	fMouseFilter64MI->SetMarked(true);
1025 //	fCanvasView->SetSelection(fDocument->Selection());
1026 
1027 	fPathListView->SetMenu(fPathMenu);
1028 	fPathListView->SetCommandStack(fDocument->CommandStack());
1029 	fPathListView->SetSelection(fDocument->Selection());
1030 
1031 	fStyleListView->SetMenu(fStyleMenu);
1032 	fStyleListView->SetCommandStack(fDocument->CommandStack());
1033 	fStyleListView->SetSelection(fDocument->Selection());
1034 	fStyleListView->SetCurrentColor(fCurrentColor);
1035 
1036 	fStyleView->SetCommandStack(fDocument->CommandStack());
1037 	fStyleView->SetCurrentColor(fCurrentColor);
1038 
1039 	fShapeListView->SetMenu(fShapeMenu);
1040 	fShapeListView->SetCommandStack(fDocument->CommandStack());
1041 	fShapeListView->SetSelection(fDocument->Selection());
1042 
1043 	fTransformerListView->SetMenu(fTransformerMenu);
1044 	fTransformerListView->SetCommandStack(fDocument->CommandStack());
1045 	fTransformerListView->SetSelection(fDocument->Selection());
1046 
1047 	fPropertyListView->SetCommandStack(fDocument->CommandStack());
1048 	fPropertyListView->SetSelection(fDocument->Selection());
1049 	fPropertyListView->SetMenu(fPropertyMenu);
1050 
1051 	fDocument->CommandStack()->AddObserver(this);
1052 
1053 	fSwatchGroup->SetCurrentColor(fCurrentColor);
1054 
1055 	SetIcon(fDocument->Icon());
1056 
1057 	AddShortcut('Y', 0, new BMessage(MSG_UNDO));
1058 	AddShortcut('Y', B_SHIFT_KEY, new BMessage(MSG_REDO));
1059 	AddShortcut('E', 0, new BMessage(MSG_RENAME_OBJECT));
1060 }
1061 
1062 
1063 void
1064 MainWindow::_CreateGUI()
1065 {
1066 	SetLayout(new BGroupLayout(B_HORIZONTAL));
1067 
1068 	BGridLayout* layout = new BGridLayout();
1069 	layout->SetSpacing(0, 0);
1070 	BView* rootView = new BView("root view", 0, layout);
1071 	AddChild(rootView);
1072 	rootView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1073 
1074 	BGroupView* leftTopView = new BGroupView(B_VERTICAL, 0);
1075 	layout->AddView(leftTopView, 0, 0);
1076 
1077 	// views along the left side
1078 	BMenuBar* mainMenuBar = _CreateMenuBar();
1079 	leftTopView->AddChild(mainMenuBar);
1080 
1081 	float splitWidth = 13 * be_plain_font->Size();
1082 	BSize minSize = leftTopView->MinSize();
1083 	splitWidth = std::max(splitWidth, minSize.width);
1084 	leftTopView->SetExplicitMaxSize(BSize(splitWidth, B_SIZE_UNSET));
1085 	leftTopView->SetExplicitMinSize(BSize(splitWidth, B_SIZE_UNSET));
1086 
1087 	BGroupView* iconPreviews = new BGroupView(B_HORIZONTAL);
1088 	iconPreviews->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1089 	iconPreviews->GroupLayout()->SetSpacing(5);
1090 
1091 	// icon previews
1092 	fIconPreview16Folder = new IconView(BRect(0, 0, 15, 15),
1093 		"icon preview 16 folder");
1094 	fIconPreview16Menu = new IconView(BRect(0, 0, 15, 15),
1095 		"icon preview 16 menu");
1096 	fIconPreview16Menu->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR));
1097 
1098 	fIconPreview32Folder = new IconView(BRect(0, 0, 31, 31),
1099 		"icon preview 32 folder");
1100 	fIconPreview32Desktop = new IconView(BRect(0, 0, 31, 31),
1101 		"icon preview 32 desktop");
1102 	fIconPreview32Desktop->SetLowColor(ui_color(B_DESKTOP_COLOR));
1103 
1104 	fIconPreview64 = new IconView(BRect(0, 0, 63, 63), "icon preview 64");
1105 	fIconPreview64->SetLowColor(ui_color(B_DESKTOP_COLOR));
1106 
1107 
1108 	BGroupView* smallPreviews = new BGroupView(B_VERTICAL);
1109 	smallPreviews->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1110 	smallPreviews->GroupLayout()->SetSpacing(5);
1111 
1112 	smallPreviews->AddChild(fIconPreview16Folder);
1113 	smallPreviews->AddChild(fIconPreview16Menu);
1114 
1115 	BGroupView* mediumPreviews = new BGroupView(B_VERTICAL);
1116 	mediumPreviews->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1117 	mediumPreviews->GroupLayout()->SetSpacing(5);
1118 
1119 	mediumPreviews->AddChild(fIconPreview32Folder);
1120 	mediumPreviews->AddChild(fIconPreview32Desktop);
1121 
1122 //	iconPreviews->AddChild(fIconPreview48);
1123 
1124 	iconPreviews->AddChild(smallPreviews);
1125 	iconPreviews->AddChild(mediumPreviews);
1126 	iconPreviews->AddChild(fIconPreview64);
1127 	iconPreviews->SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED));
1128 
1129 	leftTopView->AddChild(iconPreviews);
1130 
1131 
1132 	BSplitView* leftSideView = new BSplitView(B_VERTICAL, 0);
1133 	layout->AddView(leftSideView, 0, 1);
1134 	leftSideView->SetExplicitMaxSize(BSize(splitWidth, B_SIZE_UNSET));
1135 
1136 	fPathListView = new PathListView(BRect(0, 0, splitWidth, 100),
1137 		"path list view", new BMessage(MSG_PATH_SELECTED), this);
1138 	fShapeListView = new ShapeListView(BRect(0, 0, splitWidth, 100),
1139 		"shape list view", new BMessage(MSG_SHAPE_SELECTED), this);
1140 	fTransformerListView = new TransformerListView(BRect(0, 0, splitWidth, 100),
1141 		"transformer list view", new BMessage(MSG_TRANSFORMER_SELECTED), this);
1142 	fPropertyListView = new IconObjectListView();
1143 
1144 	BLayoutBuilder::Split<>(leftSideView)
1145 		.AddGroup(B_VERTICAL, 0)
1146 			.AddGroup(B_VERTICAL, 0)
1147 				.SetInsets(-2, -1, -1, -1)
1148 				.Add(new BMenuField(NULL, fPathMenu))
1149 			.End()
1150 			.Add(new BScrollView("path scroll view", fPathListView,
1151 				B_FOLLOW_NONE, 0, false, true, B_NO_BORDER))
1152 		.End()
1153 		.AddGroup(B_VERTICAL, 0)
1154 			.AddGroup(B_VERTICAL, 0)
1155 				.SetInsets(-2, -2, -1, -1)
1156 				.Add(new BMenuField(NULL, fShapeMenu))
1157 			.End()
1158 			.Add(new BScrollView("shape scroll view", fShapeListView,
1159 				B_FOLLOW_NONE, 0, false, true, B_NO_BORDER))
1160 		.End()
1161 		.AddGroup(B_VERTICAL, 0)
1162 			.AddGroup(B_VERTICAL, 0)
1163 				.SetInsets(-2, -2, -1, -1)
1164 				.Add(new BMenuField(NULL, fTransformerMenu))
1165 			.End()
1166 			.Add(new BScrollView("transformer scroll view",
1167 				fTransformerListView, B_FOLLOW_NONE, 0, false, true, B_NO_BORDER))
1168 		.End()
1169 		.AddGroup(B_VERTICAL, 0)
1170 			.AddGroup(B_VERTICAL, 0)
1171 				.SetInsets(-2, -2, -1, -1)
1172 				.Add(new BMenuField(NULL, fPropertyMenu))
1173 			.End()
1174 			.Add(new ScrollView(fPropertyListView, SCROLL_VERTICAL,
1175 				BRect(0, 0, splitWidth, 100), "property scroll view",
1176 				B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, B_PLAIN_BORDER,
1177 				BORDER_RIGHT))
1178 		.End()
1179 	.End();
1180 
1181 	BGroupLayout* topSide = new BGroupLayout(B_HORIZONTAL);
1182 	topSide->SetSpacing(0);
1183 	BView* topSideView = new BView("top side view", 0, topSide);
1184 	layout->AddView(topSideView, 1, 0);
1185 
1186 	// canvas view
1187 	BRect canvasBounds = BRect(0, 0, 200, 200);
1188 	fCanvasView = new CanvasView(canvasBounds);
1189 
1190 	// scroll view around canvas view
1191 	canvasBounds.bottom += B_H_SCROLL_BAR_HEIGHT;
1192 	canvasBounds.right += B_V_SCROLL_BAR_WIDTH;
1193 	ScrollView* canvasScrollView = new ScrollView(fCanvasView, SCROLL_VERTICAL
1194 			| SCROLL_HORIZONTAL | SCROLL_VISIBLE_RECT_IS_CHILD_BOUNDS,
1195 		canvasBounds, "canvas scroll view", B_FOLLOW_NONE,
1196 		B_WILL_DRAW | B_FRAME_EVENTS, B_NO_BORDER);
1197 	layout->AddView(canvasScrollView, 1, 1);
1198 
1199 	// views along the top
1200 
1201 	BGroupView* styleGroupView = new BGroupView(B_VERTICAL, 0);
1202 	topSide->AddView(styleGroupView);
1203 
1204 	fStyleListView = new StyleListView(BRect(0, 0, splitWidth, 100),
1205 		"style list view", new BMessage(MSG_STYLE_SELECTED), this);
1206 
1207 	BScrollView* scrollView = new BScrollView("style list scroll view",
1208 		fStyleListView, B_FOLLOW_NONE, 0, false, true, B_NO_BORDER);
1209 	scrollView->SetExplicitMaxSize(BSize(splitWidth, B_SIZE_UNLIMITED));
1210 
1211 	BLayoutBuilder::Group<>(styleGroupView)
1212 		.AddGroup(B_VERTICAL, 0)
1213 			.SetInsets(-2, -2, -1, -1)
1214 			.Add(new BMenuField(NULL, fStyleMenu))
1215 		.End()
1216 		.Add(scrollView)
1217 	.End();
1218 
1219 	// style view
1220 	fStyleView = new StyleView(BRect(0, 0, 200, 100));
1221 	topSide->AddView(fStyleView);
1222 
1223 	// swatch group
1224 	BGroupLayout* swatchGroup = new BGroupLayout(B_VERTICAL);
1225 	swatchGroup->SetSpacing(0);
1226 	BView* swatchGroupView = new BView("swatch group", 0, swatchGroup);
1227 	topSide->AddView(swatchGroupView);
1228 
1229 	BMenuBar* menuBar = new BMenuBar("swatches menu bar");
1230 	menuBar->AddItem(fSwatchMenu);
1231 	swatchGroup->AddView(menuBar);
1232 
1233 	fSwatchGroup = new SwatchGroup(BRect(0, 0, 100, 100));
1234 	swatchGroup->AddView(fSwatchGroup);
1235 
1236 	swatchGroupView->SetExplicitMaxSize(swatchGroupView->MinSize());
1237 
1238 	// make sure the top side has fixed height
1239 	topSideView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
1240 		swatchGroupView->MinSize().height));
1241 }
1242 
1243 
1244 BMenuBar*
1245 MainWindow::_CreateMenuBar()
1246 {
1247 	BMenuBar* menuBar = new BMenuBar("main menu");
1248 
1249 
1250 	#undef B_TRANSLATION_CONTEXT
1251 	#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Menus"
1252 
1253 
1254 	BMenu* fileMenu = new BMenu(B_TRANSLATE("File"));
1255 	BMenu* editMenu = new BMenu(B_TRANSLATE("Edit"));
1256 	BMenu* settingsMenu = new BMenu(B_TRANSLATE("Settings"));
1257 	fPathMenu = new BMenu(B_TRANSLATE("Path"));
1258 	fStyleMenu = new BMenu(B_TRANSLATE("Style"));
1259 	fShapeMenu = new BMenu(B_TRANSLATE("Shape"));
1260 	fTransformerMenu = new BMenu(B_TRANSLATE("Transformer"));
1261 	fPropertyMenu = new BMenu(B_TRANSLATE("Properties"));
1262 	fSwatchMenu = new BMenu(B_TRANSLATE("Swatches"));
1263 
1264 	menuBar->AddItem(fileMenu);
1265 	menuBar->AddItem(editMenu);
1266 	menuBar->AddItem(settingsMenu);
1267 
1268 
1269 	// File
1270 	#undef B_TRANSLATION_CONTEXT
1271 	#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Menu-File"
1272 
1273 
1274 	BMenuItem* item = new BMenuItem(B_TRANSLATE("New"),
1275 		new BMessage(MSG_NEW), 'N');
1276 	fileMenu->AddItem(item);
1277 	item->SetTarget(be_app);
1278 	item = new BMenuItem(B_TRANSLATE("Open" B_UTF8_ELLIPSIS),
1279 		new BMessage(MSG_OPEN), 'O');
1280 	fileMenu->AddItem(item);
1281 	BMessage* appendMessage = new BMessage(MSG_APPEND);
1282 	appendMessage->AddPointer("window", this);
1283 	item = new BMenuItem(B_TRANSLATE("Append" B_UTF8_ELLIPSIS),
1284 		appendMessage, 'O', B_SHIFT_KEY);
1285 	fileMenu->AddItem(item);
1286 	item->SetTarget(be_app);
1287 	fileMenu->AddSeparatorItem();
1288 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Save"),
1289 		new BMessage(MSG_SAVE), 'S'));
1290 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
1291 		new BMessage(MSG_SAVE_AS), 'S', B_SHIFT_KEY));
1292 	fileMenu->AddSeparatorItem();
1293 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Export"),
1294 		new BMessage(MSG_EXPORT), 'P'));
1295 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Export as" B_UTF8_ELLIPSIS),
1296 		new BMessage(MSG_EXPORT_AS), 'P', B_SHIFT_KEY));
1297 	fileMenu->AddSeparatorItem();
1298 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
1299 		new BMessage(B_QUIT_REQUESTED), 'W'));
1300 	item = new BMenuItem(B_TRANSLATE("Quit"),
1301 		new BMessage(B_QUIT_REQUESTED), 'Q');
1302 	fileMenu->AddItem(item);
1303 	item->SetTarget(be_app);
1304 
1305 	// Edit
1306 	#undef B_TRANSLATION_CONTEXT
1307 	#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Menu-Edit"
1308 
1309 
1310 	fUndoMI = new BMenuItem(B_TRANSLATE("<nothing to undo>"),
1311 		new BMessage(MSG_UNDO), 'Z');
1312 	fRedoMI = new BMenuItem(B_TRANSLATE("<nothing to redo>"),
1313 		new BMessage(MSG_REDO), 'Z', B_SHIFT_KEY);
1314 
1315 	fUndoMI->SetEnabled(false);
1316 	fRedoMI->SetEnabled(false);
1317 
1318 	editMenu->AddItem(fUndoMI);
1319 	editMenu->AddItem(fRedoMI);
1320 
1321 
1322 	// Settings
1323 	#undef B_TRANSLATION_CONTEXT
1324 	#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Menu-Settings"
1325 
1326 
1327 	BMenu* filterModeMenu = new BMenu(B_TRANSLATE("Snap to grid"));
1328 	BMessage* message = new BMessage(MSG_MOUSE_FILTER_MODE);
1329 	message->AddInt32("mode", SNAPPING_OFF);
1330 	fMouseFilterOffMI = new BMenuItem(B_TRANSLATE("Off"), message, '4');
1331 	filterModeMenu->AddItem(fMouseFilterOffMI);
1332 
1333 	message = new BMessage(MSG_MOUSE_FILTER_MODE);
1334 	message->AddInt32("mode", SNAPPING_64);
1335 	fMouseFilter64MI = new BMenuItem(B_TRANSLATE_COMMENT("64 × 64",
1336 		"The '×' is the Unicode multiplication sign U+00D7"), message, '3');
1337 	filterModeMenu->AddItem(fMouseFilter64MI);
1338 
1339 	message = new BMessage(MSG_MOUSE_FILTER_MODE);
1340 	message->AddInt32("mode", SNAPPING_32);
1341 	fMouseFilter32MI = new BMenuItem(B_TRANSLATE_COMMENT("32 × 32",
1342 		"The '×' is the Unicode multiplication sign U+00D7"), message, '2');
1343 	filterModeMenu->AddItem(fMouseFilter32MI);
1344 
1345 	message = new BMessage(MSG_MOUSE_FILTER_MODE);
1346 	message->AddInt32("mode", SNAPPING_16);
1347 	fMouseFilter16MI = new BMenuItem(B_TRANSLATE_COMMENT("16 × 16",
1348 		"The '×' is the Unicode multiplication sign U+00D7"), message, '1');
1349 	filterModeMenu->AddItem(fMouseFilter16MI);
1350 
1351 	filterModeMenu->SetRadioMode(true);
1352 
1353 	settingsMenu->AddItem(filterModeMenu);
1354 
1355 	return menuBar;
1356 }
1357 
1358 
1359 void
1360 MainWindow::_ImproveScrollBarLayout(BView* target)
1361 {
1362 	// NOTE: The BListViews for which this function is used
1363 	// are directly below a BMenuBar. If the BScrollBar and
1364 	// the BMenuBar share bottom/top border respectively, the
1365 	// GUI looks a little more polished. This trick can be
1366 	// removed if/when the BScrollViews are embedded in a
1367 	// surounding border like in WonderBrush.
1368 
1369 	if (BScrollBar* scrollBar = target->ScrollBar(B_VERTICAL)) {
1370 		scrollBar->MoveBy(0, -1);
1371 		scrollBar->ResizeBy(0, 1);
1372 	}
1373 }
1374 
1375 
1376 // #pragma mark -
1377 
1378 
1379 void
1380 MainWindow::_WorkspaceEntered()
1381 {
1382 	BScreen screen(this);
1383 	fIconPreview32Desktop->SetIconBGColor(screen.DesktopColor());
1384 	fIconPreview64->SetIconBGColor(screen.DesktopColor());
1385 }
1386 
1387 
1388 // #pragma mark -
1389 
1390 
1391 bool
1392 MainWindow::_CheckSaveIcon(const BMessage* currentMessage)
1393 {
1394 	if (fDocument->IsEmpty() || fDocument->CommandStack()->IsSaved())
1395 		return true;
1396 
1397 	// Make sure the user sees us.
1398 	Activate();
1399 
1400 	BAlert* alert = new BAlert("save",
1401 		B_TRANSLATE("Save changes to current icon before closing?"),
1402 			B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"),
1403 			B_TRANSLATE("Save"), B_WIDTH_AS_USUAL,	B_OFFSET_SPACING,
1404 			B_WARNING_ALERT);
1405 	alert->SetShortcut(0, B_ESCAPE);
1406 	alert->SetShortcut(1, 'd');
1407 	alert->SetShortcut(2, 's');
1408 	int32 choice = alert->Go();
1409 	switch (choice) {
1410 		case 0:
1411 			// cancel
1412 			return false;
1413 		case 1:
1414 			// don't save
1415 			return true;
1416 		case 2:
1417 		default:
1418 			// cancel (save first) but pick up what we were doing before
1419 			PostMessage(MSG_SAVE);
1420 			if (currentMessage != NULL) {
1421 				delete fMessageAfterSave;
1422 				fMessageAfterSave = new BMessage(*currentMessage);
1423 			}
1424 			return false;
1425 	}
1426 }
1427 
1428 
1429 void
1430 MainWindow::_PickUpActionBeforeSave()
1431 {
1432 	if (fDocument->WriteLock()) {
1433 		fDocument->CommandStack()->Save();
1434 		fDocument->WriteUnlock();
1435 	}
1436 
1437 	if (fMessageAfterSave == NULL)
1438 		return;
1439 
1440 	PostMessage(fMessageAfterSave);
1441 	delete fMessageAfterSave;
1442 	fMessageAfterSave = NULL;
1443 }
1444 
1445 
1446 // #pragma mark -
1447 
1448 
1449 void
1450 MainWindow::_MakeIconEmpty()
1451 {
1452 	if (!_CheckSaveIcon(CurrentMessage()))
1453 		return;
1454 
1455 	AutoWriteLocker locker(fDocument);
1456 
1457 	MakeEmpty();
1458 	fDocument->MakeEmpty();
1459 
1460 	locker.Unlock();
1461 }
1462 
1463 
1464 DocumentSaver*
1465 MainWindow::_CreateSaver(const entry_ref& ref, uint32 exportMode)
1466 {
1467 	DocumentSaver* saver;
1468 
1469 	switch (exportMode) {
1470 		case EXPORT_MODE_FLAT_ICON:
1471 			saver = new SimpleFileSaver(new FlatIconExporter(), ref);
1472 			break;
1473 
1474 		case EXPORT_MODE_ICON_ATTR:
1475 		case EXPORT_MODE_ICON_MIME_ATTR: {
1476 			const char* attrName
1477 				= exportMode == EXPORT_MODE_ICON_ATTR ?
1478 					kVectorAttrNodeName : kVectorAttrMimeName;
1479 			saver = new AttributeSaver(ref, attrName);
1480 			break;
1481 		}
1482 
1483 		case EXPORT_MODE_ICON_RDEF:
1484 			saver = new SimpleFileSaver(new RDefExporter(), ref);
1485 			break;
1486 		case EXPORT_MODE_ICON_SOURCE:
1487 			saver = new SimpleFileSaver(new SourceExporter(), ref);
1488 			break;
1489 
1490 		case EXPORT_MODE_BITMAP_16:
1491 			saver = new SimpleFileSaver(new BitmapExporter(16), ref);
1492 			break;
1493 		case EXPORT_MODE_BITMAP_32:
1494 			saver = new SimpleFileSaver(new BitmapExporter(32), ref);
1495 			break;
1496 		case EXPORT_MODE_BITMAP_64:
1497 			saver = new SimpleFileSaver(new BitmapExporter(64), ref);
1498 			break;
1499 
1500 		case EXPORT_MODE_BITMAP_SET:
1501 			saver = new BitmapSetSaver(ref);
1502 			break;
1503 
1504 		case EXPORT_MODE_SVG:
1505 			saver = new SimpleFileSaver(new SVGExporter(), ref);
1506 			break;
1507 
1508 		case EXPORT_MODE_MESSAGE:
1509 		default:
1510 			saver = new NativeSaver(ref);
1511 			break;
1512 	}
1513 
1514 	return saver;
1515 }
1516 
1517 
1518 const entry_ref*
1519 MainWindow::_FileRef(bool preferExporter) const
1520 {
1521 	FileSaver* saver1;
1522 	FileSaver* saver2;
1523 	if (preferExporter) {
1524 		saver1 = dynamic_cast<FileSaver*>(fDocument->ExportSaver());
1525 		saver2 = dynamic_cast<FileSaver*>(fDocument->NativeSaver());
1526 	} else {
1527 		saver1 = dynamic_cast<FileSaver*>(fDocument->NativeSaver());
1528 		saver2 = dynamic_cast<FileSaver*>(fDocument->ExportSaver());
1529 	}
1530 	const entry_ref* fileRef = NULL;
1531 	if (saver1 != NULL)
1532 		fileRef = saver1->Ref();
1533 	if ((fileRef == NULL || fileRef->name == NULL || fileRef->name[0] == '\0') && saver2 != NULL)
1534 		fileRef = saver2->Ref();
1535 	return fileRef;
1536 }
1537 
1538 
1539 void
1540 MainWindow::_UpdateWindowTitle()
1541 {
1542 	const entry_ref* fileRef = _FileRef(false);
1543 	const char* fileName = NULL;
1544 	if (fileRef != NULL)
1545 		fileName = fileRef->name;
1546 	if (fileName != NULL)
1547 		SetTitle(fileName);
1548 	else
1549 		SetTitle(B_TRANSLATE_SYSTEM_NAME("Icon-O-Matic"));
1550 }
1551