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