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 fSavePanel->SetTarget(messenger); 183 fSavePanel->Show(); 184 break; 185 } 186 187 case MSG_WINDOW_CLOSED: 188 { 189 fWindowCount--; 190 if (fWindowCount == 0) 191 PostMessage(B_QUIT_REQUESTED); 192 BMessage settings; 193 if (message->FindMessage("settings", &settings) == B_OK) 194 fLastWindowSettings = settings; 195 BRect frame; 196 if (message->FindRect("window frame", &frame) == B_OK) { 197 fLastWindowFrame = frame; 198 fLastWindowFrame.OffsetBy(-kWindowOffset, -kWindowOffset); 199 } 200 break; 201 } 202 203 default: 204 BApplication::MessageReceived(message); 205 break; 206 } 207 } 208 209 210 void 211 IconEditorApp::ReadyToRun() 212 { 213 // create main window 214 if (fWindowCount == 0) 215 _NewWindow()->Show(); 216 217 _InstallDocumentMimeType(); 218 } 219 220 221 void 222 IconEditorApp::RefsReceived(BMessage* message) 223 { 224 bool append; 225 if (message->FindBool("append", &append) != B_OK) 226 append = false; 227 bool referenceImage; 228 if (message->FindBool("reference image", &referenceImage) != B_OK) 229 referenceImage = false; 230 MainWindow* window; 231 if (message->FindPointer("window", (void**)&window) != B_OK) 232 window = NULL; 233 // When appending, we need to know a window. 234 if (append && window == NULL) 235 return; 236 entry_ref ref; 237 if (append || referenceImage) { 238 if (!window->Lock()) 239 return; 240 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { 241 if (append) 242 window->Open(ref, true); 243 if (referenceImage) 244 window->AddReferenceImage(ref); 245 } 246 window->Unlock(); 247 } else { 248 for (int32 i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { 249 if (window != NULL && i == 0) { 250 window->Lock(); 251 window->Open(ref, false); 252 window->Unlock(); 253 } else { 254 window = _NewWindow(); 255 window->Open(ref, false); 256 window->Show(); 257 } 258 } 259 } 260 261 if (fOpenPanel != NULL && fSavePanel != NULL) 262 _SyncPanels(fOpenPanel, fSavePanel); 263 } 264 265 266 void 267 IconEditorApp::ArgvReceived(int32 argc, char** argv) 268 { 269 if (argc < 2) 270 return; 271 272 entry_ref ref; 273 274 for (int32 i = 1; i < argc; i++) { 275 if (get_ref_for_path(argv[i], &ref) == B_OK) { 276 MainWindow* window = _NewWindow(); 277 window->Open(ref); 278 window->Show(); 279 } 280 } 281 } 282 283 284 // #pragma mark - 285 286 287 MainWindow* 288 IconEditorApp::_NewWindow() 289 { 290 fLastWindowFrame.OffsetBy(kWindowOffset, kWindowOffset); 291 MainWindow* window = new MainWindow(fLastWindowFrame, this, 292 &fLastWindowSettings); 293 fWindowCount++; 294 return window; 295 } 296 297 298 void 299 IconEditorApp::_SyncPanels(BFilePanel* from, BFilePanel* to) 300 { 301 if (from->Window()->Lock()) { 302 // location 303 if (to->Window()->Lock()) { 304 BRect frame = from->Window()->Frame(); 305 to->Window()->MoveTo(frame.left, frame.top); 306 to->Window()->ResizeTo(frame.Width(), frame.Height()); 307 to->Window()->Unlock(); 308 } 309 // current folder 310 entry_ref panelDir; 311 from->GetPanelDirectory(&panelDir); 312 to->SetPanelDirectory(&panelDir); 313 from->Window()->Unlock(); 314 } 315 } 316 317 318 const char* 319 IconEditorApp::_LastFilePath(path_kind which) 320 { 321 const char* path = NULL; 322 323 switch (which) { 324 case LAST_PATH_OPEN: 325 if (fLastOpenPath.Length() > 0) 326 path = fLastOpenPath.String(); 327 else if (fLastSavePath.Length() > 0) 328 path = fLastSavePath.String(); 329 else if (fLastExportPath.Length() > 0) 330 path = fLastExportPath.String(); 331 break; 332 case LAST_PATH_SAVE: 333 if (fLastSavePath.Length() > 0) 334 path = fLastSavePath.String(); 335 else if (fLastExportPath.Length() > 0) 336 path = fLastExportPath.String(); 337 else if (fLastOpenPath.Length() > 0) 338 path = fLastOpenPath.String(); 339 break; 340 case LAST_PATH_EXPORT: 341 if (fLastExportPath.Length() > 0) 342 path = fLastExportPath.String(); 343 else if (fLastSavePath.Length() > 0) 344 path = fLastSavePath.String(); 345 else if (fLastOpenPath.Length() > 0) 346 path = fLastOpenPath.String(); 347 break; 348 } 349 if (path == NULL) { 350 351 BPath homePath; 352 if (find_directory(B_USER_DIRECTORY, &homePath) == B_OK) 353 path = homePath.Path(); 354 else 355 path = "/boot/home"; 356 } 357 358 return path; 359 } 360 361 362 // #pragma mark - 363 364 365 void 366 IconEditorApp::_StoreSettings() 367 { 368 BMessage settings('stns'); 369 370 settings.AddRect("window frame", fLastWindowFrame); 371 settings.AddMessage("window settings", &fLastWindowSettings); 372 settings.AddInt32("export mode", fSavePanel->ExportMode()); 373 374 save_settings(&settings, "Icon-O-Matic"); 375 } 376 377 378 void 379 IconEditorApp::_RestoreSettings() 380 { 381 BMessage settings('stns'); 382 load_settings(&settings, "Icon-O-Matic"); 383 384 BRect frame; 385 if (settings.FindRect("window frame", &frame) == B_OK) { 386 fLastWindowFrame = frame; 387 // Compensate offset for next window... 388 fLastWindowFrame.OffsetBy(-kWindowOffset, -kWindowOffset); 389 } 390 BMessage lastSettings; 391 if (settings.FindMessage("window settings", &lastSettings) 392 == B_OK) { 393 fLastWindowSettings = lastSettings; 394 } 395 396 int32 mode; 397 if (settings.FindInt32("export mode", &mode) >= B_OK) 398 fSavePanel->SetExportMode(mode); 399 } 400 401 402 void 403 IconEditorApp::_InstallDocumentMimeType() 404 { 405 // install mime type of documents 406 BMimeType mime(kNativeIconMimeType); 407 status_t ret = mime.InitCheck(); 408 if (ret < B_OK) { 409 fprintf(stderr, "Could not init native document mime type (%s): %s.\n", 410 kNativeIconMimeType, strerror(ret)); 411 return; 412 } 413 414 if (mime.IsInstalled() && !(modifiers() & B_SHIFT_KEY)) { 415 // mime is already installed, and the user is not 416 // pressing the shift key to force a re-install 417 return; 418 } 419 420 ret = mime.Install(); 421 if (ret < B_OK) { 422 fprintf(stderr, "Could not install native document mime type (%s): " 423 "%s.\n", kNativeIconMimeType, strerror(ret)); 424 return; 425 } 426 // set preferred app 427 ret = mime.SetPreferredApp(kAppSig); 428 if (ret < B_OK) 429 fprintf(stderr, "Could not set native document preferred app: %s\n", 430 strerror(ret)); 431 432 // set descriptions 433 ret = mime.SetShortDescription("Haiku Icon"); 434 if (ret < B_OK) 435 fprintf(stderr, "Could not set short description of mime type: %s\n", 436 strerror(ret)); 437 ret = mime.SetLongDescription("Native Haiku vector icon"); 438 if (ret < B_OK) 439 fprintf(stderr, "Could not set long description of mime type: %s\n", 440 strerror(ret)); 441 442 // set extensions 443 BMessage message('extn'); 444 message.AddString("extensions", "icon"); 445 ret = mime.SetFileExtensions(&message); 446 if (ret < B_OK) 447 fprintf(stderr, "Could not set extensions of mime type: %s\n", 448 strerror(ret)); 449 450 // set sniffer rule 451 const char* snifferRule = "0.9 ('IMSG')"; 452 ret = mime.SetSnifferRule(snifferRule); 453 if (ret < B_OK) { 454 BString parseError; 455 BMimeType::CheckSnifferRule(snifferRule, &parseError); 456 fprintf(stderr, "Could not set sniffer rule of mime type: %s\n", 457 parseError.String()); 458 } 459 } 460 461