1 /* 2 * Copyright 2006-2007, Axel Dörfler, axeld@pinc-software.de. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "ApplicationTypesWindow.h" 8 #include "ApplicationTypeWindow.h" 9 #include "FileTypes.h" 10 #include "FileTypesWindow.h" 11 #include "FileTypeWindow.h" 12 13 #include <AppFileInfo.h> 14 #include <Application.h> 15 #include <Alert.h> 16 #include <Catalog.h> 17 #include <Locale.h> 18 #include <TextView.h> 19 #include <FilePanel.h> 20 #include <FindDirectory.h> 21 #include <Directory.h> 22 #include <Entry.h> 23 #include <Path.h> 24 #include <Resources.h> 25 #include <Screen.h> 26 27 #include <stdio.h> 28 #include <strings.h> 29 30 31 #undef B_TRANSLATION_CONTEXT 32 #define B_TRANSLATION_CONTEXT "FileTypes" 33 34 35 const char* kSignature = "application/x-vnd.Haiku-FileTypes"; 36 37 static const uint32 kMsgFileTypesSettings = 'FTst'; 38 static const uint32 kCascadeOffset = 20; 39 40 41 class Settings { 42 public: 43 Settings(); 44 ~Settings(); 45 46 const BMessage& Message() const { return fMessage; } 47 void UpdateFrom(BMessage* message); 48 49 private: 50 void _SetDefaults(); 51 status_t _Open(BFile* file, int32 mode); 52 53 BMessage fMessage; 54 bool fUpdated; 55 }; 56 57 class FileTypes : public BApplication { 58 public: 59 FileTypes(); 60 virtual ~FileTypes(); 61 62 virtual void ReadyToRun(); 63 64 virtual void RefsReceived(BMessage* message); 65 virtual void ArgvReceived(int32 argc, char** argv); 66 virtual void MessageReceived(BMessage* message); 67 68 virtual bool QuitRequested(); 69 70 private: 71 void _WindowClosed(); 72 73 // Add one degree of offset to the starting position of the next ApplicationTypeWindow 74 void _AppTypeCascade(BRect lastFrame); 75 76 private: 77 Settings fSettings; 78 BFilePanel* fFilePanel; 79 BMessenger fFilePanelTarget; 80 FileTypesWindow* fTypesWindow; 81 BWindow* fApplicationTypesWindow; 82 uint32 fWindowCount; 83 uint32 fTypeWindowCount; 84 BString fArgvType; 85 }; 86 87 88 Settings::Settings() 89 : 90 fMessage(kMsgFileTypesSettings), 91 fUpdated(false) 92 { 93 _SetDefaults(); 94 95 BFile file; 96 if (_Open(&file, B_READ_ONLY) != B_OK) 97 return; 98 99 BMessage settings; 100 if (settings.Unflatten(&file) == B_OK) { 101 // We don't unflatten into our default message to make sure 102 // nothing is lost (because of old or corrupted on disk settings) 103 UpdateFrom(&settings); 104 fUpdated = false; 105 } 106 } 107 108 109 Settings::~Settings() 110 { 111 // only save the settings if something has changed 112 if (!fUpdated) 113 return; 114 115 BFile file; 116 if (_Open(&file, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY) != B_OK) 117 return; 118 119 fMessage.Flatten(&file); 120 } 121 122 123 void 124 Settings::UpdateFrom(BMessage* message) 125 { 126 BRect frame; 127 if (message->FindRect("file_types_frame", &frame) == B_OK) 128 fMessage.ReplaceRect("file_types_frame", frame); 129 130 if (message->FindRect("app_types_frame", &frame) == B_OK) 131 fMessage.ReplaceRect("app_types_frame", frame); 132 133 // "app_type_initial_frame" is omitted because it is not meant to be updated 134 135 if (message->FindRect("app_type_next_frame", &frame) == B_OK) 136 fMessage.ReplaceRect("app_type_next_frame", frame); 137 138 bool showIcons; 139 if (message->FindBool("show_icons", &showIcons) == B_OK) 140 fMessage.ReplaceBool("show_icons", showIcons); 141 142 bool showRule; 143 if (message->FindBool("show_rule", &showRule) == B_OK) 144 fMessage.ReplaceBool("show_rule", showRule); 145 146 float splitWeight; 147 if (message->FindFloat("left_split_weight", &splitWeight) == B_OK) 148 fMessage.ReplaceFloat("left_split_weight", splitWeight); 149 if (message->FindFloat("right_split_weight", &splitWeight) == B_OK) 150 fMessage.ReplaceFloat("right_split_weight", splitWeight); 151 152 fUpdated = true; 153 } 154 155 156 void 157 Settings::_SetDefaults() 158 { 159 fMessage.AddRect("file_types_frame", BRect(80.0f, 80.0f, 600.0f, 480.0f)); 160 fMessage.AddRect("app_types_frame", BRect(100.0f, 100.0f, 540.0f, 480.0f)); 161 fMessage.AddRect("app_type_initial_frame", BRect(100.0f, 110.0f, 250.0f, 340.0f)); 162 fMessage.AddRect("app_type_next_frame", BRect(100.0f, 110.0f, 250.0f, 340.0f)); 163 fMessage.AddBool("show_icons", true); 164 fMessage.AddBool("show_rule", false); 165 fMessage.AddFloat("left_split_weight", 0.2); 166 fMessage.AddFloat("right_split_weight", 0.8); 167 } 168 169 170 status_t 171 Settings::_Open(BFile* file, int32 mode) 172 { 173 BPath path; 174 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 175 return B_ERROR; 176 177 path.Append("FileTypes settings"); 178 179 return file->SetTo(path.Path(), mode); 180 } 181 182 183 // #pragma mark - 184 185 186 FileTypes::FileTypes() 187 : 188 BApplication(kSignature), 189 fTypesWindow(NULL), 190 fApplicationTypesWindow(NULL), 191 fWindowCount(0), 192 fTypeWindowCount(0) 193 { 194 fFilePanel = new BFilePanel(B_OPEN_PANEL, NULL, NULL, 195 B_FILE_NODE, false); 196 } 197 198 199 FileTypes::~FileTypes() 200 { 201 delete fFilePanel; 202 } 203 204 205 void 206 FileTypes::ReadyToRun() 207 { 208 // are there already windows open? 209 if (CountWindows() != 1) 210 return; 211 212 // if not, open the FileTypes window 213 PostMessage(kMsgOpenTypesWindow); 214 } 215 216 217 void 218 FileTypes::RefsReceived(BMessage* message) 219 { 220 bool traverseLinks = (modifiers() & B_SHIFT_KEY) == 0; 221 222 // filter out applications and entries we can't open 223 int32 index = 0; 224 entry_ref ref; 225 while (message->FindRef("refs", index++, &ref) == B_OK) { 226 BEntry entry; 227 BFile file; 228 229 status_t status = entry.SetTo(&ref, traverseLinks); 230 if (status == B_OK) 231 status = file.SetTo(&entry, B_READ_ONLY); 232 233 if (status != B_OK) { 234 // file cannot be opened 235 236 char buffer[1024]; 237 snprintf(buffer, sizeof(buffer), 238 B_TRANSLATE("Could not open \"%s\":\n" 239 "%s"), 240 ref.name, strerror(status)); 241 242 BAlert* alert = new BAlert(B_TRANSLATE("FileTypes request"), 243 buffer, B_TRANSLATE("OK"), NULL, NULL, 244 B_WIDTH_AS_USUAL, B_STOP_ALERT); 245 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 246 alert->Go(); 247 248 message->RemoveData("refs", --index); 249 continue; 250 } 251 252 if (!is_application(file) && !is_resource(file)) { 253 entry_ref target; 254 if (entry.GetRef(&target) == B_OK && target != ref) 255 message->ReplaceRef("refs", index - 1, &ref); 256 continue; 257 } 258 259 // remove application from list 260 message->RemoveData("refs", --index); 261 262 // There are some refs left that want to be handled by the type window 263 264 BWindow* window = new ApplicationTypeWindow(fSettings.Message(), entry); 265 _AppTypeCascade(window->Frame()); 266 // For accurate height and width, get the frame that results after layouting, 267 // instead of the initial frame that's stored in fSettings. 268 window->Show(); 269 270 fTypeWindowCount++; 271 fWindowCount++; 272 } 273 274 if (message->FindRef("refs", &ref) != B_OK) 275 return; 276 277 // There are some refs left that want to be handled by the type window 278 BPoint point(100.0f + kCascadeOffset * fTypeWindowCount, 279 110.0f + kCascadeOffset * fTypeWindowCount); 280 281 BWindow* window = new FileTypeWindow(point, *message); 282 window->Show(); 283 284 fTypeWindowCount++; 285 fWindowCount++; 286 } 287 288 289 void 290 FileTypes::ArgvReceived(int32 argc, char** argv) 291 { 292 if (argc == 3 && strcmp(argv[1], "-type") == 0) { 293 fArgvType = argv[2]; 294 return; 295 } 296 297 BMessage* message = CurrentMessage(); 298 299 BDirectory currentDirectory; 300 if (message != NULL) 301 currentDirectory.SetTo(message->FindString("cwd")); 302 303 BMessage refs; 304 305 for (int i = 1 ; i < argc ; i++) { 306 BPath path; 307 if (argv[i][0] == '/') 308 path.SetTo(argv[i]); 309 else 310 path.SetTo(¤tDirectory, argv[i]); 311 312 status_t status; 313 entry_ref ref; 314 BEntry entry; 315 316 if ((status = entry.SetTo(path.Path(), false)) != B_OK 317 || (status = entry.GetRef(&ref)) != B_OK) { 318 fprintf(stderr, "Could not open file \"%s\": %s\n", 319 path.Path(), strerror(status)); 320 continue; 321 } 322 323 refs.AddRef("refs", &ref); 324 } 325 326 RefsReceived(&refs); 327 } 328 329 330 void 331 FileTypes::MessageReceived(BMessage* message) 332 { 333 switch (message->what) { 334 case kMsgSettingsChanged: 335 fSettings.UpdateFrom(message); 336 break; 337 338 case kMsgOpenTypesWindow: 339 if (fTypesWindow == NULL) { 340 fTypesWindow = new FileTypesWindow(fSettings.Message()); 341 if (fArgvType.Length() > 0) { 342 // Set the window to the type that was requested on the 343 // command line (-type), we do this only once, if we 344 // ever opened more than one FileTypesWindow. 345 fTypesWindow->SelectType(fArgvType.String()); 346 fArgvType = ""; 347 } 348 fTypesWindow->Show(); 349 fWindowCount++; 350 } else 351 fTypesWindow->Activate(true); 352 break; 353 case kMsgTypesWindowClosed: 354 fTypesWindow = NULL; 355 _WindowClosed(); 356 break; 357 358 case kMsgOpenApplicationTypesWindow: 359 if (fApplicationTypesWindow == NULL) { 360 fApplicationTypesWindow = new ApplicationTypesWindow( 361 fSettings.Message()); 362 fApplicationTypesWindow->Show(); 363 fWindowCount++; 364 } else 365 fApplicationTypesWindow->Activate(true); 366 break; 367 case kMsgApplicationTypesWindowClosed: 368 fApplicationTypesWindow = NULL; 369 _WindowClosed(); 370 break; 371 372 case kMsgTypeWindowClosed: 373 fTypeWindowCount--; 374 // supposed to fall through 375 376 case kMsgWindowClosed: 377 _WindowClosed(); 378 break; 379 380 381 case kMsgOpenFilePanel: 382 { 383 // the open file panel sends us a message when it's done 384 const char* subTitle; 385 if (message->FindString("title", &subTitle) != B_OK) 386 subTitle = B_TRANSLATE("Open file"); 387 388 int32 what; 389 if (message->FindInt32("message", &what) != B_OK) 390 what = B_REFS_RECEIVED; 391 392 BMessenger target; 393 if (message->FindMessenger("target", &target) != B_OK) 394 target = be_app_messenger; 395 396 BString title = B_TRANSLATE("FileTypes"); 397 if (subTitle != NULL && subTitle[0]) { 398 title.Append(": "); 399 title.Append(subTitle); 400 } 401 402 uint32 flavors = B_FILE_NODE; 403 if (message->FindBool("allowDirs")) 404 flavors |= B_DIRECTORY_NODE; 405 fFilePanel->SetNodeFlavors(flavors); 406 407 408 fFilePanel->SetMessage(new BMessage(what)); 409 fFilePanel->Window()->SetTitle(title.String()); 410 fFilePanel->SetTarget(target); 411 412 if (!fFilePanel->IsShowing()) 413 fFilePanel->Show(); 414 break; 415 } 416 417 case B_SILENT_RELAUNCH: 418 // In case we were launched via the add-on, there is no types 419 // window yet. 420 if (fTypesWindow == NULL) 421 PostMessage(kMsgOpenTypesWindow); 422 break; 423 424 case B_CANCEL: 425 if (fWindowCount == 0) 426 PostMessage(B_QUIT_REQUESTED); 427 break; 428 429 case B_SIMPLE_DATA: 430 RefsReceived(message); 431 break; 432 433 default: 434 BApplication::MessageReceived(message); 435 break; 436 } 437 } 438 439 440 bool 441 FileTypes::QuitRequested() 442 { 443 return true; 444 } 445 446 447 void 448 FileTypes::_WindowClosed() 449 { 450 if (--fWindowCount == 0 && !fFilePanel->IsShowing()) 451 PostMessage(B_QUIT_REQUESTED); 452 } 453 454 455 void 456 FileTypes::_AppTypeCascade(BRect lastFrame) 457 { 458 BScreen screen; 459 BRect screenBorder = screen.Frame(); 460 BRect initFrame; 461 462 float left = lastFrame.left + kCascadeOffset; 463 if (left + lastFrame.Width() > screenBorder.right) { 464 // If about to cascade off the right edge of the screen, revert the horizontal 465 // position to that of the first window. 466 if (fSettings.Message().FindRect("app_type_initial_frame", &initFrame) == B_OK) 467 left = initFrame.LeftTop().x; 468 } 469 470 float top = lastFrame.top + kCascadeOffset; 471 if (top + lastFrame.Height() > screenBorder.bottom) { 472 if (fSettings.Message().FindRect("app_type_initial_frame", &initFrame) == B_OK) 473 top = initFrame.LeftTop().y; 474 } 475 476 lastFrame.OffsetTo(BPoint(left, top)); 477 BMessage update(kMsgSettingsChanged); 478 update.AddRect("app_type_next_frame", lastFrame); 479 fSettings.UpdateFrom(&update); 480 } 481 482 483 // #pragma mark - 484 485 486 bool 487 is_application(BFile& file) 488 { 489 BAppFileInfo appInfo(&file); 490 if (appInfo.InitCheck() != B_OK) 491 return false; 492 493 char type[B_MIME_TYPE_LENGTH]; 494 if (appInfo.GetType(type) != B_OK 495 || strcasecmp(type, B_APP_MIME_TYPE)) 496 return false; 497 498 return true; 499 } 500 501 502 bool 503 is_resource(BFile& file) 504 { 505 BResources resources(&file); 506 if (resources.InitCheck() != B_OK) 507 return false; 508 509 BNodeInfo nodeInfo(&file); 510 char type[B_MIME_TYPE_LENGTH]; 511 if (nodeInfo.GetType(type) != B_OK 512 || strcasecmp(type, B_RESOURCE_MIME_TYPE)) 513 return false; 514 515 return true; 516 } 517 518 519 void 520 error_alert(const char* message, status_t status, alert_type type) 521 { 522 char warning[512]; 523 if (status != B_OK) { 524 snprintf(warning, sizeof(warning), "%s:\n\t%s\n", message, 525 strerror(status)); 526 } 527 528 BAlert* alert = new BAlert(B_TRANSLATE("FileTypes request"), 529 status == B_OK ? message : warning, 530 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, type); 531 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 532 alert->Go(); 533 } 534 535 536 int 537 main(int argc, char** argv) 538 { 539 FileTypes probe; 540 probe.Run(); 541 return 0; 542 } 543