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 22 #include "support_settings.h" 23 24 #include "AttributeSaver.h" 25 #include "AutoLocker.h" 26 #include "BitmapExporter.h" 27 #include "BitmapSetSaver.h" 28 #include "CommandStack.h" 29 #include "Document.h" 30 #include "FlatIconExporter.h" 31 #include "FlatIconFormat.h" 32 #include "FlatIconImporter.h" 33 #include "Icon.h" 34 #include "MainWindow.h" 35 #include "MessageExporter.h" 36 #include "MessageImporter.h" 37 #include "MessengerSaver.h" 38 #include "PathContainer.h" 39 #include "RDefExporter.h" 40 #include "SavePanel.h" 41 #include "ShapeContainer.h" 42 #include "SimpleFileSaver.h" 43 #include "SVGExporter.h" 44 #include "SVGImporter.h" 45 46 using std::nothrow; 47 48 // constructor 49 IconEditorApp::IconEditorApp() 50 : BApplication("application/x-vnd.haiku-icon_o_matic"), 51 fMainWindow(NULL), 52 fDocument(new Document("test")), 53 54 fOpenPanel(NULL), 55 fSavePanel(NULL), 56 57 fLastOpenPath(""), 58 fLastSavePath(""), 59 fLastExportPath("") 60 { 61 } 62 63 // destructor 64 IconEditorApp::~IconEditorApp() 65 { 66 // NOTE: it is important that the GUI has been deleted 67 // at this point, so that all the listener/observer 68 // stuff is properly detached 69 delete fDocument; 70 71 delete fOpenPanel; 72 delete fSavePanel; 73 } 74 75 // #pragma mark - 76 77 // QuitRequested 78 bool 79 IconEditorApp::QuitRequested() 80 { 81 // TODO: ask main window if quitting is ok 82 _StoreSettings(); 83 84 fMainWindow->Lock(); 85 fMainWindow->Quit(); 86 fMainWindow = NULL; 87 88 return true; 89 } 90 91 // MessageReceived 92 void 93 IconEditorApp::MessageReceived(BMessage* message) 94 { 95 switch (message->what) { 96 case MSG_NEW: 97 _MakeIconEmpty(); 98 break; 99 case MSG_OPEN: { 100 // fOpenPanel->Refresh(); 101 BMessage openMessage(B_REFS_RECEIVED); 102 fOpenPanel->SetMessage(&openMessage); 103 fOpenPanel->Show(); 104 break; 105 } 106 case MSG_APPEND: { 107 // fOpenPanel->Refresh(); 108 BMessage openMessage(B_REFS_RECEIVED); 109 openMessage.AddBool("append", true); 110 fOpenPanel->SetMessage(&openMessage); 111 fOpenPanel->Show(); 112 break; 113 } 114 case MSG_SAVE: 115 case MSG_EXPORT: { 116 DocumentSaver* saver; 117 if (message->what == MSG_SAVE) 118 saver = fDocument->NativeSaver(); 119 else 120 saver = fDocument->ExportSaver(); 121 if (saver) { 122 saver->Save(fDocument); 123 break; 124 } // else fall through 125 } 126 case MSG_SAVE_AS: 127 case MSG_EXPORT_AS: { 128 int32 exportMode; 129 if (message->FindInt32("export mode", &exportMode) < B_OK) 130 exportMode = EXPORT_MODE_MESSAGE; 131 entry_ref ref; 132 const char* name; 133 if (message->FindRef("directory", &ref) == B_OK 134 && message->FindString("name", &name) == B_OK) { 135 // this message comes from the file panel 136 BDirectory dir(&ref); 137 BEntry entry; 138 if (dir.InitCheck() >= B_OK 139 && entry.SetTo(&dir, name, true) >= B_OK 140 && entry.GetRef(&ref) >= B_OK) { 141 142 // create the document saver and remember it for later 143 DocumentSaver* saver = _CreateSaver(ref, exportMode); 144 if (saver) { 145 if (exportMode == EXPORT_MODE_MESSAGE) 146 fDocument->SetNativeSaver(saver); 147 else 148 fDocument->SetExportSaver(saver); 149 saver->Save(fDocument); 150 } 151 } 152 _SyncPanels(fSavePanel, fOpenPanel); 153 } else { 154 // configure the file panel 155 const char* saveText = NULL; 156 FileSaver* saver = dynamic_cast<FileSaver*>( 157 fDocument->NativeSaver()); 158 159 bool exportMode = message->what == MSG_EXPORT_AS 160 || message->what == MSG_EXPORT; 161 if (exportMode) { 162 saver = dynamic_cast<FileSaver*>( 163 fDocument->ExportSaver()); 164 } 165 166 if (saver) 167 saveText = saver->Ref()->name; 168 169 fSavePanel->SetExportMode(exportMode); 170 // fSavePanel->Refresh(); 171 if (saveText) 172 fSavePanel->SetSaveText(saveText); 173 fSavePanel->Show(); 174 } 175 break; 176 } 177 case B_EDIT_ICON_DATA: { 178 BMessenger messenger; 179 if (message->FindMessenger("reply to", &messenger) < B_OK) { 180 // required 181 break; 182 } 183 const uint8* data; 184 ssize_t size; 185 if (message->FindData("icon data", B_VECTOR_ICON_TYPE, 186 (const void**)&data, &size) < B_OK) { 187 // optional (new icon will be created) 188 data = NULL; 189 size = 0; 190 } 191 _Open(messenger, data, size); 192 break; 193 } 194 195 default: 196 BApplication::MessageReceived(message); 197 break; 198 } 199 } 200 201 // ReadyToRun 202 void 203 IconEditorApp::ReadyToRun() 204 { 205 // create file panels 206 BMessenger messenger(this, this); 207 fOpenPanel = new BFilePanel(B_OPEN_PANEL, 208 &messenger, 209 NULL, 210 B_FILE_NODE, 211 true, 212 new BMessage(B_REFS_RECEIVED)); 213 214 fSavePanel = new SavePanel("save panel", 215 &messenger, 216 NULL, 217 B_FILE_NODE 218 | B_DIRECTORY_NODE 219 | B_SYMLINK_NODE, 220 false, 221 new BMessage(MSG_SAVE_AS)); 222 223 // create main window 224 fMainWindow = new MainWindow(this, fDocument); 225 226 _RestoreSettings(); 227 228 fMainWindow->Show(); 229 } 230 231 // RefsReceived 232 void 233 IconEditorApp::RefsReceived(BMessage* message) 234 { 235 // TODO: multiple documents (iterate over refs) 236 bool append; 237 if (message->FindBool("append", &append) < B_OK) 238 append = false; 239 entry_ref ref; 240 if (message->FindRef("refs", &ref) == B_OK) 241 _Open(ref, append); 242 243 if (fOpenPanel && fSavePanel) 244 _SyncPanels(fOpenPanel, fSavePanel); 245 } 246 247 // ArgvReceived 248 void 249 IconEditorApp::ArgvReceived(int32 argc, char** argv) 250 { 251 if (argc < 2) 252 return; 253 254 // TODO: multiple documents (iterate over argv) 255 entry_ref ref; 256 if (get_ref_for_path(argv[1], &ref) == B_OK) 257 _Open(ref); 258 } 259 260 // #pragma mark - 261 262 void 263 IconEditorApp::_MakeIconEmpty() 264 { 265 bool mainWindowLocked = fMainWindow && fMainWindow->Lock(); 266 267 AutoWriteLocker locker(fDocument); 268 269 if (fMainWindow) 270 fMainWindow->MakeEmpty(); 271 272 fDocument->MakeEmpty(); 273 274 locker.Unlock(); 275 276 if (mainWindowLocked) 277 fMainWindow->Unlock(); 278 } 279 280 // _Open 281 void 282 IconEditorApp::_Open(const entry_ref& ref, bool append) 283 { 284 BFile file(&ref, B_READ_ONLY); 285 if (file.InitCheck() < B_OK) 286 return; 287 288 Icon* icon; 289 if (append) 290 icon = new (nothrow) Icon(*fDocument->Icon()); 291 else 292 icon = new (nothrow) Icon(); 293 294 if (!icon) 295 return; 296 297 enum { 298 REF_NONE = 0, 299 REF_MESSAGE, 300 REF_FLAT, 301 REF_SVG 302 }; 303 uint32 refMode = REF_NONE; 304 305 // try different file types 306 FlatIconImporter flatImporter; 307 status_t ret = flatImporter.Import(icon, &file); 308 if (ret >= B_OK) { 309 refMode = REF_FLAT; 310 } else { 311 file.Seek(0, SEEK_SET); 312 MessageImporter msgImporter; 313 ret = msgImporter.Import(icon, &file); 314 if (ret >= B_OK) { 315 refMode = REF_MESSAGE; 316 } else { 317 file.Seek(0, SEEK_SET); 318 SVGImporter svgImporter; 319 ret = svgImporter.Import(icon, &ref); 320 if (ret >= B_OK) 321 refMode = REF_SVG; 322 } 323 } 324 325 if (ret < B_OK) { 326 // inform user of failure at this point 327 BString helper("Opening the document failed!"); 328 helper << "\n\n" << "Error: " << strerror(ret); 329 BAlert* alert = new BAlert("bad news", helper.String(), 330 "Bummer", NULL, NULL); 331 // launch alert asynchronously 332 alert->Go(NULL); 333 334 delete icon; 335 return; 336 } 337 338 // keep the mainwindow locked while switching icons 339 bool mainWindowLocked = fMainWindow && fMainWindow->Lock(); 340 341 AutoWriteLocker locker(fDocument); 342 343 if (mainWindowLocked) 344 fMainWindow->SetIcon(NULL); 345 346 // incorporate the loaded icon into the document 347 // (either replace it or append to it) 348 fDocument->MakeEmpty(!append); 349 // if append, the document savers are preserved 350 fDocument->SetIcon(icon); 351 if (!append) { 352 // document got replaced, but we have at 353 // least one ref already 354 switch (refMode) { 355 case REF_MESSAGE: 356 fDocument->SetNativeSaver( 357 new SimpleFileSaver(new MessageExporter(), ref)); 358 break; 359 case REF_FLAT: 360 fDocument->SetExportSaver( 361 new SimpleFileSaver(new FlatIconExporter(), ref)); 362 break; 363 case REF_SVG: 364 fDocument->SetExportSaver( 365 new SimpleFileSaver(new SVGExporter(), ref)); 366 break; 367 } 368 } 369 370 locker.Unlock(); 371 372 if (mainWindowLocked) { 373 fMainWindow->Unlock(); 374 // cause the mainwindow to adopt icon in 375 // it's own thread 376 fMainWindow->PostMessage(MSG_SET_ICON); 377 } 378 } 379 380 // _Open 381 void 382 IconEditorApp::_Open(const BMessenger& externalObserver, 383 const uint8* data, size_t size) 384 { 385 if (!externalObserver.IsValid()) 386 return; 387 388 Icon* icon = new (nothrow) Icon(); 389 if (!icon) 390 return; 391 392 if (data && size > 0) { 393 // try to open the icon from the provided data 394 FlatIconImporter flatImporter; 395 status_t ret = flatImporter.Import(icon, const_cast<uint8*>(data), size); 396 // NOTE: the const_cast is a bit ugly, but no harm is done 397 // the reason is that the LittleEndianBuffer knows read and write 398 // mode, in this case it is used read-only, and it does not assume 399 // ownership of the buffer 400 401 if (ret < B_OK) { 402 // inform user of failure at this point 403 BString helper("Opening the icon failed!"); 404 helper << "\n\n" << "Error: " << strerror(ret); 405 BAlert* alert = new BAlert("bad news", helper.String(), 406 "Bummer", NULL, NULL); 407 // launch alert asynchronously 408 alert->Go(NULL); 409 410 delete icon; 411 return; 412 } 413 } 414 415 // keep the mainwindow locked while switching icons 416 bool mainWindowLocked = fMainWindow && fMainWindow->Lock(); 417 418 AutoWriteLocker locker(fDocument); 419 420 if (mainWindowLocked) 421 fMainWindow->SetIcon(NULL); 422 423 // incorporate the loaded icon into the document 424 // (either replace it or append to it) 425 fDocument->MakeEmpty(); 426 fDocument->SetIcon(icon); 427 428 fDocument->SetNativeSaver(new MessengerSaver(externalObserver)); 429 430 locker.Unlock(); 431 432 if (mainWindowLocked) { 433 fMainWindow->Unlock(); 434 // cause the mainwindow to adopt icon in 435 // it's own thread 436 fMainWindow->PostMessage(MSG_SET_ICON); 437 } 438 } 439 440 // _CreateSaver 441 DocumentSaver* 442 IconEditorApp::_CreateSaver(const entry_ref& ref, uint32 exportMode) 443 { 444 DocumentSaver* saver; 445 446 switch (exportMode) { 447 case EXPORT_MODE_FLAT_ICON: 448 saver = new SimpleFileSaver(new FlatIconExporter(), ref); 449 break; 450 451 case EXPORT_MODE_ICON_ATTR: 452 case EXPORT_MODE_ICON_MIME_ATTR: { 453 const char* attrName 454 = exportMode == EXPORT_MODE_ICON_ATTR ? 455 kVectorAttrNodeName : kVectorAttrMimeName; 456 saver = new AttributeSaver(ref, attrName); 457 break; 458 } 459 460 case EXPORT_MODE_ICON_RDEF: 461 saver = new SimpleFileSaver(new RDefExporter(), ref); 462 break; 463 464 case EXPORT_MODE_BITMAP: 465 saver = new SimpleFileSaver(new BitmapExporter(64), ref); 466 break; 467 468 case EXPORT_MODE_BITMAP_SET: 469 saver = new BitmapSetSaver(ref); 470 break; 471 472 case EXPORT_MODE_SVG: 473 saver = new SimpleFileSaver(new SVGExporter(), ref); 474 break; 475 476 case EXPORT_MODE_MESSAGE: 477 default: 478 saver = new SimpleFileSaver(new MessageExporter(), ref); 479 break; 480 } 481 482 return saver; 483 } 484 485 // _SyncPanels 486 void 487 IconEditorApp::_SyncPanels(BFilePanel* from, BFilePanel* to) 488 { 489 if (from->Window()->Lock()) { 490 // location 491 if (to->Window()->Lock()) { 492 BRect frame = from->Window()->Frame(); 493 to->Window()->MoveTo(frame.left, frame.top); 494 to->Window()->ResizeTo(frame.Width(), frame.Height()); 495 to->Window()->Unlock(); 496 } 497 // current folder 498 entry_ref panelDir; 499 from->GetPanelDirectory(&panelDir); 500 to->SetPanelDirectory(&panelDir); 501 from->Window()->Unlock(); 502 } 503 } 504 505 // _LastFilePath 506 const char* 507 IconEditorApp::_LastFilePath(path_kind which) 508 { 509 const char* path = NULL; 510 511 switch (which) { 512 case LAST_PATH_OPEN: 513 if (fLastOpenPath.Length() > 0) 514 path = fLastOpenPath.String(); 515 else if (fLastSavePath.Length() > 0) 516 path = fLastSavePath.String(); 517 else if (fLastExportPath.Length() > 0) 518 path = fLastExportPath.String(); 519 break; 520 case LAST_PATH_SAVE: 521 if (fLastSavePath.Length() > 0) 522 path = fLastSavePath.String(); 523 else if (fLastExportPath.Length() > 0) 524 path = fLastExportPath.String(); 525 else if (fLastOpenPath.Length() > 0) 526 path = fLastOpenPath.String(); 527 break; 528 case LAST_PATH_EXPORT: 529 if (fLastExportPath.Length() > 0) 530 path = fLastExportPath.String(); 531 else if (fLastSavePath.Length() > 0) 532 path = fLastSavePath.String(); 533 else if (fLastOpenPath.Length() > 0) 534 path = fLastOpenPath.String(); 535 break; 536 } 537 if (!path) 538 path = "/boot/home"; 539 540 return path; 541 } 542 543 // #pragma mark - 544 545 // _StoreSettings 546 void 547 IconEditorApp::_StoreSettings() 548 { 549 BMessage settings('stns'); 550 551 fMainWindow->StoreSettings(&settings); 552 553 if (settings.ReplaceInt32("export mode", fSavePanel->ExportMode()) < B_OK) 554 settings.AddInt32("export mode", fSavePanel->ExportMode()); 555 556 save_settings(&settings, "Icon-O-Matic"); 557 } 558 559 // _RestoreSettings 560 void 561 IconEditorApp::_RestoreSettings() 562 { 563 BMessage settings('stns'); 564 load_settings(&settings, "Icon-O-Matic"); 565 566 int32 mode; 567 if (settings.FindInt32("export mode", &mode) >= B_OK) 568 fSavePanel->SetExportMode(mode); 569 570 fMainWindow->RestoreSettings(&settings); 571 } 572 573