1 /* 2 * Copyright 2006, 2011, Stephan Aßmus <superstippi@gmx.de>. 3 * Copyright 2023 Haiku, Inc. All rights reserved. 4 * Distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * Zardshard 8 */ 9 10 11 #include "IconEditorApp.h" 12 13 #include <new> 14 #include <stdio.h> 15 #include <string.h> 16 17 #include <Alert.h> 18 #include <Catalog.h> 19 #include <FilePanel.h> 20 #include <FindDirectory.h> 21 #include <IconEditorProtocol.h> 22 #include <Locale.h> 23 #include <Message.h> 24 #include <Mime.h> 25 #include <Path.h> 26 27 #include "support_settings.h" 28 29 #include "AutoLocker.h" 30 #include "Defines.h" 31 #include "MainWindow.h" 32 #include "SavePanel.h" 33 #include "ShapeListView.h" 34 35 36 #undef B_TRANSLATION_CONTEXT 37 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-Main" 38 39 40 using std::nothrow; 41 42 static const char* kAppSig = "application/x-vnd.haiku-icon_o_matic"; 43 44 static const float kWindowOffset = 20; 45 46 47 IconEditorApp::IconEditorApp() 48 : 49 BApplication(kAppSig), 50 fWindowCount(0), 51 fLastWindowFrame(50, 50, 900, 750), 52 53 fOpenPanel(NULL), 54 fSavePanel(NULL), 55 56 fLastOpenPath(""), 57 fLastSavePath(""), 58 fLastExportPath("") 59 { 60 // create file panels 61 BMessenger messenger(this, this); 62 BMessage message(B_REFS_RECEIVED); 63 fOpenPanel = new BFilePanel(B_OPEN_PANEL, &messenger, NULL, B_FILE_NODE, 64 true, &message); 65 66 message.what = MSG_SAVE_AS; 67 fSavePanel = new SavePanel("save panel", &messenger, NULL, B_FILE_NODE 68 | B_DIRECTORY_NODE | B_SYMLINK_NODE, false, &message); 69 70 _RestoreSettings(); 71 } 72 73 74 IconEditorApp::~IconEditorApp() 75 { 76 delete fOpenPanel; 77 delete fSavePanel; 78 } 79 80 81 // #pragma mark - 82 83 84 bool 85 IconEditorApp::QuitRequested() 86 { 87 // Run the QuitRequested() hook in each window's own thread. Otherwise 88 // the BAlert which a window shows when an icon is not saved will not 89 // repaint the window. (BAlerts check which thread is running Go() and 90 // will repaint windows when it's a BWindow.) 91 bool quit = true; 92 for (int32 i = 0; BWindow* window = WindowAt(i); i++) { 93 if (!window->Lock()) 94 continue; 95 // Try to cast the window while the pointer must be valid. 96 MainWindow* mainWindow = dynamic_cast<MainWindow*>(window); 97 window->Unlock(); 98 if (mainWindow == NULL) 99 continue; 100 BMessenger messenger(window, window); 101 BMessage reply; 102 if (messenger.SendMessage(B_QUIT_REQUESTED, &reply) != B_OK) 103 continue; 104 bool result; 105 if (reply.FindBool("result", &result) == B_OK && !result) 106 quit = false; 107 } 108 109 if (!quit) 110 return false; 111 112 _StoreSettings(); 113 114 return true; 115 } 116 117 118 void 119 IconEditorApp::MessageReceived(BMessage* message) 120 { 121 switch (message->what) { 122 case MSG_NEW: 123 _NewWindow()->Show(); 124 break; 125 case MSG_OPEN: 126 { 127 BMessage openMessage(B_REFS_RECEIVED); 128 MainWindow* window; 129 if (message->FindPointer("window", (void**)&window) == B_OK) 130 openMessage.AddPointer("window", window); 131 bool referenceImage; 132 if (message->FindBool("reference image", &referenceImage) == B_OK) 133 openMessage.AddBool("reference image", referenceImage); 134 fOpenPanel->SetMessage(&openMessage); 135 fOpenPanel->Show(); 136 break; 137 } 138 case MSG_APPEND: 139 { 140 MainWindow* window; 141 if (message->FindPointer("window", (void**)&window) != B_OK) 142 break; 143 BMessage openMessage(B_REFS_RECEIVED); 144 openMessage.AddBool("append", true); 145 openMessage.AddPointer("window", window); 146 fOpenPanel->SetMessage(&openMessage); 147 fOpenPanel->Show(); 148 break; 149 } 150 case B_EDIT_ICON_DATA: 151 { 152 BMessenger messenger; 153 if (message->FindMessenger("reply to", &messenger) < B_OK) { 154 // required 155 break; 156 } 157 const uint8* data; 158 ssize_t size; 159 if (message->FindData("icon data", B_VECTOR_ICON_TYPE, 160 (const void**)&data, &size) < B_OK) { 161 // optional (new icon will be created) 162 data = NULL; 163 size = 0; 164 } 165 MainWindow* window = _NewWindow(); 166 window->Open(messenger, data, size); 167 window->Show(); 168 break; 169 } 170 case MSG_SAVE_AS: 171 case MSG_EXPORT_AS: 172 { 173 BMessenger messenger; 174 if (message->FindMessenger("target", &messenger) != B_OK) 175 break; 176 177 fSavePanel->SetExportMode(message->what == MSG_EXPORT_AS); 178 // fSavePanel->Refresh(); 179 const char* saveText; 180 if (message->FindString("save text", &saveText) == B_OK) 181 fSavePanel->SetSaveText(saveText); 182 entry_ref saveDirectory; 183 if (message->FindRef("save directory", &saveDirectory) == B_OK) 184 fSavePanel->SetPanelDirectory(&saveDirectory); 185 fSavePanel->SetTarget(messenger); 186 fSavePanel->Show(); 187 break; 188 } 189 190 case MSG_WINDOW_CLOSED: 191 { 192 fWindowCount--; 193 if (fWindowCount == 0) 194 PostMessage(B_QUIT_REQUESTED); 195 BMessage settings; 196 if (message->FindMessage("settings", &settings) == B_OK) 197 fLastWindowSettings = settings; 198 BRect frame; 199 if (message->FindRect("window frame", &frame) == B_OK) { 200 fLastWindowFrame = frame; 201 fLastWindowFrame.OffsetBy(-kWindowOffset, -kWindowOffset); 202 } 203 break; 204 } 205 206 default: 207 BApplication::MessageReceived(message); 208 break; 209 } 210 } 211 212 213 void 214 IconEditorApp::ReadyToRun() 215 { 216 // create main window 217 if (fWindowCount == 0) 218 _NewWindow()->Show(); 219 220 _InstallDocumentMimeType(); 221 } 222 223 224 void 225 IconEditorApp::RefsReceived(BMessage* message) 226 { 227 bool append; 228 if (message->FindBool("append", &append) != B_OK) 229 append = false; 230 bool referenceImage; 231 if (message->FindBool("reference image", &referenceImage) != B_OK) 232 referenceImage = false; 233 MainWindow* window; 234 if (message->FindPointer("window", (void**)&window) != B_OK) 235 window = NULL; 236 // When appending, we need to know a window. 237 if (append && window == NULL) 238 return; 239 entry_ref ref; 240 if (append || referenceImage) { 241 if (!window->Lock()) 242 return; 243 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { 244 if (append) 245 window->Open(ref, true); 246 if (referenceImage) 247 window->AddReferenceImage(ref); 248 } 249 window->Unlock(); 250 } else { 251 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { 252 if (window != NULL && i == 0) { 253 window->Lock(); 254 window->Open(ref, false); 255 window->Unlock(); 256 } else { 257 window = _NewWindow(); 258 window->Open(ref, false); 259 window->Show(); 260 } 261 } 262 } 263 264 if (fOpenPanel != NULL && fSavePanel != NULL) 265 _SyncPanels(fOpenPanel, fSavePanel); 266 } 267 268 269 void 270 IconEditorApp::ArgvReceived(int32 argc, char** argv) 271 { 272 if (argc < 2) 273 return; 274 275 entry_ref ref; 276 277 for (int32 i = 1; i < argc; i++) { 278 if (get_ref_for_path(argv[i], &ref) == B_OK) { 279 MainWindow* window = _NewWindow(); 280 window->Open(ref); 281 window->Show(); 282 } 283 } 284 } 285 286 287 // #pragma mark - 288 289 290 MainWindow* 291 IconEditorApp::_NewWindow() 292 { 293 fLastWindowFrame.OffsetBy(kWindowOffset, kWindowOffset); 294 MainWindow* window = new MainWindow(fLastWindowFrame, this, 295 &fLastWindowSettings); 296 fWindowCount++; 297 298 window->MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN); 299 300 return window; 301 } 302 303 304 void 305 IconEditorApp::_SyncPanels(BFilePanel* from, BFilePanel* to) 306 { 307 if (from->Window()->Lock()) { 308 // location 309 if (to->Window()->Lock()) { 310 BRect frame = from->Window()->Frame(); 311 to->Window()->MoveTo(frame.left, frame.top); 312 to->Window()->ResizeTo(frame.Width(), frame.Height()); 313 to->Window()->Unlock(); 314 } 315 // current folder 316 entry_ref panelDir; 317 from->GetPanelDirectory(&panelDir); 318 to->SetPanelDirectory(&panelDir); 319 from->Window()->Unlock(); 320 } 321 } 322 323 324 const char* 325 IconEditorApp::_LastFilePath(path_kind which) 326 { 327 const char* path = NULL; 328 329 switch (which) { 330 case LAST_PATH_OPEN: 331 if (fLastOpenPath.Length() > 0) 332 path = fLastOpenPath.String(); 333 else if (fLastSavePath.Length() > 0) 334 path = fLastSavePath.String(); 335 else if (fLastExportPath.Length() > 0) 336 path = fLastExportPath.String(); 337 break; 338 case LAST_PATH_SAVE: 339 if (fLastSavePath.Length() > 0) 340 path = fLastSavePath.String(); 341 else if (fLastExportPath.Length() > 0) 342 path = fLastExportPath.String(); 343 else if (fLastOpenPath.Length() > 0) 344 path = fLastOpenPath.String(); 345 break; 346 case LAST_PATH_EXPORT: 347 if (fLastExportPath.Length() > 0) 348 path = fLastExportPath.String(); 349 else if (fLastSavePath.Length() > 0) 350 path = fLastSavePath.String(); 351 else if (fLastOpenPath.Length() > 0) 352 path = fLastOpenPath.String(); 353 break; 354 } 355 if (path == NULL) { 356 357 BPath homePath; 358 if (find_directory(B_USER_DIRECTORY, &homePath) == B_OK) 359 path = homePath.Path(); 360 else 361 path = "/boot/home"; 362 } 363 364 return path; 365 } 366 367 368 // #pragma mark - 369 370 371 void 372 IconEditorApp::_StoreSettings() 373 { 374 BMessage settings('stns'); 375 376 settings.AddRect("window frame", fLastWindowFrame); 377 settings.AddMessage("window settings", &fLastWindowSettings); 378 settings.AddInt32("export mode", fSavePanel->ExportMode()); 379 380 save_settings(&settings, "Icon-O-Matic"); 381 } 382 383 384 void 385 IconEditorApp::_RestoreSettings() 386 { 387 BMessage settings('stns'); 388 load_settings(&settings, "Icon-O-Matic"); 389 390 BRect frame; 391 if (settings.FindRect("window frame", &frame) == B_OK) { 392 fLastWindowFrame = frame; 393 // Compensate offset for next window... 394 fLastWindowFrame.OffsetBy(-kWindowOffset, -kWindowOffset); 395 } 396 BMessage lastSettings; 397 if (settings.FindMessage("window settings", &lastSettings) 398 == B_OK) { 399 fLastWindowSettings = lastSettings; 400 } 401 402 int32 mode; 403 if (settings.FindInt32("export mode", &mode) >= B_OK) 404 fSavePanel->SetExportMode(mode); 405 } 406 407 408 void 409 IconEditorApp::_InstallDocumentMimeType() 410 { 411 // install mime type of documents 412 BMimeType mime(kNativeIconMimeType); 413 status_t ret = mime.InitCheck(); 414 if (ret < B_OK) { 415 fprintf(stderr, "Could not init native document mime type (%s): %s.\n", 416 kNativeIconMimeType, strerror(ret)); 417 return; 418 } 419 420 if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) { 421 // mime is already installed, and the user is not 422 // pressing the shift key to force a re-install 423 return; 424 } 425 426 ret = mime.Install(); 427 if (ret < B_OK) { 428 fprintf(stderr, "Could not install native document mime type (%s): " 429 "%s.\n", kNativeIconMimeType, strerror(ret)); 430 return; 431 } 432 // set preferred app 433 ret = mime.SetPreferredApp(kAppSig); 434 if (ret < B_OK) 435 fprintf(stderr, "Could not set native document preferred app: %s\n", 436 strerror(ret)); 437 438 // set descriptions 439 ret = mime.SetShortDescription("Haiku Icon"); 440 if (ret < B_OK) 441 fprintf(stderr, "Could not set short description of mime type: %s\n", 442 strerror(ret)); 443 ret = mime.SetLongDescription("Native Haiku vector icon"); 444 if (ret < B_OK) 445 fprintf(stderr, "Could not set long description of mime type: %s\n", 446 strerror(ret)); 447 448 // set extensions 449 BMessage message('extn'); 450 message.AddString("extensions", "icon"); 451 ret = mime.SetFileExtensions(&message); 452 if (ret < B_OK) 453 fprintf(stderr, "Could not set extensions of mime type: %s\n", 454 strerror(ret)); 455 456 // set sniffer rule 457 const char* snifferRule = "0.9 ('IMSG')"; 458 ret = mime.SetSnifferRule(snifferRule); 459 if (ret < B_OK) { 460 BString parseError; 461 BMimeType::CheckSnifferRule(snifferRule, &parseError); 462 fprintf(stderr, "Could not set sniffer rule of mime type: %s\n", 463 parseError.String()); 464 } 465 } 466 467