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