xref: /haiku/src/apps/icon-o-matic/MainWindow.cpp (revision 26bda37ca592675f71c0adbcd4794b93aa670424)
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 "ShapeListView.h"
87 #include "StrokeTransformer.h"
88 #include "Style.h"
89 #include "VectorPath.h"
90 
91 #include "StyledTextImporter.h"
92 
93 
94 #undef B_TRANSLATION_CONTEXT
95 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-Main"
96 
97 
98 using std::nothrow;
99 
100 enum {
101 	MSG_UNDO						= 'undo',
102 	MSG_REDO						= 'redo',
103 	MSG_UNDO_STACK_CHANGED			= 'usch',
104 
105 	MSG_PATH_SELECTED				= 'vpsl',
106 	MSG_STYLE_SELECTED				= 'stsl',
107 	MSG_SHAPE_SELECTED				= 'spsl',
108 
109 	MSG_SHAPE_RESET_TRANSFORMATION	= 'rtsh',
110 	MSG_STYLE_RESET_TRANSFORMATION	= 'rtst',
111 
112 	MSG_MOUSE_FILTER_MODE			= 'mfmd',
113 
114 	MSG_RENAME_OBJECT				= 'rnam',
115 };
116 
117 
118 MainWindow::MainWindow(BRect frame, IconEditorApp* app,
119 		const BMessage* settings)
120 	:
121 	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Icon-O-Matic"),
122 		B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
123 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
124 	fApp(app),
125 	fDocument(new Document(B_TRANSLATE("Untitled"))),
126 	fCurrentColor(new CurrentColor()),
127 	fIcon(NULL),
128 	fMessageAfterSave(NULL)
129 {
130 	_Init();
131 
132 	RestoreSettings(settings);
133 }
134 
135 
136 MainWindow::~MainWindow()
137 {
138 	SetIcon(NULL);
139 
140 	delete fState;
141 
142 	// Make sure there are no listeners attached to the document anymore.
143 	while (BView* child = ChildAt(0L)) {
144 		child->RemoveSelf();
145 		delete child;
146 	}
147 
148 	fDocument->CommandStack()->RemoveObserver(this);
149 
150 	// NOTE: it is important that the GUI has been deleted
151 	// at this point, so that all the listener/observer
152 	// stuff is properly detached
153 	delete fDocument;
154 
155 	delete fCurrentColor;
156 	delete fMessageAfterSave;
157 }
158 
159 
160 // #pragma mark -
161 
162 
163 void
164 MainWindow::MessageReceived(BMessage* message)
165 {
166 	bool discard = false;
167 
168 	// Figure out if we need the write lock on the Document. For most
169 	// messages we do, but exporting takes place in another thread and
170 	// locking is taken care of there.
171 	bool requiresWriteLock = true;
172 	switch (message->what) {
173 		case MSG_SAVE:
174 		case MSG_EXPORT:
175 		case MSG_SAVE_AS:
176 		case MSG_EXPORT_AS:
177 			requiresWriteLock = false;
178 			break;
179 		default:
180 			break;
181 	}
182 	if (requiresWriteLock && !fDocument->WriteLock()) {
183 		BWindow::MessageReceived(message);
184 		return;
185 	}
186 
187 	if (message->WasDropped()) {
188 		const rgb_color* color;
189 		ssize_t length;
190 		// create styles from dropped colors
191 		for (int32 i = 0; message->FindData("RGBColor", B_RGB_COLOR_TYPE, i,
192 			(const void**)&color, &length) == B_OK; i++) {
193 			if (length != sizeof(rgb_color))
194 				continue;
195 			char name[30];
196 			sprintf(name,
197 				B_TRANSLATE_COMMENT("Color (#%02x%02x%02x)",
198 					"Style name after dropping a color"),
199 				color->red, color->green, color->blue);
200 			Style* style = new (nothrow) Style(*color);
201 			style->SetName(name);
202 			Style* styles[1] = { style };
203 			AddCommand<Style>* styleCommand = new (nothrow) AddCommand<Style>(
204 				fDocument->Icon()->Styles(), styles, 1, true,
205 				fDocument->Icon()->Styles()->CountItems());
206 			fDocument->CommandStack()->Perform(styleCommand);
207 			// don't handle anything else,
208 			// or we might paste the clipboard on B_PASTE
209 			discard = true;
210 		}
211 	}
212 
213 	switch (message->what) {
214 
215 		case B_REFS_RECEIVED:
216 		case B_SIMPLE_DATA:
217 		{
218 			entry_ref ref;
219 			if (message->FindRef("refs", &ref) != B_OK)
220 				break;
221 
222 			// Check if this is best represented by a ReferenceImage
223 			BMimeType type;
224 			if (BMimeType::GuessMimeType(&ref, &type) == B_OK) {
225 				BMimeType superType;
226 				if (type.GetSupertype(&superType) == B_OK
227 					&& superType == BMimeType("image")
228 					&& !(type == BMimeType("image/svg+xml"))
229 					&& !(type == BMimeType("image/x-hvif"))) {
230 					AddReferenceImage(ref);
231 					break;
232 				}
233 			}
234 
235 			// If our icon is empty, open the file in this window,
236 			// otherwise forward to the application which will open
237 			// it in another window, unless we append.
238 			message->what = B_REFS_RECEIVED;
239 			if (fDocument->Icon()->Styles()->CountItems() == 0
240 				&& fDocument->Icon()->Paths()->CountItems() == 0
241 				&& fDocument->Icon()->Shapes()->CountItems() == 0) {
242 				Open(ref);
243 				break;
244 			}
245 			if (modifiers() & B_SHIFT_KEY) {
246 				// We want the icon appended to this window.
247 				message->AddBool("append", true);
248 				message->AddPointer("window", this);
249 			}
250 			be_app->PostMessage(message);
251 			break;
252 		}
253 
254 		case B_PASTE:
255 		case B_MIME_DATA:
256 		{
257 			BMessage* clip = message;
258 			status_t err;
259 
260 			if (discard)
261 				break;
262 
263 			if (message->what == B_PASTE) {
264 				if (!be_clipboard->Lock())
265 					break;
266 				clip = be_clipboard->Data();
267 			}
268 
269 			if (!clip || !clip->HasData("text/plain", B_MIME_TYPE)) {
270 				if (message->what == B_PASTE)
271 					be_clipboard->Unlock();
272 				break;
273 			}
274 
275 			Icon* icon = new (std::nothrow) Icon(*fDocument->Icon());
276 			if (icon != NULL) {
277 				StyledTextImporter importer;
278 				err = importer.Import(icon, clip);
279 				if (err >= B_OK) {
280 					AutoWriteLocker locker(fDocument);
281 
282 					SetIcon(NULL);
283 
284 					// incorporate the loaded icon into the document
285 					// (either replace it or append to it)
286 					fDocument->MakeEmpty(false);
287 						// if append, the document savers are preserved
288 					fDocument->SetIcon(icon);
289 					SetIcon(icon);
290 				}
291 			}
292 
293 			if (message->what == B_PASTE)
294 				be_clipboard->Unlock();
295 			break;
296 		}
297 
298 		case MSG_OPEN:
299 		{
300 			// If our icon is empty, we want the icon to open in this
301 			// window.
302 			bool emptyDocument = fDocument->Icon()->Styles()->CountItems() == 0
303 				&& fDocument->Icon()->Paths()->CountItems() == 0
304 				&& fDocument->Icon()->Shapes()->CountItems() == 0;
305 
306 			bool openingReferenceImage;
307 			if (message->FindBool("reference image", &openingReferenceImage) != B_OK)
308 				openingReferenceImage = false;
309 
310 			if (emptyDocument || openingReferenceImage)
311 				message->AddPointer("window", this);
312 
313 			be_app->PostMessage(message);
314 			break;
315 		}
316 
317 		case MSG_SAVE:
318 		case MSG_EXPORT:
319 		{
320 			DocumentSaver* saver;
321 			if (message->what == MSG_SAVE)
322 				saver = fDocument->NativeSaver();
323 			else
324 				saver = fDocument->ExportSaver();
325 			if (saver != NULL) {
326 				saver->Save(fDocument);
327 				_PickUpActionBeforeSave();
328 				break;
329 			} // else fall through
330 		}
331 		case MSG_SAVE_AS:
332 		case MSG_EXPORT_AS:
333 		{
334 			int32 exportMode;
335 			if (message->FindInt32("export mode", &exportMode) < B_OK)
336 				exportMode = EXPORT_MODE_MESSAGE;
337 			entry_ref ref;
338 			const char* name;
339 			if (message->FindRef("directory", &ref) == B_OK
340 				&& message->FindString("name", &name) == B_OK) {
341 				// this message comes from the file panel
342 				BDirectory dir(&ref);
343 				BEntry entry;
344 				if (dir.InitCheck() >= B_OK
345 					&& entry.SetTo(&dir, name, true) >= B_OK
346 					&& entry.GetRef(&ref) >= B_OK) {
347 
348 					// create the document saver and remember it for later
349 					DocumentSaver* saver = _CreateSaver(ref, exportMode);
350 					if (saver != NULL) {
351 						if (fDocument->WriteLock()) {
352 							if (exportMode == EXPORT_MODE_MESSAGE)
353 								fDocument->SetNativeSaver(saver);
354 							else
355 								fDocument->SetExportSaver(saver);
356 							_UpdateWindowTitle();
357 							fDocument->WriteUnlock();
358 						}
359 						saver->Save(fDocument);
360 						_PickUpActionBeforeSave();
361 					}
362 				}
363 // TODO: ...
364 //				_SyncPanels(fSavePanel, fOpenPanel);
365 			} else {
366 				// configure the file panel
367 				uint32 requestRefWhat = MSG_SAVE_AS;
368 				bool isExportMode = message->what == MSG_EXPORT_AS
369 					|| message->what == MSG_EXPORT;
370 				if (isExportMode)
371 					requestRefWhat = MSG_EXPORT_AS;
372 				const char* saveText = _FileName(isExportMode);
373 
374 				BMessage requestRef(requestRefWhat);
375 				if (saveText != NULL)
376 					requestRef.AddString("save text", saveText);
377 				requestRef.AddMessenger("target", BMessenger(this, this));
378 				be_app->PostMessage(&requestRef);
379 			}
380 			break;
381 		}
382 		case B_CANCEL:
383 			// FilePanel was canceled, do not execute the fMessageAfterSave
384 			// next time a file panel is used, in case it was set!
385 			delete fMessageAfterSave;
386 			fMessageAfterSave = NULL;
387 			break;
388 
389 		case MSG_UNDO:
390 			fDocument->CommandStack()->Undo();
391 			break;
392 		case MSG_REDO:
393 			fDocument->CommandStack()->Redo();
394 			break;
395 		case MSG_UNDO_STACK_CHANGED:
396 		{
397 			// relable Undo item and update enabled status
398 			BString label(B_TRANSLATE("Undo: %action%"));
399 			BString temp;
400 			fUndoMI->SetEnabled(fDocument->CommandStack()->GetUndoName(temp));
401 			label.ReplaceFirst("%action%", temp);
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: %action%"));
411 			temp.SetTo("");
412 			fRedoMI->SetEnabled(fDocument->CommandStack()->GetRedoName(temp));
413 			label.ReplaceFirst("%action%", temp);
414 			if (fRedoMI->IsEnabled())
415 				fRedoMI->SetLabel(label.String());
416 			else {
417 				fRedoMI->SetLabel(B_TRANSLATE_CONTEXT("<nothing to redo>",
418 					"Icon-O-Matic-Menu-Edit"));
419 			}
420 			break;
421 		}
422 
423 		case MSG_MOUSE_FILTER_MODE:
424 		{
425 			uint32 mode;
426 			if (message->FindInt32("mode", (int32*)&mode) == B_OK)
427 				fCanvasView->SetMouseFilterMode(mode);
428 			break;
429 		}
430 
431 		case MSG_ADD_SHAPE: {
432 			AddStylesCommand* styleCommand = NULL;
433 			Style* style = NULL;
434 			if (message->HasBool("style")) {
435 				new_style(fCurrentColor->Color(),
436 					fDocument->Icon()->Styles(), &style, &styleCommand);
437 			}
438 
439 			AddPathsCommand* pathCommand = NULL;
440 			VectorPath* path = NULL;
441 			if (message->HasBool("path")) {
442 				new_path(fDocument->Icon()->Paths(), &path, &pathCommand);
443 			}
444 
445 			if (!style) {
446 				// use current or first style
447 				int32 currentStyle = fStyleListView->CurrentSelection(0);
448 				style = fDocument->Icon()->Styles()->ItemAt(currentStyle);
449 				if (!style)
450 					style = fDocument->Icon()->Styles()->ItemAt(0);
451 			}
452 
453 			PathSourceShape* shape = new (nothrow) PathSourceShape(style);
454 			AddShapesCommand* shapeCommand = new (nothrow) AddShapesCommand(
455 				fDocument->Icon()->Shapes(), (Shape**) &shape, 1,
456 				fDocument->Icon()->Shapes()->CountItems());
457 
458 			if (path && shape)
459 				shape->Paths()->AddItem(path);
460 
461 			::Command* command = NULL;
462 			if (styleCommand || pathCommand) {
463 				if (styleCommand && pathCommand) {
464 					Command** commands = new Command*[3];
465 					commands[0] = styleCommand;
466 					commands[1] = pathCommand;
467 					commands[2] = shapeCommand;
468 					command = new CompoundCommand(commands, 3,
469 						B_TRANSLATE_CONTEXT("Add shape with path & style",
470 							"Icon-O-Matic-Menu-Shape"),
471 						0);
472 				} else if (styleCommand) {
473 					Command** commands = new Command*[2];
474 					commands[0] = styleCommand;
475 					commands[1] = shapeCommand;
476 					command = new CompoundCommand(commands, 2,
477 						B_TRANSLATE_CONTEXT("Add shape with style",
478 							"Icon-O-Matic-Menu-Shape"),
479 						0);
480 				} else {
481 					Command** commands = new Command*[2];
482 					commands[0] = pathCommand;
483 					commands[1] = shapeCommand;
484 					command = new CompoundCommand(commands, 2,
485 						B_TRANSLATE_CONTEXT("Add shape with path",
486 							"Icon-O-Matic-Menu-Shape"),
487 						0);
488 				}
489 			} else {
490 				command = shapeCommand;
491 			}
492 			fDocument->CommandStack()->Perform(command);
493 			break;
494 		}
495 
496 // TODO: listen to selection in CanvasView to add a manipulator
497 case MSG_PATH_SELECTED: {
498 	VectorPath* path;
499 	if (message->FindPointer("path", (void**)&path) < B_OK)
500 		path = NULL;
501 
502 	fPathListView->SetCurrentShape(NULL);
503 	fStyleListView->SetCurrentShape(NULL);
504 	fTransformerListView->SetShape(NULL);
505 
506 	fState->DeleteManipulators();
507 	if (fDocument->Icon()->Paths()->HasItem(path)) {
508 		PathManipulator* pathManipulator = new (nothrow) PathManipulator(path);
509 		fState->AddManipulator(pathManipulator);
510 	}
511 	break;
512 }
513 case MSG_STYLE_SELECTED:
514 case MSG_STYLE_TYPE_CHANGED: {
515 	Style* style;
516 	if (message->FindPointer("style", (void**)&style) < B_OK)
517 		style = NULL;
518 	if (!fDocument->Icon()->Styles()->HasItem(style))
519 		style = NULL;
520 
521 	fStyleView->SetStyle(style);
522 	fPathListView->SetCurrentShape(NULL);
523 	fStyleListView->SetCurrentShape(NULL);
524 	fTransformerListView->SetShape(NULL);
525 
526 	fState->DeleteManipulators();
527 	Gradient* gradient = style ? style->Gradient() : NULL;
528 	if (gradient != NULL) {
529 		TransformGradientBox* transformBox
530 			= new (nothrow) TransformGradientBox(fCanvasView, gradient, NULL);
531 		fState->AddManipulator(transformBox);
532 	}
533 	break;
534 }
535 case MSG_SHAPE_SELECTED: {
536 	Shape* shape;
537 	if (message->FindPointer("shape", (void**)&shape) < B_OK)
538 		shape = NULL;
539 	if (!fIcon || !fIcon->Shapes()->HasItem(shape))
540 		shape = NULL;
541 
542 	fPathListView->SetCurrentShape(shape);
543 	fStyleListView->SetCurrentShape(shape);
544 	fTransformerListView->SetShape(shape);
545 
546 	BList selectedShapes;
547 	Container<Shape>* shapes = fDocument->Icon()->Shapes();
548 	int32 count = shapes->CountItems();
549 	for (int32 i = 0; i < count; i++) {
550 		shape = shapes->ItemAtFast(i);
551 		if (shape->IsSelected()) {
552 			selectedShapes.AddItem((void*)shape);
553 		}
554 	}
555 
556 	fState->DeleteManipulators();
557 	if (selectedShapes.CountItems() > 0) {
558 		TransformShapesBox* transformBox = new (nothrow) TransformShapesBox(
559 			fCanvasView,
560 			(const Shape**)selectedShapes.Items(),
561 			selectedShapes.CountItems());
562 		fState->AddManipulator(transformBox);
563 	}
564 	break;
565 }
566 		case MSG_RENAME_OBJECT:
567 			fPropertyListView->FocusNameProperty();
568 			break;
569 
570 		default:
571 			BWindow::MessageReceived(message);
572 	}
573 
574 	if (requiresWriteLock)
575 		fDocument->WriteUnlock();
576 }
577 
578 
579 void
580 MainWindow::Show()
581 {
582 	BWindow::Show();
583 	BMenuBar* bar = static_cast<BMenuBar*>(FindView("main menu"));
584 	SetKeyMenuBar(bar);
585 }
586 
587 
588 bool
589 MainWindow::QuitRequested()
590 {
591 	if (!_CheckSaveIcon(CurrentMessage()))
592 		return false;
593 
594 	BMessage message(MSG_WINDOW_CLOSED);
595 
596 	BMessage settings;
597 	StoreSettings(&settings);
598 	message.AddMessage("settings", &settings);
599 	message.AddRect("window frame", Frame());
600 
601 	be_app->PostMessage(&message);
602 
603 	return true;
604 }
605 
606 
607 void
608 MainWindow::WorkspaceActivated(int32 workspace, bool active)
609 {
610 	BWindow::WorkspaceActivated(workspace, active);
611 
612 	if (active)
613 		_WorkspaceEntered();
614 }
615 
616 
617 void
618 MainWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces)
619 {
620 	BWindow::WorkspacesChanged(oldWorkspaces, newWorkspaces);
621 
622 	if((1 << current_workspace() & newWorkspaces) != 0)
623 		_WorkspaceEntered();
624 }
625 
626 
627 // #pragma mark -
628 
629 
630 void
631 MainWindow::ObjectChanged(const Observable* object)
632 {
633 	if (!fDocument || !fDocument->ReadLock())
634 		return;
635 
636 	if (object == fDocument->CommandStack())
637 		PostMessage(MSG_UNDO_STACK_CHANGED);
638 
639 	fDocument->ReadUnlock();
640 }
641 
642 
643 // #pragma mark -
644 
645 
646 void
647 MainWindow::MakeEmpty()
648 {
649 	fPathListView->SetCurrentShape(NULL);
650 	fStyleListView->SetCurrentShape(NULL);
651 	fStyleView->SetStyle(NULL);
652 
653 	fTransformerListView->SetShape(NULL);
654 
655 	fState->DeleteManipulators();
656 }
657 
658 
659 void
660 MainWindow::Open(const entry_ref& ref, bool append)
661 {
662 	BFile file(&ref, B_READ_ONLY);
663 	if (file.InitCheck() < B_OK)
664 		return;
665 
666 	Icon* icon;
667 	if (append)
668 		icon = new (nothrow) Icon(*fDocument->Icon());
669 	else
670 		icon = new (nothrow) Icon();
671 
672 	if (icon == NULL) {
673 		// TODO: Report error to user.
674 		return;
675 	}
676 
677 	enum {
678 		REF_NONE = 0,
679 		REF_MESSAGE,
680 		REF_FLAT,
681 		REF_FLAT_ATTR,
682 		REF_SVG
683 	};
684 	uint32 refMode = REF_NONE;
685 
686 	// try different file types
687 	FlatIconImporter flatImporter;
688 	status_t ret = flatImporter.Import(icon, &file);
689 	if (ret >= B_OK) {
690 		refMode = REF_FLAT;
691 	} else {
692 		file.Seek(0, SEEK_SET);
693 		MessageImporter msgImporter;
694 		ret = msgImporter.Import(icon, &file);
695 		if (ret >= B_OK) {
696 			refMode = REF_MESSAGE;
697 		} else {
698 			file.Seek(0, SEEK_SET);
699 			SVGImporter svgImporter;
700 			ret = svgImporter.Import(icon, &ref);
701 			if (ret >= B_OK) {
702 				refMode = REF_SVG;
703 			} else {
704 				// fall back to flat icon format but use the icon attribute
705 				ret = B_OK;
706 				attr_info attrInfo;
707 				if (file.GetAttrInfo(kVectorAttrNodeName, &attrInfo) == B_OK) {
708 					if (attrInfo.type != B_VECTOR_ICON_TYPE)
709 						ret = B_ERROR;
710 					// If the attribute is there, we must succeed in reading
711 					// an icon! Otherwise we may overwrite an existing icon
712 					// when the user saves.
713 					uint8* buffer = NULL;
714 					if (ret == B_OK) {
715 						buffer = new(nothrow) uint8[attrInfo.size];
716 						if (buffer == NULL)
717 							ret = B_NO_MEMORY;
718 					}
719 					if (ret == B_OK) {
720 						ssize_t bytesRead = file.ReadAttr(kVectorAttrNodeName,
721 							B_VECTOR_ICON_TYPE, 0, buffer, attrInfo.size);
722 						if (bytesRead != (ssize_t)attrInfo.size) {
723 							ret = bytesRead < 0 ? (status_t)bytesRead
724 								: B_IO_ERROR;
725 						}
726 					}
727 					if (ret == B_OK) {
728 						ret = flatImporter.Import(icon, buffer, attrInfo.size);
729 						if (ret == B_OK)
730 							refMode = REF_FLAT_ATTR;
731 					}
732 
733 					delete[] buffer;
734 				} else {
735 					// If there is no icon attribute, simply fall back
736 					// to creating an icon for this file. TODO: We may or may
737 					// not want to display an alert asking the user if that is
738 					// what he wants to do.
739 					refMode = REF_FLAT_ATTR;
740 				}
741 			}
742 		}
743 	}
744 
745 	if (ret < B_OK) {
746 		// inform user of failure at this point
747 		BString helper(B_TRANSLATE("Opening the document failed!"));
748 		helper << "\n\n" << B_TRANSLATE("Error: ") << strerror(ret);
749 		BAlert* alert = new BAlert(
750 			B_TRANSLATE_CONTEXT("Bad news", "Title of error alert"),
751 			helper.String(),
752 			B_TRANSLATE_COMMENT("Bummer",
753 				"Cancel button - error alert"),
754 			NULL, NULL);
755 		// launch alert asynchronously
756 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
757 		alert->Go(NULL);
758 
759 		delete icon;
760 		return;
761 	}
762 
763 	AutoWriteLocker locker(fDocument);
764 
765 	// incorporate the loaded icon into the document
766 	// (either replace it or append to it)
767 	fDocument->MakeEmpty(!append);
768 		// if append, the document savers are preserved
769 	fDocument->SetIcon(icon);
770 	if (!append) {
771 		// document got replaced, but we have at
772 		// least one ref already
773 		switch (refMode) {
774 			case REF_MESSAGE:
775 				fDocument->SetNativeSaver(new NativeSaver(ref));
776 				break;
777 			case REF_FLAT:
778 				fDocument->SetExportSaver(
779 					new SimpleFileSaver(new FlatIconExporter(), ref));
780 				break;
781 			case REF_FLAT_ATTR:
782 				fDocument->SetNativeSaver(
783 					new AttributeSaver(ref, kVectorAttrNodeName));
784 				break;
785 			case REF_SVG:
786 				fDocument->SetExportSaver(
787 					new SimpleFileSaver(new SVGExporter(), ref));
788 				break;
789 		}
790 	}
791 
792 	locker.Unlock();
793 
794 	SetIcon(icon);
795 
796 	_UpdateWindowTitle();
797 }
798 
799 
800 void
801 MainWindow::Open(const BMessenger& externalObserver, const uint8* data,
802 	size_t size)
803 {
804 	if (!_CheckSaveIcon(CurrentMessage()))
805 		return;
806 
807 	if (!externalObserver.IsValid())
808 		return;
809 
810 	Icon* icon = new (nothrow) Icon();
811 	if (!icon)
812 		return;
813 
814 	if (data && size > 0) {
815 		// try to open the icon from the provided data
816 		FlatIconImporter flatImporter;
817 		status_t ret = flatImporter.Import(icon, const_cast<uint8*>(data),
818 			size);
819 			// NOTE: the const_cast is a bit ugly, but no harm is done
820 			// the reason is that the LittleEndianBuffer knows read and write
821 			// mode, in this case it is used read-only, and it does not assume
822 			// ownership of the buffer
823 
824 		if (ret < B_OK) {
825 			// inform user of failure at this point
826 			BString helper(B_TRANSLATE("Opening the icon failed!"));
827 			helper << "\n\n" << B_TRANSLATE("Error: ") << strerror(ret);
828 			BAlert* alert = new BAlert(
829 				B_TRANSLATE_CONTEXT("Bad news", "Title of error alert"),
830 				helper.String(),
831 				B_TRANSLATE_COMMENT("Bummer",
832 					"Cancel button - error alert"),
833 				NULL, NULL);
834 			// launch alert asynchronously
835 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
836 			alert->Go(NULL);
837 
838 			delete icon;
839 			return;
840 		}
841 	}
842 
843 	AutoWriteLocker locker(fDocument);
844 
845 	SetIcon(NULL);
846 
847 	// incorporate the loaded icon into the document
848 	// (either replace it or append to it)
849 	fDocument->MakeEmpty();
850 	fDocument->SetIcon(icon);
851 
852 	fDocument->SetNativeSaver(new MessengerSaver(externalObserver));
853 
854 	locker.Unlock();
855 
856 	SetIcon(icon);
857 }
858 
859 
860 void
861 MainWindow::AddReferenceImage(const entry_ref& ref)
862 {
863 	BBitmap* image = BTranslationUtils::GetBitmap(&ref);
864 	if (image == NULL)
865 		return;
866 	Shape* shape = new (nothrow) ReferenceImage(image);
867 	if (shape == NULL)
868 		return;
869 
870 	AddShapesCommand* shapeCommand = new (nothrow) AddShapesCommand(
871 		fDocument->Icon()->Shapes(), &shape, 1,
872 		fDocument->Icon()->Shapes()->CountItems());
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