1 /* 2 * Copyright 2006, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #include "IconEditorApp.h" 10 11 #include <new> 12 #include <stdio.h> 13 #include <string.h> 14 15 #include <Alert.h> 16 #include <Directory.h> 17 #include <Entry.h> 18 #include <File.h> 19 #include <FilePanel.h> 20 #include <fs_attr.h> 21 #include <IconEditorProtocol.h> 22 #include <Message.h> 23 #include <Mime.h> 24 25 #include "support_settings.h" 26 27 #include "AttributeSaver.h" 28 #include "AutoLocker.h" 29 #include "BitmapExporter.h" 30 #include "BitmapSetSaver.h" 31 #include "CommandStack.h" 32 #include "Defines.h" 33 #include "Document.h" 34 #include "FlatIconExporter.h" 35 #include "FlatIconFormat.h" 36 #include "FlatIconImporter.h" 37 #include "Icon.h" 38 #include "MainWindow.h" 39 #include "MessageExporter.h" 40 #include "MessageImporter.h" 41 #include "MessengerSaver.h" 42 #include "NativeSaver.h" 43 #include "PathContainer.h" 44 #include "RDefExporter.h" 45 #include "SavePanel.h" 46 #include "ShapeContainer.h" 47 #include "SimpleFileSaver.h" 48 #include "SourceExporter.h" 49 #include "SVGExporter.h" 50 #include "SVGImporter.h" 51 52 using std::nothrow; 53 54 static const char* kAppSig = "application/x-vnd.haiku-icon_o_matic"; 55 56 // constructor 57 IconEditorApp::IconEditorApp() 58 : BApplication(kAppSig), 59 fMainWindow(NULL), 60 fDocument(new Document("test")), 61 62 fOpenPanel(NULL), 63 fSavePanel(NULL), 64 65 fLastOpenPath(""), 66 fLastSavePath(""), 67 fLastExportPath(""), 68 69 fMessageAfterSave(NULL) 70 { 71 } 72 73 // destructor 74 IconEditorApp::~IconEditorApp() 75 { 76 // NOTE: it is important that the GUI has been deleted 77 // at this point, so that all the listener/observer 78 // stuff is properly detached 79 delete fDocument; 80 81 delete fOpenPanel; 82 delete fSavePanel; 83 84 delete fMessageAfterSave; 85 } 86 87 // #pragma mark - 88 89 // QuitRequested 90 bool 91 IconEditorApp::QuitRequested() 92 { 93 if (!_CheckSaveIcon(CurrentMessage())) 94 return false; 95 96 _StoreSettings(); 97 98 fMainWindow->Lock(); 99 fMainWindow->Quit(); 100 fMainWindow = NULL; 101 102 return true; 103 } 104 105 // MessageReceived 106 void 107 IconEditorApp::MessageReceived(BMessage* message) 108 { 109 switch (message->what) { 110 case MSG_NEW: 111 _MakeIconEmpty(); 112 break; 113 case MSG_OPEN: { 114 BMessage openMessage(B_REFS_RECEIVED); 115 fOpenPanel->SetMessage(&openMessage); 116 fOpenPanel->Show(); 117 break; 118 } 119 case MSG_APPEND: { 120 BMessage openMessage(B_REFS_RECEIVED); 121 openMessage.AddBool("append", true); 122 fOpenPanel->SetMessage(&openMessage); 123 fOpenPanel->Show(); 124 break; 125 } 126 case MSG_SAVE: 127 case MSG_EXPORT: { 128 DocumentSaver* saver; 129 if (message->what == MSG_SAVE) 130 saver = fDocument->NativeSaver(); 131 else 132 saver = fDocument->ExportSaver(); 133 if (saver) { 134 saver->Save(fDocument); 135 _PickUpActionBeforeSave(); 136 break; 137 } // else fall through 138 } 139 case MSG_SAVE_AS: 140 case MSG_EXPORT_AS: { 141 int32 exportMode; 142 if (message->FindInt32("export mode", &exportMode) < B_OK) 143 exportMode = EXPORT_MODE_MESSAGE; 144 entry_ref ref; 145 const char* name; 146 if (message->FindRef("directory", &ref) == B_OK 147 && message->FindString("name", &name) == B_OK) { 148 // this message comes from the file panel 149 BDirectory dir(&ref); 150 BEntry entry; 151 if (dir.InitCheck() >= B_OK 152 && entry.SetTo(&dir, name, true) >= B_OK 153 && entry.GetRef(&ref) >= B_OK) { 154 155 // create the document saver and remember it for later 156 DocumentSaver* saver = _CreateSaver(ref, exportMode); 157 if (saver) { 158 if (exportMode == EXPORT_MODE_MESSAGE) 159 fDocument->SetNativeSaver(saver); 160 else 161 fDocument->SetExportSaver(saver); 162 saver->Save(fDocument); 163 _PickUpActionBeforeSave(); 164 } 165 } 166 _SyncPanels(fSavePanel, fOpenPanel); 167 } else { 168 // configure the file panel 169 const char* saveText = NULL; 170 FileSaver* saver = dynamic_cast<FileSaver*>( 171 fDocument->NativeSaver()); 172 173 bool exportMode = message->what == MSG_EXPORT_AS 174 || message->what == MSG_EXPORT; 175 if (exportMode) { 176 saver = dynamic_cast<FileSaver*>( 177 fDocument->ExportSaver()); 178 } 179 180 if (saver) 181 saveText = saver->Ref()->name; 182 183 fSavePanel->SetExportMode(exportMode); 184 // fSavePanel->Refresh(); 185 if (saveText) 186 fSavePanel->SetSaveText(saveText); 187 fSavePanel->Show(); 188 } 189 break; 190 } 191 case B_EDIT_ICON_DATA: { 192 BMessenger messenger; 193 if (message->FindMessenger("reply to", &messenger) < B_OK) { 194 // required 195 break; 196 } 197 const uint8* data; 198 ssize_t size; 199 if (message->FindData("icon data", B_VECTOR_ICON_TYPE, 200 (const void**)&data, &size) < B_OK) { 201 // optional (new icon will be created) 202 data = NULL; 203 size = 0; 204 } 205 _Open(messenger, data, size); 206 break; 207 } 208 209 default: 210 BApplication::MessageReceived(message); 211 break; 212 } 213 } 214 215 // ReadyToRun 216 void 217 IconEditorApp::ReadyToRun() 218 { 219 // create file panels 220 BMessenger messenger(this, this); 221 BMessage message(B_REFS_RECEIVED); 222 fOpenPanel = new BFilePanel(B_OPEN_PANEL, 223 &messenger, 224 NULL, 225 B_FILE_NODE, 226 true, 227 &message); 228 229 message.what = MSG_SAVE_AS; 230 fSavePanel = new SavePanel("save panel", 231 &messenger, 232 NULL, 233 B_FILE_NODE 234 | B_DIRECTORY_NODE 235 | B_SYMLINK_NODE, 236 false, 237 &message); 238 239 // create main window 240 BMessage settings('stns'); 241 _RestoreSettings(settings); 242 243 fMainWindow = new MainWindow(this, fDocument, &settings); 244 fMainWindow->Show(); 245 246 _InstallDocumentMimeType(); 247 } 248 249 // RefsReceived 250 void 251 IconEditorApp::RefsReceived(BMessage* message) 252 { 253 // TODO: multiple documents (iterate over refs) 254 bool append; 255 if (message->FindBool("append", &append) < B_OK) 256 append = false; 257 entry_ref ref; 258 if (message->FindRef("refs", &ref) == B_OK) 259 _Open(ref, append); 260 261 if (fOpenPanel && fSavePanel) 262 _SyncPanels(fOpenPanel, fSavePanel); 263 } 264 265 // ArgvReceived 266 void 267 IconEditorApp::ArgvReceived(int32 argc, char** argv) 268 { 269 if (argc < 2) 270 return; 271 272 // TODO: multiple documents (iterate over argv) 273 entry_ref ref; 274 if (get_ref_for_path(argv[1], &ref) == B_OK) 275 _Open(ref); 276 } 277 278 // #pragma mark - 279 280 // _CheckSaveIcon 281 bool 282 IconEditorApp::_CheckSaveIcon(const BMessage* currentMessage) 283 { 284 if (fDocument->IsEmpty() || fDocument->CommandStack()->IsSaved()) 285 return true; 286 287 BAlert* alert = new BAlert("save", "Save changes to current icon?", 288 "Discard", "Cancel", "Save"); 289 int32 choice = alert->Go(); 290 switch (choice) { 291 case 0: 292 // discard 293 return true; 294 case 1: 295 // cancel 296 return false; 297 case 2: 298 default: 299 // cancel (save first) but pick what we were doing before 300 PostMessage(MSG_SAVE); 301 if (currentMessage) { 302 delete fMessageAfterSave; 303 fMessageAfterSave = new BMessage(*currentMessage); 304 } 305 return false; 306 } 307 } 308 309 // _PickUpActionBeforeSave 310 void 311 IconEditorApp::_PickUpActionBeforeSave() 312 { 313 if (fDocument->WriteLock()) { 314 fDocument->CommandStack()->Save(); 315 fDocument->WriteUnlock(); 316 } 317 318 if (!fMessageAfterSave) 319 return; 320 321 PostMessage(fMessageAfterSave); 322 delete fMessageAfterSave; 323 fMessageAfterSave = NULL; 324 } 325 326 // #pragma mark - 327 328 // _MakeIconEmpty 329 void 330 IconEditorApp::_MakeIconEmpty() 331 { 332 if (!_CheckSaveIcon(CurrentMessage())) 333 return; 334 335 bool mainWindowLocked = fMainWindow && fMainWindow->Lock(); 336 337 AutoWriteLocker locker(fDocument); 338 339 if (fMainWindow) 340 fMainWindow->MakeEmpty(); 341 342 fDocument->MakeEmpty(); 343 344 locker.Unlock(); 345 346 if (mainWindowLocked) 347 fMainWindow->Unlock(); 348 } 349 350 // _Open 351 void 352 IconEditorApp::_Open(const entry_ref& ref, bool append) 353 { 354 if (!_CheckSaveIcon(CurrentMessage())) 355 return; 356 357 BFile file(&ref, B_READ_ONLY); 358 if (file.InitCheck() < B_OK) 359 return; 360 361 Icon* icon; 362 if (append) 363 icon = new (nothrow) Icon(*fDocument->Icon()); 364 else 365 icon = new (nothrow) Icon(); 366 367 if (!icon) 368 return; 369 370 enum { 371 REF_NONE = 0, 372 REF_MESSAGE, 373 REF_FLAT, 374 REF_FLAT_ATTR, 375 REF_SVG 376 }; 377 uint32 refMode = REF_NONE; 378 379 // try different file types 380 FlatIconImporter flatImporter; 381 status_t ret = flatImporter.Import(icon, &file); 382 if (ret >= B_OK) { 383 refMode = REF_FLAT; 384 } else { 385 file.Seek(0, SEEK_SET); 386 MessageImporter msgImporter; 387 ret = msgImporter.Import(icon, &file); 388 if (ret >= B_OK) { 389 refMode = REF_MESSAGE; 390 } else { 391 file.Seek(0, SEEK_SET); 392 SVGImporter svgImporter; 393 ret = svgImporter.Import(icon, &ref); 394 if (ret >= B_OK) { 395 refMode = REF_SVG; 396 } else { 397 // fall back to flat icon format but use the icon attribute 398 ret = B_OK; 399 attr_info attrInfo; 400 if (file.GetAttrInfo(kVectorAttrNodeName, &attrInfo) == B_OK) { 401 if (attrInfo.type != B_VECTOR_ICON_TYPE) 402 ret = B_ERROR; 403 // If the attribute is there, we must succeed in reading 404 // an icon! Otherwise we may overwrite an existing icon 405 // when the user saves. 406 uint8* buffer = NULL; 407 if (ret == B_OK) { 408 buffer = new(nothrow) uint8[attrInfo.size]; 409 if (buffer == NULL) 410 ret = B_NO_MEMORY; 411 } 412 if (ret == B_OK) { 413 ssize_t bytesRead = file.ReadAttr(kVectorAttrNodeName, 414 B_VECTOR_ICON_TYPE, 0, buffer, attrInfo.size); 415 if (bytesRead != (ssize_t)attrInfo.size) { 416 ret = bytesRead < 0 ? (status_t)bytesRead 417 : B_IO_ERROR; 418 } 419 } 420 if (ret == B_OK) { 421 ret = flatImporter.Import(icon, buffer, attrInfo.size); 422 if (ret == B_OK) 423 refMode = REF_FLAT_ATTR; 424 } 425 426 delete[] buffer; 427 } else { 428 // If there is no icon attribute, simply fall back 429 // to creating an icon for this file. TODO: We may or may 430 // not want to display an alert asking the user if that is 431 // what he wants to do. 432 refMode = REF_FLAT_ATTR; 433 } 434 } 435 } 436 } 437 438 if (ret < B_OK) { 439 // inform user of failure at this point 440 BString helper("Opening the document failed!"); 441 helper << "\n\n" << "Error: " << strerror(ret); 442 BAlert* alert = new BAlert("bad news", helper.String(), 443 "Bummer", NULL, NULL); 444 // launch alert asynchronously 445 alert->Go(NULL); 446 447 delete icon; 448 return; 449 } 450 451 // keep the mainwindow locked while switching icons 452 bool mainWindowLocked = fMainWindow && fMainWindow->Lock(); 453 454 AutoWriteLocker locker(fDocument); 455 456 if (mainWindowLocked) 457 fMainWindow->SetIcon(NULL); 458 459 // incorporate the loaded icon into the document 460 // (either replace it or append to it) 461 fDocument->MakeEmpty(!append); 462 // if append, the document savers are preserved 463 fDocument->SetIcon(icon); 464 if (!append) { 465 // document got replaced, but we have at 466 // least one ref already 467 switch (refMode) { 468 case REF_MESSAGE: 469 fDocument->SetNativeSaver( 470 new NativeSaver(ref)); 471 break; 472 case REF_FLAT: 473 fDocument->SetExportSaver( 474 new SimpleFileSaver(new FlatIconExporter(), ref)); 475 break; 476 case REF_FLAT_ATTR: 477 fDocument->SetNativeSaver( 478 new AttributeSaver(ref, kVectorAttrNodeName)); 479 break; 480 case REF_SVG: 481 fDocument->SetExportSaver( 482 new SimpleFileSaver(new SVGExporter(), ref)); 483 break; 484 } 485 } 486 487 locker.Unlock(); 488 489 if (mainWindowLocked) { 490 fMainWindow->Unlock(); 491 // cause the mainwindow to adopt icon in 492 // it's own thread 493 fMainWindow->PostMessage(MSG_SET_ICON); 494 } 495 } 496 497 // _Open 498 void 499 IconEditorApp::_Open(const BMessenger& externalObserver, const uint8* data, 500 size_t size) 501 { 502 if (!_CheckSaveIcon(CurrentMessage())) 503 return; 504 505 if (!externalObserver.IsValid()) 506 return; 507 508 Icon* icon = new (nothrow) Icon(); 509 if (!icon) 510 return; 511 512 if (data && size > 0) { 513 // try to open the icon from the provided data 514 FlatIconImporter flatImporter; 515 status_t ret = flatImporter.Import(icon, const_cast<uint8*>(data), size); 516 // NOTE: the const_cast is a bit ugly, but no harm is done 517 // the reason is that the LittleEndianBuffer knows read and write 518 // mode, in this case it is used read-only, and it does not assume 519 // ownership of the buffer 520 521 if (ret < B_OK) { 522 // inform user of failure at this point 523 BString helper("Opening the icon failed!"); 524 helper << "\n\n" << "Error: " << strerror(ret); 525 BAlert* alert = new BAlert("bad news", helper.String(), 526 "Bummer", NULL, NULL); 527 // launch alert asynchronously 528 alert->Go(NULL); 529 530 delete icon; 531 return; 532 } 533 } 534 535 // keep the mainwindow locked while switching icons 536 bool mainWindowLocked = fMainWindow && fMainWindow->Lock(); 537 538 AutoWriteLocker locker(fDocument); 539 540 if (mainWindowLocked) 541 fMainWindow->SetIcon(NULL); 542 543 // incorporate the loaded icon into the document 544 // (either replace it or append to it) 545 fDocument->MakeEmpty(); 546 fDocument->SetIcon(icon); 547 548 fDocument->SetNativeSaver(new MessengerSaver(externalObserver)); 549 550 locker.Unlock(); 551 552 if (mainWindowLocked) { 553 fMainWindow->Unlock(); 554 // cause the mainwindow to adopt icon in 555 // it's own thread 556 fMainWindow->PostMessage(MSG_SET_ICON); 557 } 558 } 559 560 // _CreateSaver 561 DocumentSaver* 562 IconEditorApp::_CreateSaver(const entry_ref& ref, uint32 exportMode) 563 { 564 DocumentSaver* saver; 565 566 switch (exportMode) { 567 case EXPORT_MODE_FLAT_ICON: 568 saver = new SimpleFileSaver(new FlatIconExporter(), ref); 569 break; 570 571 case EXPORT_MODE_ICON_ATTR: 572 case EXPORT_MODE_ICON_MIME_ATTR: { 573 const char* attrName 574 = exportMode == EXPORT_MODE_ICON_ATTR ? 575 kVectorAttrNodeName : kVectorAttrMimeName; 576 saver = new AttributeSaver(ref, attrName); 577 break; 578 } 579 580 case EXPORT_MODE_ICON_RDEF: 581 saver = new SimpleFileSaver(new RDefExporter(), ref); 582 break; 583 case EXPORT_MODE_ICON_SOURCE: 584 saver = new SimpleFileSaver(new SourceExporter(), ref); 585 break; 586 587 case EXPORT_MODE_BITMAP: 588 saver = new SimpleFileSaver(new BitmapExporter(64), ref); 589 break; 590 591 case EXPORT_MODE_BITMAP_SET: 592 saver = new BitmapSetSaver(ref); 593 break; 594 595 case EXPORT_MODE_SVG: 596 saver = new SimpleFileSaver(new SVGExporter(), ref); 597 break; 598 599 case EXPORT_MODE_MESSAGE: 600 default: 601 saver = new NativeSaver(ref); 602 break; 603 } 604 605 return saver; 606 } 607 608 // _SyncPanels 609 void 610 IconEditorApp::_SyncPanels(BFilePanel* from, BFilePanel* to) 611 { 612 if (from->Window()->Lock()) { 613 // location 614 if (to->Window()->Lock()) { 615 BRect frame = from->Window()->Frame(); 616 to->Window()->MoveTo(frame.left, frame.top); 617 to->Window()->ResizeTo(frame.Width(), frame.Height()); 618 to->Window()->Unlock(); 619 } 620 // current folder 621 entry_ref panelDir; 622 from->GetPanelDirectory(&panelDir); 623 to->SetPanelDirectory(&panelDir); 624 from->Window()->Unlock(); 625 } 626 } 627 628 // _LastFilePath 629 const char* 630 IconEditorApp::_LastFilePath(path_kind which) 631 { 632 const char* path = NULL; 633 634 switch (which) { 635 case LAST_PATH_OPEN: 636 if (fLastOpenPath.Length() > 0) 637 path = fLastOpenPath.String(); 638 else if (fLastSavePath.Length() > 0) 639 path = fLastSavePath.String(); 640 else if (fLastExportPath.Length() > 0) 641 path = fLastExportPath.String(); 642 break; 643 case LAST_PATH_SAVE: 644 if (fLastSavePath.Length() > 0) 645 path = fLastSavePath.String(); 646 else if (fLastExportPath.Length() > 0) 647 path = fLastExportPath.String(); 648 else if (fLastOpenPath.Length() > 0) 649 path = fLastOpenPath.String(); 650 break; 651 case LAST_PATH_EXPORT: 652 if (fLastExportPath.Length() > 0) 653 path = fLastExportPath.String(); 654 else if (fLastSavePath.Length() > 0) 655 path = fLastSavePath.String(); 656 else if (fLastOpenPath.Length() > 0) 657 path = fLastOpenPath.String(); 658 break; 659 } 660 if (!path) 661 path = "/boot/home"; 662 663 return path; 664 } 665 666 // #pragma mark - 667 668 // _StoreSettings 669 void 670 IconEditorApp::_StoreSettings() 671 { 672 BMessage settings('stns'); 673 674 fMainWindow->StoreSettings(&settings); 675 676 if (settings.ReplaceInt32("export mode", fSavePanel->ExportMode()) < B_OK) 677 settings.AddInt32("export mode", fSavePanel->ExportMode()); 678 679 save_settings(&settings, "Icon-O-Matic"); 680 } 681 682 // _RestoreSettings 683 void 684 IconEditorApp::_RestoreSettings(BMessage& settings) 685 { 686 load_settings(&settings, "Icon-O-Matic"); 687 688 int32 mode; 689 if (settings.FindInt32("export mode", &mode) >= B_OK) 690 fSavePanel->SetExportMode(mode); 691 } 692 693 // _InstallDocumentMimeType 694 void 695 IconEditorApp::_InstallDocumentMimeType() 696 { 697 // install mime type of documents 698 BMimeType mime(kNativeIconMimeType); 699 status_t ret = mime.InitCheck(); 700 if (ret < B_OK) { 701 fprintf(stderr, "Could not init native document mime type (%s): %s.\n", 702 kNativeIconMimeType, strerror(ret)); 703 return; 704 } 705 706 if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) { 707 // mime is already installed, and the user is not 708 // pressing the shift key to force a re-install 709 return; 710 } 711 712 ret = mime.Install(); 713 if (ret < B_OK) { 714 fprintf(stderr, "Could not install native document mime type (%s): %s.\n", 715 kNativeIconMimeType, strerror(ret)); 716 return; 717 } 718 // set preferred app 719 ret = mime.SetPreferredApp(kAppSig); 720 if (ret < B_OK) 721 fprintf(stderr, "Could not set native document preferred app: %s\n", 722 strerror(ret)); 723 724 // set descriptions 725 ret = mime.SetShortDescription("Haiku Icon"); 726 if (ret < B_OK) 727 fprintf(stderr, "Could not set short description of mime type: %s\n", 728 strerror(ret)); 729 ret = mime.SetLongDescription("Native Haiku vector icon"); 730 if (ret < B_OK) 731 fprintf(stderr, "Could not set long description of mime type: %s\n", 732 strerror(ret)); 733 734 // set extensions 735 BMessage message('extn'); 736 message.AddString("extensions", "icon"); 737 ret = mime.SetFileExtensions(&message); 738 if (ret < B_OK) 739 fprintf(stderr, "Could not set extensions of mime type: %s\n", 740 strerror(ret)); 741 742 // set sniffer rule 743 const char* snifferRule = "0.9 ('IMSG')"; 744 ret = mime.SetSnifferRule(snifferRule); 745 if (ret < B_OK) { 746 BString parseError; 747 BMimeType::CheckSnifferRule(snifferRule, &parseError); 748 fprintf(stderr, "Could not set sniffer rule of mime type: %s\n", 749 parseError.String()); 750 } 751 752 // NOTE: Icon-O-Matic writes the icon being saved as icon of the file anyways 753 // therefor, the following code is not needed, it is also not tested and I am 754 // spotting an error with SetIcon() 755 // // set document icon 756 // BResources* resources = AppResources(); 757 // // does not need to be freed (belongs to BApplication base) 758 // if (resources) { 759 // size_t size; 760 // const void* iconData = resources->LoadResource('VICN', "IOM:DOC_ICON", &size); 761 // if (iconData && size > 0) { 762 // memcpy(largeIcon.Bits(), iconData, size); 763 // if (mime.SetIcon(&largeIcon, B_LARGE_ICON) < B_OK) 764 // fprintf(stderr, "Could not set large icon of mime type.\n"); 765 // } else 766 // fprintf(stderr, "Could not find icon in app resources (data: %p, size: %ld).\n", iconData, size); 767 // } else 768 // fprintf(stderr, "Could not find app resources.\n"); 769 } 770