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 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 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 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 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 501 case 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 } 517 case MSG_STYLE_SELECTED: 518 case 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 } 539 case 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 } 570 case 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 597 void 598 MainWindow::Show() 599 { 600 BWindow::Show(); 601 BMenuBar* bar = static_cast<BMenuBar*>(FindView("main menu")); 602 SetKeyMenuBar(bar); 603 } 604 605 606 bool 607 MainWindow::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 625 void 626 MainWindow::WorkspaceActivated(int32 workspace, bool active) 627 { 628 BWindow::WorkspaceActivated(workspace, active); 629 630 if (active) 631 _WorkspaceEntered(); 632 } 633 634 635 void 636 MainWindow::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 648 void 649 MainWindow::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 664 void 665 MainWindow::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 677 void 678 MainWindow::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 818 void 819 MainWindow::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 878 void 879 MainWindow::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 900 void 901 MainWindow::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 923 void 924 MainWindow::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 967 void 968 MainWindow::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 978 void 979 MainWindow::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 995 void 996 MainWindow::_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 1053 void 1054 MainWindow::_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 1234 BMenuBar* 1235 MainWindow::_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 1349 void 1350 MainWindow::_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 1369 void 1370 MainWindow::_WorkspaceEntered() 1371 { 1372 BScreen screen(this); 1373 fIconPreview32Desktop->SetIconBGColor(screen.DesktopColor()); 1374 fIconPreview64->SetIconBGColor(screen.DesktopColor()); 1375 } 1376 1377 1378 // #pragma mark - 1379 1380 1381 bool 1382 MainWindow::_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 1419 void 1420 MainWindow::_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 1439 void 1440 MainWindow::_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 1454 DocumentSaver* 1455 MainWindow::_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 1508 const char* 1509 MainWindow::_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 1529 void 1530 MainWindow::_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