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