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