xref: /haiku/src/apps/icon-o-matic/MainWindow.cpp (revision e680a439bf54eb3f561c3fc8cefd299587e69368)
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 		ssize_t 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 		case B_CANCEL:
348 			// FilePanel was canceled, do not execute the fMessageAfterSave
349 			// next time a file panel is used, in case it was set!
350 			delete fMessageAfterSave;
351 			fMessageAfterSave = NULL;
352 			break;
353 
354 		case MSG_UNDO:
355 			fDocument->CommandStack()->Undo();
356 			break;
357 		case MSG_REDO:
358 			fDocument->CommandStack()->Redo();
359 			break;
360 		case MSG_UNDO_STACK_CHANGED:
361 		{
362 			// relable Undo item and update enabled status
363 			BString label(B_TRANSLATE("Undo"));
364 			fUndoMI->SetEnabled(fDocument->CommandStack()->GetUndoName(label));
365 			if (fUndoMI->IsEnabled())
366 				fUndoMI->SetLabel(label.String());
367 			else {
368 				fUndoMI->SetLabel(B_TRANSLATE_CONTEXT("<nothing to undo>",
369 					"Icon-O-Matic-Menu-Edit"));
370 			}
371 
372 			// relable Redo item and update enabled status
373 			label.SetTo(B_TRANSLATE("Redo"));
374 			fRedoMI->SetEnabled(fDocument->CommandStack()->GetRedoName(label));
375 			if (fRedoMI->IsEnabled())
376 				fRedoMI->SetLabel(label.String());
377 			else {
378 				fRedoMI->SetLabel(B_TRANSLATE_CONTEXT("<nothing to redo>",
379 					"Icon-O-Matic-Menu-Edit"));
380 			}
381 			break;
382 		}
383 
384 		case MSG_MOUSE_FILTER_MODE:
385 		{
386 			uint32 mode;
387 			if (message->FindInt32("mode", (int32*)&mode) == B_OK)
388 				fCanvasView->SetMouseFilterMode(mode);
389 			break;
390 		}
391 
392 		case MSG_ADD_SHAPE: {
393 			AddStylesCommand* styleCommand = NULL;
394 			Style* style = NULL;
395 			if (message->HasBool("style")) {
396 				new_style(fCurrentColor->Color(),
397 					fDocument->Icon()->Styles(), &style, &styleCommand);
398 			}
399 
400 			AddPathsCommand* pathCommand = NULL;
401 			VectorPath* path = NULL;
402 			if (message->HasBool("path")) {
403 				new_path(fDocument->Icon()->Paths(), &path, &pathCommand);
404 			}
405 
406 			if (!style) {
407 				// use current or first style
408 				int32 currentStyle = fStyleListView->CurrentSelection(0);
409 				style = fDocument->Icon()->Styles()->StyleAt(currentStyle);
410 				if (!style)
411 					style = fDocument->Icon()->Styles()->StyleAt(0);
412 			}
413 
414 			Shape* shape = new (nothrow) Shape(style);
415 			Shape* shapes[1];
416 			shapes[0] = shape;
417 			AddShapesCommand* shapeCommand = new (nothrow) AddShapesCommand(
418 				fDocument->Icon()->Shapes(), shapes, 1,
419 				fDocument->Icon()->Shapes()->CountShapes(),
420 				fDocument->Selection());
421 
422 			if (path && shape)
423 				shape->Paths()->AddPath(path);
424 
425 			::Command* command = NULL;
426 			if (styleCommand || pathCommand) {
427 				if (styleCommand && pathCommand) {
428 					Command** commands = new Command*[3];
429 					commands[0] = styleCommand;
430 					commands[1] = pathCommand;
431 					commands[2] = shapeCommand;
432 					command = new CompoundCommand(commands, 3,
433 						B_TRANSLATE_CONTEXT("Add shape with path & style",
434 							"Icon-O-Matic-Menu-Shape"),
435 						0);
436 				} else if (styleCommand) {
437 					Command** commands = new Command*[2];
438 					commands[0] = styleCommand;
439 					commands[1] = shapeCommand;
440 					command = new CompoundCommand(commands, 2,
441 						B_TRANSLATE_CONTEXT("Add shape with style",
442 							"Icon-O-Matic-Menu-Shape"),
443 						0);
444 				} else {
445 					Command** commands = new Command*[2];
446 					commands[0] = pathCommand;
447 					commands[1] = shapeCommand;
448 					command = new CompoundCommand(commands, 2,
449 						B_TRANSLATE_CONTEXT("Add shape with path",
450 							"Icon-O-Matic-Menu-Shape"),
451 						0);
452 				}
453 			} else {
454 				command = shapeCommand;
455 			}
456 			fDocument->CommandStack()->Perform(command);
457 			break;
458 		}
459 
460 // TODO: listen to selection in CanvasView to add a manipulator
461 case MSG_PATH_SELECTED: {
462 	VectorPath* path;
463 	if (message->FindPointer("path", (void**)&path) < B_OK)
464 		path = NULL;
465 
466 	fPathListView->SetCurrentShape(NULL);
467 	fStyleListView->SetCurrentShape(NULL);
468 	fTransformerListView->SetShape(NULL);
469 
470 	fState->DeleteManipulators();
471 	if (fDocument->Icon()->Paths()->HasPath(path)) {
472 		PathManipulator* pathManipulator = new (nothrow) PathManipulator(path);
473 		fState->AddManipulator(pathManipulator);
474 	}
475 	break;
476 }
477 case MSG_STYLE_SELECTED:
478 case MSG_STYLE_TYPE_CHANGED: {
479 	Style* style;
480 	if (message->FindPointer("style", (void**)&style) < B_OK)
481 		style = NULL;
482 	if (!fDocument->Icon()->Styles()->HasStyle(style))
483 		style = NULL;
484 
485 	fStyleView->SetStyle(style);
486 	fPathListView->SetCurrentShape(NULL);
487 	fStyleListView->SetCurrentShape(NULL);
488 	fTransformerListView->SetShape(NULL);
489 
490 	fState->DeleteManipulators();
491 	Gradient* gradient = style ? style->Gradient() : NULL;
492 	if (gradient != NULL) {
493 		TransformGradientBox* transformBox
494 			= new (nothrow) TransformGradientBox(fCanvasView, gradient, NULL);
495 		fState->AddManipulator(transformBox);
496 	}
497 	break;
498 }
499 case MSG_SHAPE_SELECTED: {
500 	Shape* shape;
501 	if (message->FindPointer("shape", (void**)&shape) < B_OK)
502 		shape = NULL;
503 	if (!fIcon || !fIcon->Shapes()->HasShape(shape))
504 		shape = NULL;
505 
506 	fPathListView->SetCurrentShape(shape);
507 	fStyleListView->SetCurrentShape(shape);
508 	fTransformerListView->SetShape(shape);
509 
510 	BList selectedShapes;
511 	ShapeContainer* shapes = fDocument->Icon()->Shapes();
512 	int32 count = shapes->CountShapes();
513 	for (int32 i = 0; i < count; i++) {
514 		shape = shapes->ShapeAtFast(i);
515 		if (shape->IsSelected()) {
516 			selectedShapes.AddItem((void*)shape);
517 		}
518 	}
519 
520 	fState->DeleteManipulators();
521 	if (selectedShapes.CountItems() > 0) {
522 		TransformShapesBox* transformBox = new (nothrow) TransformShapesBox(
523 			fCanvasView,
524 			(const Shape**)selectedShapes.Items(),
525 			selectedShapes.CountItems());
526 		fState->AddManipulator(transformBox);
527 	}
528 	break;
529 }
530 		case MSG_RENAME_OBJECT:
531 			fPropertyListView->FocusNameProperty();
532 			break;
533 
534 		default:
535 			BWindow::MessageReceived(message);
536 	}
537 
538 	if (requiresWriteLock)
539 		fDocument->WriteUnlock();
540 }
541 
542 
543 bool
544 MainWindow::QuitRequested()
545 {
546 	if (!_CheckSaveIcon(CurrentMessage()))
547 		return false;
548 
549 	BMessage message(MSG_WINDOW_CLOSED);
550 
551 	BMessage settings;
552 	StoreSettings(&settings);
553 	message.AddMessage("settings", &settings);
554 	message.AddRect("window frame", Frame());
555 
556 	be_app->PostMessage(&message);
557 
558 	return true;
559 }
560 
561 
562 void
563 MainWindow::WorkspaceActivated(int32 workspace, bool active)
564 {
565 	BWindow::WorkspaceActivated(workspace, active);
566 
567 	// NOTE: hack to workaround buggy BScreen::DesktopColor() on R5
568 
569 	uint32 workspaces = Workspaces();
570 	if (!active || ((1 << workspace) & workspaces) == 0)
571 		return;
572 
573 	WorkspacesChanged(workspaces, workspaces);
574 }
575 
576 
577 void
578 MainWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces)
579 {
580 	if (oldWorkspaces != newWorkspaces)
581 		BWindow::WorkspacesChanged(oldWorkspaces, newWorkspaces);
582 
583 	BScreen screen(this);
584 
585 	// Unfortunately, this is buggy on R5: screen.DesktopColor()
586 	// as well as ui_color(B_DESKTOP_COLOR) return the color
587 	// of the *active* screen, not the one on which this window
588 	// is. So this trick only works when you drag this window
589 	// from another workspace onto the current workspace, not
590 	// when you drag the window from the current workspace onto
591 	// another workspace and then switch to the other workspace.
592 
593 	fIconPreview32Desktop->SetIconBGColor(screen.DesktopColor());
594 	fIconPreview64->SetIconBGColor(screen.DesktopColor());
595 }
596 
597 
598 // #pragma mark -
599 
600 
601 void
602 MainWindow::ObjectChanged(const Observable* object)
603 {
604 	if (!fDocument || !fDocument->ReadLock())
605 		return;
606 
607 	if (object == fDocument->CommandStack())
608 		PostMessage(MSG_UNDO_STACK_CHANGED);
609 
610 	fDocument->ReadUnlock();
611 }
612 
613 
614 // #pragma mark -
615 
616 
617 void
618 MainWindow::MakeEmpty()
619 {
620 	fPathListView->SetCurrentShape(NULL);
621 	fStyleListView->SetCurrentShape(NULL);
622 	fStyleView->SetStyle(NULL);
623 
624 	fTransformerListView->SetShape(NULL);
625 
626 	fState->DeleteManipulators();
627 }
628 
629 
630 void
631 MainWindow::Open(const entry_ref& ref, bool append)
632 {
633 	BFile file(&ref, B_READ_ONLY);
634 	if (file.InitCheck() < B_OK)
635 		return;
636 
637 	Icon* icon;
638 	if (append)
639 		icon = new (nothrow) Icon(*fDocument->Icon());
640 	else
641 		icon = new (nothrow) Icon();
642 
643 	if (icon == NULL) {
644 		// TODO: Report error to user.
645 		return;
646 	}
647 
648 	enum {
649 		REF_NONE = 0,
650 		REF_MESSAGE,
651 		REF_FLAT,
652 		REF_FLAT_ATTR,
653 		REF_SVG
654 	};
655 	uint32 refMode = REF_NONE;
656 
657 	// try different file types
658 	FlatIconImporter flatImporter;
659 	status_t ret = flatImporter.Import(icon, &file);
660 	if (ret >= B_OK) {
661 		refMode = REF_FLAT;
662 	} else {
663 		file.Seek(0, SEEK_SET);
664 		MessageImporter msgImporter;
665 		ret = msgImporter.Import(icon, &file);
666 		if (ret >= B_OK) {
667 			refMode = REF_MESSAGE;
668 		} else {
669 			file.Seek(0, SEEK_SET);
670 			SVGImporter svgImporter;
671 			ret = svgImporter.Import(icon, &ref);
672 			if (ret >= B_OK) {
673 				refMode = REF_SVG;
674 			} else {
675 				// fall back to flat icon format but use the icon attribute
676 				ret = B_OK;
677 				attr_info attrInfo;
678 				if (file.GetAttrInfo(kVectorAttrNodeName, &attrInfo) == B_OK) {
679 					if (attrInfo.type != B_VECTOR_ICON_TYPE)
680 						ret = B_ERROR;
681 					// If the attribute is there, we must succeed in reading
682 					// an icon! Otherwise we may overwrite an existing icon
683 					// when the user saves.
684 					uint8* buffer = NULL;
685 					if (ret == B_OK) {
686 						buffer = new(nothrow) uint8[attrInfo.size];
687 						if (buffer == NULL)
688 							ret = B_NO_MEMORY;
689 					}
690 					if (ret == B_OK) {
691 						ssize_t bytesRead = file.ReadAttr(kVectorAttrNodeName,
692 							B_VECTOR_ICON_TYPE, 0, buffer, attrInfo.size);
693 						if (bytesRead != (ssize_t)attrInfo.size) {
694 							ret = bytesRead < 0 ? (status_t)bytesRead
695 								: B_IO_ERROR;
696 						}
697 					}
698 					if (ret == B_OK) {
699 						ret = flatImporter.Import(icon, buffer, attrInfo.size);
700 						if (ret == B_OK)
701 							refMode = REF_FLAT_ATTR;
702 					}
703 
704 					delete[] buffer;
705 				} else {
706 					// If there is no icon attribute, simply fall back
707 					// to creating an icon for this file. TODO: We may or may
708 					// not want to display an alert asking the user if that is
709 					// what he wants to do.
710 					refMode = REF_FLAT_ATTR;
711 				}
712 			}
713 		}
714 	}
715 
716 	if (ret < B_OK) {
717 		// inform user of failure at this point
718 		BString helper(B_TRANSLATE("Opening the document failed!"));
719 		helper << "\n\n" << B_TRANSLATE("Error: ") << strerror(ret);
720 		BAlert* alert = new BAlert(
721 			B_TRANSLATE_CONTEXT("bad news", "Title of error alert"),
722 			helper.String(),
723 			B_TRANSLATE_CONTEXT("Bummer",
724 				"Cancel button - error alert"),
725 			NULL, NULL);
726 		// launch alert asynchronously
727 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
728 		alert->Go(NULL);
729 
730 		delete icon;
731 		return;
732 	}
733 
734 	AutoWriteLocker locker(fDocument);
735 
736 	// incorporate the loaded icon into the document
737 	// (either replace it or append to it)
738 	fDocument->MakeEmpty(!append);
739 		// if append, the document savers are preserved
740 	fDocument->SetIcon(icon);
741 	if (!append) {
742 		// document got replaced, but we have at
743 		// least one ref already
744 		switch (refMode) {
745 			case REF_MESSAGE:
746 				fDocument->SetNativeSaver(new NativeSaver(ref));
747 				break;
748 			case REF_FLAT:
749 				fDocument->SetExportSaver(
750 					new SimpleFileSaver(new FlatIconExporter(), ref));
751 				break;
752 			case REF_FLAT_ATTR:
753 				fDocument->SetNativeSaver(
754 					new AttributeSaver(ref, kVectorAttrNodeName));
755 				break;
756 			case REF_SVG:
757 				fDocument->SetExportSaver(
758 					new SimpleFileSaver(new SVGExporter(), ref));
759 				break;
760 		}
761 	}
762 
763 	locker.Unlock();
764 
765 	SetIcon(icon);
766 
767 	_UpdateWindowTitle();
768 }
769 
770 
771 void
772 MainWindow::Open(const BMessenger& externalObserver, const uint8* data,
773 	size_t size)
774 {
775 	if (!_CheckSaveIcon(CurrentMessage()))
776 		return;
777 
778 	if (!externalObserver.IsValid())
779 		return;
780 
781 	Icon* icon = new (nothrow) Icon();
782 	if (!icon)
783 		return;
784 
785 	if (data && size > 0) {
786 		// try to open the icon from the provided data
787 		FlatIconImporter flatImporter;
788 		status_t ret = flatImporter.Import(icon, const_cast<uint8*>(data),
789 			size);
790 			// NOTE: the const_cast is a bit ugly, but no harm is done
791 			// the reason is that the LittleEndianBuffer knows read and write
792 			// mode, in this case it is used read-only, and it does not assume
793 			// ownership of the buffer
794 
795 		if (ret < B_OK) {
796 			// inform user of failure at this point
797 			BString helper(B_TRANSLATE("Opening the icon failed!"));
798 			helper << "\n\n" << B_TRANSLATE("Error: ") << strerror(ret);
799 			BAlert* alert = new BAlert(
800 				B_TRANSLATE_CONTEXT("bad news", "Title of error alert"),
801 				helper.String(),
802 				B_TRANSLATE_CONTEXT("Bummer",
803 					"Cancel button - error alert"),
804 				NULL, NULL);
805 			// launch alert asynchronously
806 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
807 			alert->Go(NULL);
808 
809 			delete icon;
810 			return;
811 		}
812 	}
813 
814 	AutoWriteLocker locker(fDocument);
815 
816 	SetIcon(NULL);
817 
818 	// incorporate the loaded icon into the document
819 	// (either replace it or append to it)
820 	fDocument->MakeEmpty();
821 	fDocument->SetIcon(icon);
822 
823 	fDocument->SetNativeSaver(new MessengerSaver(externalObserver));
824 
825 	locker.Unlock();
826 
827 	SetIcon(icon);
828 }
829 
830 
831 void
832 MainWindow::SetIcon(Icon* icon)
833 {
834 	if (fIcon == icon)
835 		return;
836 
837 	Icon* oldIcon = fIcon;
838 
839 	fIcon = icon;
840 
841 	if (fIcon != NULL)
842 		fIcon->AcquireReference();
843 	else
844 		MakeEmpty();
845 
846 	fCanvasView->SetIcon(fIcon);
847 
848 	fPathListView->SetPathContainer(fIcon != NULL ? fIcon->Paths() : NULL);
849 	fPathListView->SetShapeContainer(fIcon != NULL ? fIcon->Shapes() : NULL);
850 
851 	fStyleListView->SetStyleContainer(fIcon != NULL ? fIcon->Styles() : NULL);
852 	fStyleListView->SetShapeContainer(fIcon != NULL ? fIcon->Shapes() : NULL);
853 
854 	fShapeListView->SetShapeContainer(fIcon != NULL ? fIcon->Shapes() : NULL);
855 	fShapeListView->SetStyleContainer(fIcon != NULL ? fIcon->Styles() : NULL);
856 	fShapeListView->SetPathContainer(fIcon != NULL ? fIcon->Paths() : NULL);
857 
858 	// icon previews
859 	fIconPreview16Folder->SetIcon(fIcon);
860 	fIconPreview16Menu->SetIcon(fIcon);
861 	fIconPreview32Folder->SetIcon(fIcon);
862 	fIconPreview32Desktop->SetIcon(fIcon);
863 //	fIconPreview48->SetIcon(fIcon);
864 	fIconPreview64->SetIcon(fIcon);
865 
866 	// keep this last
867 	if (oldIcon != NULL)
868 		oldIcon->ReleaseReference();
869 }
870 
871 
872 // #pragma mark -
873 
874 
875 void
876 MainWindow::StoreSettings(BMessage* archive)
877 {
878 	if (archive->ReplaceUInt32("mouse filter mode",
879 			fCanvasView->MouseFilterMode()) != B_OK) {
880 		archive->AddUInt32("mouse filter mode",
881 			fCanvasView->MouseFilterMode());
882 	}
883 }
884 
885 
886 void
887 MainWindow::RestoreSettings(const BMessage* archive)
888 {
889 	uint32 mouseFilterMode;
890 	if (archive->FindUInt32("mouse filter mode", &mouseFilterMode) == B_OK) {
891 		fCanvasView->SetMouseFilterMode(mouseFilterMode);
892 		fMouseFilterOffMI->SetMarked(mouseFilterMode == SNAPPING_OFF);
893 		fMouseFilter64MI->SetMarked(mouseFilterMode == SNAPPING_64);
894 		fMouseFilter32MI->SetMarked(mouseFilterMode == SNAPPING_32);
895 		fMouseFilter16MI->SetMarked(mouseFilterMode == SNAPPING_16);
896 	}
897 }
898 
899 
900 // #pragma mark -
901 
902 
903 void
904 MainWindow::_Init()
905 {
906 	// create the GUI
907 	_CreateGUI();
908 
909 	// fix up scrollbar layout in listviews
910 	_ImproveScrollBarLayout(fPathListView);
911 	_ImproveScrollBarLayout(fStyleListView);
912 	_ImproveScrollBarLayout(fShapeListView);
913 	_ImproveScrollBarLayout(fTransformerListView);
914 
915 	// TODO: move this to CanvasView?
916 	fState = new MultipleManipulatorState(fCanvasView);
917 	fCanvasView->SetState(fState);
918 
919 	fCanvasView->SetCatchAllEvents(true);
920 	fCanvasView->SetCommandStack(fDocument->CommandStack());
921 	fCanvasView->SetMouseFilterMode(SNAPPING_64);
922 	fMouseFilter64MI->SetMarked(true);
923 //	fCanvasView->SetSelection(fDocument->Selection());
924 
925 	fPathListView->SetMenu(fPathMenu);
926 	fPathListView->SetCommandStack(fDocument->CommandStack());
927 	fPathListView->SetSelection(fDocument->Selection());
928 
929 	fStyleListView->SetMenu(fStyleMenu);
930 	fStyleListView->SetCommandStack(fDocument->CommandStack());
931 	fStyleListView->SetSelection(fDocument->Selection());
932 	fStyleListView->SetCurrentColor(fCurrentColor);
933 
934 	fStyleView->SetCommandStack(fDocument->CommandStack());
935 	fStyleView->SetCurrentColor(fCurrentColor);
936 
937 	fShapeListView->SetMenu(fShapeMenu);
938 	fShapeListView->SetCommandStack(fDocument->CommandStack());
939 	fShapeListView->SetSelection(fDocument->Selection());
940 
941 	fTransformerListView->SetMenu(fTransformerMenu);
942 	fTransformerListView->SetCommandStack(fDocument->CommandStack());
943 	fTransformerListView->SetSelection(fDocument->Selection());
944 
945 	fPropertyListView->SetCommandStack(fDocument->CommandStack());
946 	fPropertyListView->SetSelection(fDocument->Selection());
947 	fPropertyListView->SetMenu(fPropertyMenu);
948 
949 	fDocument->CommandStack()->AddObserver(this);
950 
951 	fSwatchGroup->SetCurrentColor(fCurrentColor);
952 
953 	SetIcon(fDocument->Icon());
954 
955 	AddShortcut('Y', 0, new BMessage(MSG_UNDO));
956 	AddShortcut('Y', B_SHIFT_KEY, new BMessage(MSG_REDO));
957 	AddShortcut('E', 0, new BMessage(MSG_RENAME_OBJECT));
958 }
959 
960 
961 void
962 MainWindow::_CreateGUI()
963 {
964 	SetLayout(new BGroupLayout(B_HORIZONTAL));
965 
966 	BGridLayout* layout = new BGridLayout();
967 	layout->SetSpacing(0, 0);
968 	BView* rootView = new BView("root view", 0, layout);
969 	AddChild(rootView);
970 	rootView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
971 
972 	BGroupView* leftTopView = new BGroupView(B_VERTICAL, 0);
973 	layout->AddView(leftTopView, 0, 0);
974 
975 	// views along the left side
976 	leftTopView->AddChild(_CreateMenuBar());
977 
978 	float splitWidth = 13 * be_plain_font->Size();
979 	BSize minSize = leftTopView->MinSize();
980 	splitWidth = std::max(splitWidth, minSize.width);
981 	leftTopView->SetExplicitMaxSize(BSize(splitWidth, B_SIZE_UNSET));
982 	leftTopView->SetExplicitMinSize(BSize(splitWidth, B_SIZE_UNSET));
983 
984 	BGroupView* iconPreviews = new BGroupView(B_HORIZONTAL);
985 	iconPreviews->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
986 	iconPreviews->GroupLayout()->SetSpacing(5);
987 
988 	// icon previews
989 	fIconPreview16Folder = new IconView(BRect(0, 0, 15, 15),
990 		"icon preview 16 folder");
991 	fIconPreview16Menu = new IconView(BRect(0, 0, 15, 15),
992 		"icon preview 16 menu");
993 	fIconPreview16Menu->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR));
994 
995 	fIconPreview32Folder = new IconView(BRect(0, 0, 31, 31),
996 		"icon preview 32 folder");
997 	fIconPreview32Desktop = new IconView(BRect(0, 0, 31, 31),
998 		"icon preview 32 desktop");
999 	fIconPreview32Desktop->SetLowColor(ui_color(B_DESKTOP_COLOR));
1000 
1001 	fIconPreview64 = new IconView(BRect(0, 0, 63, 63), "icon preview 64");
1002 	fIconPreview64->SetLowColor(ui_color(B_DESKTOP_COLOR));
1003 
1004 
1005 	BGroupView* smallPreviews = new BGroupView(B_VERTICAL);
1006 	smallPreviews->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1007 	smallPreviews->GroupLayout()->SetSpacing(5);
1008 
1009 	smallPreviews->AddChild(fIconPreview16Folder);
1010 	smallPreviews->AddChild(fIconPreview16Menu);
1011 
1012 	BGroupView* mediumPreviews = new BGroupView(B_VERTICAL);
1013 	mediumPreviews->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1014 	mediumPreviews->GroupLayout()->SetSpacing(5);
1015 
1016 	mediumPreviews->AddChild(fIconPreview32Folder);
1017 	mediumPreviews->AddChild(fIconPreview32Desktop);
1018 
1019 //	iconPreviews->AddChild(fIconPreview48);
1020 
1021 	iconPreviews->AddChild(smallPreviews);
1022 	iconPreviews->AddChild(mediumPreviews);
1023 	iconPreviews->AddChild(fIconPreview64);
1024 	iconPreviews->SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED));
1025 
1026 	leftTopView->AddChild(iconPreviews);
1027 
1028 
1029 	BGroupView* leftSideView = new BGroupView(B_VERTICAL, 0);
1030 	layout->AddView(leftSideView, 0, 1);
1031 	leftSideView->SetExplicitMaxSize(BSize(splitWidth, B_SIZE_UNSET));
1032 
1033 	// path menu and list view
1034 	BMenuBar* menuBar = new BMenuBar("path menu bar");
1035 	menuBar->AddItem(fPathMenu);
1036 	leftSideView->AddChild(menuBar);
1037 
1038 	fPathListView = new PathListView(BRect(0, 0, splitWidth, 100),
1039 		"path list view", new BMessage(MSG_PATH_SELECTED), this);
1040 
1041 	BView* scrollView = new BScrollView("path list scroll view",
1042 		fPathListView, B_FOLLOW_NONE, 0, false, true, B_NO_BORDER);
1043 	leftSideView->AddChild(scrollView);
1044 
1045 	// shape list view
1046 	menuBar = new BMenuBar("shape menu bar");
1047 	menuBar->AddItem(fShapeMenu);
1048 	leftSideView->AddChild(menuBar);
1049 
1050 	fShapeListView = new ShapeListView(BRect(0, 0, splitWidth, 100),
1051 		"shape list view", new BMessage(MSG_SHAPE_SELECTED), this);
1052 	scrollView = new BScrollView("shape list scroll view",
1053 		fShapeListView, B_FOLLOW_NONE, 0, false, true, B_NO_BORDER);
1054 	leftSideView->AddChild(scrollView);
1055 
1056 	// transformer list view
1057 	menuBar = new BMenuBar("transformer menu bar");
1058 	menuBar->AddItem(fTransformerMenu);
1059 	leftSideView->AddChild(menuBar);
1060 
1061 	fTransformerListView = new TransformerListView(BRect(0, 0, splitWidth, 100),
1062 		"transformer list view");
1063 	scrollView = new BScrollView("transformer list scroll view",
1064 		fTransformerListView, B_FOLLOW_NONE, 0, false, true, B_NO_BORDER);
1065 	leftSideView->AddChild(scrollView);
1066 
1067 	// property list view
1068 	menuBar = new BMenuBar("property menu bar");
1069 	menuBar->AddItem(fPropertyMenu);
1070 	leftSideView->AddChild(menuBar);
1071 
1072 	fPropertyListView = new IconObjectListView();
1073 
1074 	// scroll view around property list view
1075 	ScrollView* propScrollView = new ScrollView(fPropertyListView,
1076 		SCROLL_VERTICAL, BRect(0, 0, splitWidth, 100), "property scroll view",
1077 		B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, B_PLAIN_BORDER,
1078 		BORDER_RIGHT);
1079 	leftSideView->AddChild(propScrollView);
1080 
1081 	BGroupLayout* topSide = new BGroupLayout(B_HORIZONTAL);
1082 	topSide->SetSpacing(0);
1083 	BView* topSideView = new BView("top side view", 0, topSide);
1084 	layout->AddView(topSideView, 1, 0);
1085 
1086 	// canvas view
1087 	BRect canvasBounds = BRect(0, 0, 200, 200);
1088 	fCanvasView = new CanvasView(canvasBounds);
1089 
1090 	// scroll view around canvas view
1091 	canvasBounds.bottom += B_H_SCROLL_BAR_HEIGHT;
1092 	canvasBounds.right += B_V_SCROLL_BAR_WIDTH;
1093 	ScrollView* canvasScrollView = new ScrollView(fCanvasView, SCROLL_VERTICAL
1094 			| SCROLL_HORIZONTAL | SCROLL_VISIBLE_RECT_IS_CHILD_BOUNDS,
1095 		canvasBounds, "canvas scroll view", B_FOLLOW_NONE,
1096 		B_WILL_DRAW | B_FRAME_EVENTS, B_NO_BORDER);
1097 	layout->AddView(canvasScrollView, 1, 1);
1098 
1099 	// views along the top
1100 
1101 	BGroupLayout* styleGroup = new BGroupLayout(B_VERTICAL, 0);
1102 	BView* styleGroupView = new BView("style group", 0, styleGroup);
1103 	topSide->AddView(styleGroupView);
1104 
1105 	// style list view
1106 	menuBar = new BMenuBar("style menu bar");
1107 	menuBar->AddItem(fStyleMenu);
1108 	styleGroup->AddView(menuBar);
1109 
1110 	fStyleListView = new StyleListView(BRect(0, 0, splitWidth, 100),
1111 		"style list view", new BMessage(MSG_STYLE_SELECTED), this);
1112 	scrollView = new BScrollView("style list scroll view", fStyleListView,
1113 		B_FOLLOW_NONE, 0, false, true, B_NO_BORDER);
1114 	scrollView->SetExplicitMaxSize(BSize(splitWidth, B_SIZE_UNLIMITED));
1115 	styleGroup->AddView(scrollView);
1116 
1117 	// style view
1118 	fStyleView = new StyleView(BRect(0, 0, 200, 100));
1119 	topSide->AddView(fStyleView);
1120 
1121 	// swatch group
1122 	BGroupLayout* swatchGroup = new BGroupLayout(B_VERTICAL);
1123 	swatchGroup->SetSpacing(0);
1124 	BView* swatchGroupView = new BView("swatch group", 0, swatchGroup);
1125 	topSide->AddView(swatchGroupView);
1126 
1127 	menuBar = new BMenuBar("swatches menu bar");
1128 	menuBar->AddItem(fSwatchMenu);
1129 	swatchGroup->AddView(menuBar);
1130 
1131 	fSwatchGroup = new SwatchGroup(BRect(0, 0, 100, 100));
1132 	swatchGroup->AddView(fSwatchGroup);
1133 
1134 	swatchGroupView->SetExplicitMaxSize(swatchGroupView->MinSize());
1135 
1136 	// make sure the top side has fixed height
1137 	topSideView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
1138 		swatchGroupView->MinSize().height));
1139 }
1140 
1141 BMenuBar*
1142 MainWindow::_CreateMenuBar()
1143 {
1144 	BMenuBar* menuBar = new BMenuBar("main menu");
1145 
1146 
1147 	#undef B_TRANSLATION_CONTEXT
1148 	#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Menus"
1149 
1150 
1151 	BMenu* fileMenu = new BMenu(B_TRANSLATE("File"));
1152 	BMenu* editMenu = new BMenu(B_TRANSLATE("Edit"));
1153 	BMenu* settingsMenu = new BMenu(B_TRANSLATE("Settings"));
1154 	fPathMenu = new BMenu(B_TRANSLATE("Path"));
1155 	fStyleMenu = new BMenu(B_TRANSLATE("Style"));
1156 	fShapeMenu = new BMenu(B_TRANSLATE("Shape"));
1157 	fTransformerMenu = new BMenu(B_TRANSLATE("Transformer"));
1158 	fPropertyMenu = new BMenu(B_TRANSLATE("Properties"));
1159 	fSwatchMenu = new BMenu(B_TRANSLATE("Swatches"));
1160 
1161 	menuBar->AddItem(fileMenu);
1162 	menuBar->AddItem(editMenu);
1163 	menuBar->AddItem(settingsMenu);
1164 
1165 
1166 	// File
1167 	#undef B_TRANSLATION_CONTEXT
1168 	#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Menu-File"
1169 
1170 
1171 	BMenuItem* item = new BMenuItem(B_TRANSLATE("New"),
1172 		new BMessage(MSG_NEW), 'N');
1173 	fileMenu->AddItem(item);
1174 	item->SetTarget(be_app);
1175 	item = new BMenuItem(B_TRANSLATE("Open" B_UTF8_ELLIPSIS),
1176 		new BMessage(MSG_OPEN), 'O');
1177 	fileMenu->AddItem(item);
1178 	BMessage* appendMessage = new BMessage(MSG_APPEND);
1179 	appendMessage->AddPointer("window", this);
1180 	item = new BMenuItem(B_TRANSLATE("Append" B_UTF8_ELLIPSIS),
1181 		appendMessage, 'O', B_SHIFT_KEY);
1182 	fileMenu->AddItem(item);
1183 	item->SetTarget(be_app);
1184 	fileMenu->AddSeparatorItem();
1185 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Save"),
1186 		new BMessage(MSG_SAVE), 'S'));
1187 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
1188 		new BMessage(MSG_SAVE_AS), 'S', B_SHIFT_KEY));
1189 	fileMenu->AddSeparatorItem();
1190 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Export"),
1191 		new BMessage(MSG_EXPORT), 'P'));
1192 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Export as" B_UTF8_ELLIPSIS),
1193 		new BMessage(MSG_EXPORT_AS), 'P', B_SHIFT_KEY));
1194 	fileMenu->AddSeparatorItem();
1195 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
1196 		new BMessage(B_QUIT_REQUESTED), 'W'));
1197 	item = new BMenuItem(B_TRANSLATE("Quit"),
1198 		new BMessage(B_QUIT_REQUESTED), 'Q');
1199 	fileMenu->AddItem(item);
1200 	item->SetTarget(be_app);
1201 
1202 	// Edit
1203 	#undef B_TRANSLATION_CONTEXT
1204 	#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Menu-Edit"
1205 
1206 
1207 	fUndoMI = new BMenuItem(B_TRANSLATE("<nothing to undo>"),
1208 		new BMessage(MSG_UNDO), 'Z');
1209 	fRedoMI = new BMenuItem(B_TRANSLATE("<nothing to redo>"),
1210 		new BMessage(MSG_REDO), 'Z', B_SHIFT_KEY);
1211 
1212 	fUndoMI->SetEnabled(false);
1213 	fRedoMI->SetEnabled(false);
1214 
1215 	editMenu->AddItem(fUndoMI);
1216 	editMenu->AddItem(fRedoMI);
1217 
1218 
1219 	// Settings
1220 	#undef B_TRANSLATION_CONTEXT
1221 	#define B_TRANSLATION_CONTEXT "Icon-O-Matic-Menu-Settings"
1222 
1223 
1224 	BMenu* filterModeMenu = new BMenu(B_TRANSLATE("Snap to grid"));
1225 	BMessage* message = new BMessage(MSG_MOUSE_FILTER_MODE);
1226 	message->AddInt32("mode", SNAPPING_OFF);
1227 	fMouseFilterOffMI = new BMenuItem(B_TRANSLATE("Off"), message, '4');
1228 	filterModeMenu->AddItem(fMouseFilterOffMI);
1229 
1230 	message = new BMessage(MSG_MOUSE_FILTER_MODE);
1231 	message->AddInt32("mode", SNAPPING_64);
1232 	fMouseFilter64MI = new BMenuItem("64 x 64", message, '3');
1233 	filterModeMenu->AddItem(fMouseFilter64MI);
1234 
1235 	message = new BMessage(MSG_MOUSE_FILTER_MODE);
1236 	message->AddInt32("mode", SNAPPING_32);
1237 	fMouseFilter32MI = new BMenuItem("32 x 32", message, '2');
1238 	filterModeMenu->AddItem(fMouseFilter32MI);
1239 
1240 	message = new BMessage(MSG_MOUSE_FILTER_MODE);
1241 	message->AddInt32("mode", SNAPPING_16);
1242 	fMouseFilter16MI = new BMenuItem("16 x 16", message, '1');
1243 	filterModeMenu->AddItem(fMouseFilter16MI);
1244 
1245 	filterModeMenu->SetRadioMode(true);
1246 
1247 	settingsMenu->AddItem(filterModeMenu);
1248 
1249 	return menuBar;
1250 }
1251 
1252 
1253 void
1254 MainWindow::_ImproveScrollBarLayout(BView* target)
1255 {
1256 	// NOTE: The BListViews for which this function is used
1257 	// are directly below a BMenuBar. If the BScrollBar and
1258 	// the BMenuBar share bottom/top border respectively, the
1259 	// GUI looks a little more polished. This trick can be
1260 	// removed if/when the BScrollViews are embedded in a
1261 	// surounding border like in WonderBrush.
1262 
1263 	if (BScrollBar* scrollBar = target->ScrollBar(B_VERTICAL)) {
1264 		scrollBar->MoveBy(0, -1);
1265 		scrollBar->ResizeBy(0, 1);
1266 	}
1267 }
1268 
1269 
1270 // #pragma mark -
1271 
1272 
1273 bool
1274 MainWindow::_CheckSaveIcon(const BMessage* currentMessage)
1275 {
1276 	if (fDocument->IsEmpty() || fDocument->CommandStack()->IsSaved())
1277 		return true;
1278 
1279 	// Make sure the user sees us.
1280 	Activate();
1281 
1282 	BAlert* alert = new BAlert("save",
1283 		B_TRANSLATE("Save changes to current icon before closing?"),
1284 			B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"),
1285 			B_TRANSLATE("Save"), B_WIDTH_AS_USUAL,	B_OFFSET_SPACING,
1286 			B_WARNING_ALERT);
1287 	alert->SetShortcut(0, B_ESCAPE);
1288 	alert->SetShortcut(1, 'd');
1289 	alert->SetShortcut(2, 's');
1290 	int32 choice = alert->Go();
1291 	switch (choice) {
1292 		case 0:
1293 			// cancel
1294 			return false;
1295 		case 1:
1296 			// don't save
1297 			return true;
1298 		case 2:
1299 		default:
1300 			// cancel (save first) but pick up what we were doing before
1301 			PostMessage(MSG_SAVE);
1302 			if (currentMessage != NULL) {
1303 				delete fMessageAfterSave;
1304 				fMessageAfterSave = new BMessage(*currentMessage);
1305 			}
1306 			return false;
1307 	}
1308 }
1309 
1310 
1311 void
1312 MainWindow::_PickUpActionBeforeSave()
1313 {
1314 	if (fDocument->WriteLock()) {
1315 		fDocument->CommandStack()->Save();
1316 		fDocument->WriteUnlock();
1317 	}
1318 
1319 	if (fMessageAfterSave == NULL)
1320 		return;
1321 
1322 	PostMessage(fMessageAfterSave);
1323 	delete fMessageAfterSave;
1324 	fMessageAfterSave = NULL;
1325 }
1326 
1327 
1328 // #pragma mark -
1329 
1330 
1331 void
1332 MainWindow::_MakeIconEmpty()
1333 {
1334 	if (!_CheckSaveIcon(CurrentMessage()))
1335 		return;
1336 
1337 	AutoWriteLocker locker(fDocument);
1338 
1339 	MakeEmpty();
1340 	fDocument->MakeEmpty();
1341 
1342 	locker.Unlock();
1343 }
1344 
1345 
1346 DocumentSaver*
1347 MainWindow::_CreateSaver(const entry_ref& ref, uint32 exportMode)
1348 {
1349 	DocumentSaver* saver;
1350 
1351 	switch (exportMode) {
1352 		case EXPORT_MODE_FLAT_ICON:
1353 			saver = new SimpleFileSaver(new FlatIconExporter(), ref);
1354 			break;
1355 
1356 		case EXPORT_MODE_ICON_ATTR:
1357 		case EXPORT_MODE_ICON_MIME_ATTR: {
1358 			const char* attrName
1359 				= exportMode == EXPORT_MODE_ICON_ATTR ?
1360 					kVectorAttrNodeName : kVectorAttrMimeName;
1361 			saver = new AttributeSaver(ref, attrName);
1362 			break;
1363 		}
1364 
1365 		case EXPORT_MODE_ICON_RDEF:
1366 			saver = new SimpleFileSaver(new RDefExporter(), ref);
1367 			break;
1368 		case EXPORT_MODE_ICON_SOURCE:
1369 			saver = new SimpleFileSaver(new SourceExporter(), ref);
1370 			break;
1371 
1372 		case EXPORT_MODE_BITMAP_16:
1373 			saver = new SimpleFileSaver(new BitmapExporter(16), ref);
1374 			break;
1375 		case EXPORT_MODE_BITMAP_32:
1376 			saver = new SimpleFileSaver(new BitmapExporter(32), ref);
1377 			break;
1378 		case EXPORT_MODE_BITMAP_64:
1379 			saver = new SimpleFileSaver(new BitmapExporter(64), ref);
1380 			break;
1381 
1382 		case EXPORT_MODE_BITMAP_SET:
1383 			saver = new BitmapSetSaver(ref);
1384 			break;
1385 
1386 		case EXPORT_MODE_SVG:
1387 			saver = new SimpleFileSaver(new SVGExporter(), ref);
1388 			break;
1389 
1390 		case EXPORT_MODE_MESSAGE:
1391 		default:
1392 			saver = new NativeSaver(ref);
1393 			break;
1394 	}
1395 
1396 	return saver;
1397 }
1398 
1399 
1400 const char*
1401 MainWindow::_FileName(bool preferExporter) const
1402 {
1403 	FileSaver* saver1;
1404 	FileSaver* saver2;
1405 	if (preferExporter) {
1406 		saver1 = dynamic_cast<FileSaver*>(fDocument->ExportSaver());
1407 		saver2 = dynamic_cast<FileSaver*>(fDocument->NativeSaver());
1408 	} else {
1409 		saver1 = dynamic_cast<FileSaver*>(fDocument->NativeSaver());
1410 		saver2 = dynamic_cast<FileSaver*>(fDocument->ExportSaver());
1411 	}
1412 	const char* fileName = NULL;
1413 	if (saver1 != NULL)
1414 		fileName = saver1->Ref()->name;
1415 	if ((fileName == NULL || fileName[0] == '\0') && saver2 != NULL)
1416 		fileName = saver2->Ref()->name;
1417 	return fileName;
1418 }
1419 
1420 
1421 void
1422 MainWindow::_UpdateWindowTitle()
1423 {
1424 	const char* fileName = _FileName(false);
1425 	if (fileName != NULL)
1426 		SetTitle(fileName);
1427 	else
1428 		SetTitle(B_TRANSLATE_SYSTEM_NAME("Icon-O-Matic"));
1429 }
1430 
1431