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 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 608 MainWindow::Show() 609 { 610 BWindow::Show(); 611 BMenuBar* bar = static_cast<BMenuBar*>(FindView("main menu")); 612 SetKeyMenuBar(bar); 613 } 614 615 616 bool 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 636 MainWindow::WorkspaceActivated(int32 workspace, bool active) 637 { 638 BWindow::WorkspaceActivated(workspace, active); 639 640 if (active) 641 _WorkspaceEntered(); 642 } 643 644 645 void 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 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 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 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 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 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 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 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 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 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 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 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* 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 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 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 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 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 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* 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* 1519 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 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