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