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