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