1 /* 2 * Copyright 2006-2010, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "ApplicationTypesWindow.h" 8 #include "FileTypes.h" 9 #include "FileTypesWindow.h" 10 #include "MimeTypeListView.h" 11 #include "StringView.h" 12 13 #include <AppFileInfo.h> 14 #include <Application.h> 15 #include <Bitmap.h> 16 #include <Box.h> 17 #include <Button.h> 18 #include <Catalog.h> 19 #include <ControlLook.h> 20 #include <LayoutBuilder.h> 21 #include <Locale.h> 22 #include <MenuField.h> 23 #include <MenuItem.h> 24 #include <Mime.h> 25 #include <NodeInfo.h> 26 #include <Path.h> 27 #include <PopUpMenu.h> 28 #include <Query.h> 29 #include <Roster.h> 30 #include <Screen.h> 31 #include <ScrollView.h> 32 #include <StatusBar.h> 33 #include <StringFormat.h> 34 #include <StringView.h> 35 #include <TextView.h> 36 #include <Volume.h> 37 #include <VolumeRoster.h> 38 39 #include <stdio.h> 40 #include <strings.h> 41 42 43 // TODO: think about adopting Tracker's info window style here (pressable path) 44 45 46 #undef B_TRANSLATION_CONTEXT 47 #define B_TRANSLATION_CONTEXT "Application Types Window" 48 49 50 class ProgressWindow : public BWindow { 51 public: 52 ProgressWindow(const char* message, int32 max, 53 volatile bool* signalQuit); 54 virtual ~ProgressWindow(); 55 56 virtual void MessageReceived(BMessage* message); 57 58 private: 59 BStatusBar* fStatusBar; 60 BButton* fAbortButton; 61 volatile bool* fQuitListener; 62 }; 63 64 const uint32 kMsgTypeSelected = 'typs'; 65 const uint32 kMsgTypeInvoked = 'typi'; 66 const uint32 kMsgRemoveUninstalled = 'runs'; 67 const uint32 kMsgEdit = 'edit'; 68 69 70 const char* 71 variety_to_text(uint32 variety) 72 { 73 switch (variety) { 74 case B_DEVELOPMENT_VERSION: 75 return B_TRANSLATE("Development"); 76 case B_ALPHA_VERSION: 77 return B_TRANSLATE("Alpha"); 78 case B_BETA_VERSION: 79 return B_TRANSLATE("Beta"); 80 case B_GAMMA_VERSION: 81 return B_TRANSLATE("Gamma"); 82 case B_GOLDEN_MASTER_VERSION: 83 return B_TRANSLATE("Golden master"); 84 case B_FINAL_VERSION: 85 return B_TRANSLATE("Final"); 86 } 87 88 return "-"; 89 } 90 91 92 // #pragma mark - 93 94 95 ProgressWindow::ProgressWindow(const char* message, 96 int32 max, volatile bool* signalQuit) 97 : 98 BWindow(BRect(0, 0, 300, 200), B_TRANSLATE("Progress"), B_MODAL_WINDOW_LOOK, 99 B_MODAL_SUBSET_WINDOW_FEEL, B_ASYNCHRONOUS_CONTROLS | 100 B_NOT_V_RESIZABLE | B_AUTO_UPDATE_SIZE_LIMITS), 101 fQuitListener(signalQuit) 102 { 103 char count[100]; 104 snprintf(count, sizeof(count), "/%" B_PRId32, max); 105 106 fStatusBar = new BStatusBar("status", message, count); 107 fStatusBar->SetMaxValue(max); 108 fAbortButton = new BButton("abort", B_TRANSLATE("Abort"), 109 new BMessage(B_CANCEL)); 110 111 float padding = be_control_look->DefaultItemSpacing(); 112 BLayoutBuilder::Group<>(this, B_VERTICAL, padding) 113 .SetInsets(padding, padding, padding, padding) 114 .Add(fStatusBar) 115 .Add(fAbortButton); 116 117 // center on screen 118 BScreen screen(this); 119 MoveTo(screen.Frame().left + (screen.Frame().Width() 120 - Bounds().Width()) / 2.0f, 121 screen.Frame().top + (screen.Frame().Height() 122 - Bounds().Height()) / 2.0f); 123 } 124 125 126 ProgressWindow::~ProgressWindow() 127 { 128 } 129 130 131 void 132 ProgressWindow::MessageReceived(BMessage* message) 133 { 134 switch (message->what) { 135 case B_UPDATE_STATUS_BAR: 136 char count[100]; 137 snprintf(count, sizeof(count), "%" B_PRId32, 138 (int32)fStatusBar->CurrentValue() + 1); 139 140 fStatusBar->Update(1, NULL, count); 141 break; 142 143 case B_CANCEL: 144 fAbortButton->SetEnabled(false); 145 if (fQuitListener != NULL) 146 *fQuitListener = true; 147 break; 148 149 default: 150 BWindow::MessageReceived(message); 151 break; 152 } 153 } 154 155 156 // #pragma mark - 157 158 159 ApplicationTypesWindow::ApplicationTypesWindow(const BMessage& settings) 160 : BWindow(_Frame(settings), B_TRANSLATE("Application types"), 161 B_TITLED_WINDOW, 162 B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS) 163 { 164 float padding = be_control_look->DefaultItemSpacing(); 165 BAlignment labelAlignment = be_control_look->DefaultLabelAlignment(); 166 BAlignment fullWidthTopAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_TOP); 167 168 // Application list 169 170 fTypeListView = new MimeTypeListView("listview", "application", true, true); 171 fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected)); 172 fTypeListView->SetInvocationMessage(new BMessage(kMsgTypeInvoked)); 173 174 BScrollView* scrollView = new BScrollView("scrollview", fTypeListView, 175 B_FRAME_EVENTS | B_WILL_DRAW, false, true); 176 177 BButton* button = new BButton("remove", B_TRANSLATE("Remove uninstalled"), 178 new BMessage(kMsgRemoveUninstalled)); 179 180 // "Information" group 181 182 BBox* infoBox = new BBox((char*)NULL); 183 infoBox->SetLabel(B_TRANSLATE("Information")); 184 infoBox->SetExplicitAlignment(fullWidthTopAlignment); 185 186 fNameView = new StringView(B_TRANSLATE("Name:"), NULL); 187 fNameView->TextView()->SetExplicitAlignment(labelAlignment); 188 fNameView->LabelView()->SetExplicitAlignment(labelAlignment); 189 fSignatureView = new StringView(B_TRANSLATE("Signature:"), NULL); 190 fSignatureView->TextView()->SetExplicitAlignment(labelAlignment); 191 fSignatureView->LabelView()->SetExplicitAlignment(labelAlignment); 192 fSignatureView->TextView()->SetExplicitMinSize(BSize( 193 fSignatureView->TextView()->StringWidth("M") * 42, B_SIZE_UNSET)); 194 fPathView = new StringView(B_TRANSLATE("Path:"), NULL); 195 fPathView->TextView()->SetExplicitAlignment(labelAlignment); 196 fPathView->LabelView()->SetExplicitAlignment(labelAlignment); 197 198 BLayoutBuilder::Grid<>(infoBox, padding, padding) 199 .SetInsets(padding, padding * 2, padding, padding) 200 .Add(fNameView->LabelView(), 0, 0) 201 .Add(fNameView->TextView(), 1, 0, 2) 202 .Add(fSignatureView->LabelView(), 0, 1) 203 .Add(fSignatureView->TextView(), 1, 1, 2) 204 .Add(fPathView->LabelView(), 0, 2) 205 .Add(fPathView->TextView(), 1, 2, 2); 206 207 // "Version" group 208 209 BBox* versionBox = new BBox(""); 210 versionBox->SetLabel(B_TRANSLATE("Version")); 211 versionBox->SetExplicitAlignment(fullWidthTopAlignment); 212 213 fVersionView = new StringView(B_TRANSLATE("Version:"), NULL); 214 fVersionView->TextView()->SetExplicitAlignment(labelAlignment); 215 fVersionView->LabelView()->SetExplicitAlignment(labelAlignment); 216 fDescriptionLabel = new StringView(B_TRANSLATE("Description:"), NULL); 217 fDescriptionLabel->LabelView()->SetExplicitAlignment(labelAlignment); 218 fDescriptionView = new BTextView("description"); 219 fDescriptionView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 220 fDescriptionView->SetLowColor(fDescriptionView->ViewColor()); 221 fDescriptionView->MakeEditable(false); 222 223 BLayoutBuilder::Grid<>(versionBox, padding, padding) 224 .SetInsets(padding, padding * 2, padding, padding) 225 .Add(fVersionView->LabelView(), 0, 0) 226 .Add(fVersionView->TextView(), 1, 0) 227 .Add(fDescriptionLabel->LabelView(), 0, 1) 228 .Add(fDescriptionView, 1, 1, 2, 2); 229 230 // Launch and Tracker buttons 231 232 fEditButton = new BButton(B_TRANSLATE("Edit" B_UTF8_ELLIPSIS), 233 new BMessage(kMsgEdit)); 234 // launch and tracker buttons get messages in _SetType() 235 fLaunchButton = new BButton(B_TRANSLATE("Launch")); 236 fTrackerButton = new BButton( 237 B_TRANSLATE("Show in Tracker" B_UTF8_ELLIPSIS)); 238 239 240 BLayoutBuilder::Group<>(this, B_HORIZONTAL, padding) 241 .AddGroup(B_VERTICAL, padding, 3) 242 .Add(scrollView) 243 .AddGroup(B_HORIZONTAL) 244 .Add(button) 245 .AddGlue() 246 .End() 247 .End() 248 .AddGroup(B_VERTICAL, padding) 249 .Add(infoBox) 250 .Add(versionBox) 251 .AddGroup(B_HORIZONTAL, padding) 252 .Add(fEditButton) 253 .Add(fLaunchButton) 254 .Add(fTrackerButton) 255 .AddGlue() 256 .End() 257 .AddGlue(10.0) 258 .End() 259 260 .SetInsets(B_USE_WINDOW_SPACING); 261 262 BMimeType::StartWatching(this); 263 _SetType(NULL); 264 } 265 266 267 ApplicationTypesWindow::~ApplicationTypesWindow() 268 { 269 BMimeType::StopWatching(this); 270 } 271 272 273 BRect 274 ApplicationTypesWindow::_Frame(const BMessage& settings) const 275 { 276 BRect rect; 277 if (settings.FindRect("app_types_frame", &rect) == B_OK) 278 return rect; 279 280 return BRect(100.0f, 100.0f, 540.0f, 480.0f); 281 } 282 283 284 void 285 ApplicationTypesWindow::_RemoveUninstalled() 286 { 287 // Note: this runs in the looper's thread, which isn't that nice 288 289 int32 removed = 0; 290 volatile bool quit = false; 291 292 BWindow* progressWindow = 293 new ProgressWindow( 294 B_TRANSLATE("Removing uninstalled application types"), 295 fTypeListView->FullListCountItems(), &quit); 296 progressWindow->AddToSubset(this); 297 progressWindow->Show(); 298 299 for (int32 i = fTypeListView->FullListCountItems(); i-- > 0 && !quit;) { 300 MimeTypeItem* item = dynamic_cast<MimeTypeItem*> 301 (fTypeListView->FullListItemAt(i)); 302 progressWindow->PostMessage(B_UPDATE_STATUS_BAR); 303 304 if (item == NULL) 305 continue; 306 307 // search for application on all volumes 308 309 bool found = false; 310 311 BVolumeRoster volumeRoster; 312 BVolume volume; 313 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 314 if (!volume.KnowsQuery()) 315 continue; 316 317 BQuery query; 318 query.PushAttr("BEOS:APP_SIG"); 319 query.PushString(item->Type()); 320 query.PushOp(B_EQ); 321 322 query.SetVolume(&volume); 323 query.Fetch(); 324 325 entry_ref ref; 326 if (query.GetNextRef(&ref) == B_OK) { 327 found = true; 328 break; 329 } 330 } 331 332 if (!found) { 333 BMimeType mimeType(item->Type()); 334 mimeType.Delete(); 335 336 removed++; 337 338 // We're blocking the message loop that received the MIME changes, 339 // so we dequeue all waiting messages from time to time 340 if (removed % 10 == 0) 341 UpdateIfNeeded(); 342 } 343 } 344 345 progressWindow->PostMessage(B_QUIT_REQUESTED); 346 347 static BStringFormat format(B_TRANSLATE("{0, plural, " 348 "one{# Application type could be removed} " 349 "other{# Application types could be removed}}")); 350 BString message; 351 format.Format(message, removed); 352 353 error_alert(message, B_OK, B_INFO_ALERT); 354 } 355 356 357 void 358 ApplicationTypesWindow::_SetType(BMimeType* type, int32 forceUpdate) 359 { 360 if (type == NULL) { 361 fCurrentType.Unset(); 362 363 // Information group 364 fNameView->SetText(NULL); 365 fNameView->SetEnabled(false); 366 fSignatureView->SetText(NULL); 367 fSignatureView->SetEnabled(false); 368 fPathView->SetText(NULL); 369 fPathView->SetEnabled(false); 370 371 // Version group 372 fVersionView->SetText(NULL); 373 fVersionView->SetEnabled(false); 374 fDescriptionView->SetText(NULL); 375 fDescriptionLabel->SetEnabled(true); 376 377 // Buttons 378 fEditButton->SetEnabled(false); 379 fLaunchButton->SetMessage(NULL); 380 fLaunchButton->SetEnabled(false); 381 fTrackerButton->SetMessage(NULL); 382 fTrackerButton->SetEnabled(false); 383 384 return; 385 } 386 387 if (fCurrentType == *type) { 388 if (!forceUpdate) 389 return; 390 } else 391 forceUpdate = B_EVERYTHING_CHANGED; 392 393 if (&fCurrentType != type) 394 fCurrentType.SetTo(type->Type()); 395 396 fSignatureView->SetText(type->Type()); 397 fSignatureView->SetEnabled(true); 398 399 if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) { 400 char description[B_MIME_TYPE_LENGTH]; 401 402 if (type->GetShortDescription(description) != B_OK) { 403 fNameView->SetText(""); 404 fNameView->SetEnabled(false); 405 } else { 406 fNameView->SetText(description); 407 fNameView->SetEnabled(true); 408 } 409 } 410 411 if ((forceUpdate & B_APP_HINT_CHANGED) != 0) { 412 bool appInfoFound = false; 413 entry_ref ref; 414 415 if (be_roster->FindApp(fCurrentType.Type(), &ref) == B_OK) { 416 // Set launch message 417 BMessenger tracker("application/x-vnd.Be-TRAK"); 418 BMessage* message = new BMessage(B_REFS_RECEIVED); 419 message->AddRef("refs", &ref); 420 421 fLaunchButton->SetMessage(message); 422 fLaunchButton->SetTarget(tracker); 423 fLaunchButton->SetEnabled(true); 424 425 // update version information 426 427 BFile file(&ref, B_READ_ONLY); 428 if (file.InitCheck() == B_OK) { 429 fEditButton->SetEnabled(true); 430 431 BAppFileInfo appInfo(&file); 432 version_info versionInfo; 433 if (appInfo.InitCheck() == B_OK 434 && appInfo.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) 435 == B_OK) { 436 char version[256]; 437 snprintf(version, sizeof(version), 438 "%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ", %s/%" B_PRIu32, 439 versionInfo.major, versionInfo.middle, 440 versionInfo.minor, 441 variety_to_text(versionInfo.variety), 442 versionInfo.internal); 443 444 fVersionView->SetText(version); 445 fVersionView->SetEnabled(true); 446 fDescriptionView->SetText(versionInfo.long_info); 447 fDescriptionLabel->SetEnabled(true); 448 449 appInfoFound = true; 450 } 451 } else { 452 fEditButton->SetEnabled(false); 453 } 454 } else { 455 fEditButton->SetEnabled(false); 456 fLaunchButton->SetMessage(NULL); 457 fLaunchButton->SetEnabled(false); 458 } 459 460 if (!appInfoFound) { 461 fVersionView->SetText(NULL); 462 fVersionView->SetEnabled(false); 463 fDescriptionView->SetText(NULL); 464 fDescriptionLabel->SetEnabled(false); 465 } 466 467 BPath path(&ref); 468 if (path.InitCheck() == B_OK) { 469 // Set path 470 path.GetParent(&path); 471 fPathView->SetText(path.Path()); 472 fPathView->SetEnabled(true); 473 474 // Set "Show In Tracker" message 475 BEntry entry(path.Path()); 476 entry_ref directoryRef; 477 if (entry.GetRef(&directoryRef) == B_OK) { 478 BMessenger tracker("application/x-vnd.Be-TRAK"); 479 BMessage* message = new BMessage(B_REFS_RECEIVED); 480 message->AddRef("refs", &directoryRef); 481 482 fTrackerButton->SetMessage(message); 483 fTrackerButton->SetTarget(tracker); 484 fTrackerButton->SetEnabled(true); 485 } else { 486 fTrackerButton->SetMessage(NULL); 487 fTrackerButton->SetEnabled(false); 488 } 489 } else { 490 fPathView->SetText(NULL); 491 fPathView->SetEnabled(false); 492 fTrackerButton->SetMessage(NULL); 493 fTrackerButton->SetEnabled(false); 494 } 495 } 496 } 497 498 499 void 500 ApplicationTypesWindow::MessageReceived(BMessage* message) 501 { 502 switch (message->what) { 503 case kMsgTypeSelected: 504 { 505 int32 index; 506 if (message->FindInt32("index", &index) == B_OK) { 507 MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index); 508 if (item != NULL) { 509 BMimeType type(item->Type()); 510 _SetType(&type); 511 } else 512 _SetType(NULL); 513 } 514 break; 515 } 516 517 case kMsgTypeInvoked: 518 { 519 int32 index; 520 if (message->FindInt32("index", &index) == B_OK) { 521 MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index); 522 if (item != NULL) { 523 BMimeType type(item->Type()); 524 entry_ref ref; 525 if (type.GetAppHint(&ref) == B_OK) { 526 BMessage refs(B_REFS_RECEIVED); 527 refs.AddRef("refs", &ref); 528 529 be_app->PostMessage(&refs); 530 } 531 } 532 } 533 break; 534 } 535 536 case kMsgEdit: 537 fTypeListView->Invoke(); 538 break; 539 540 case kMsgRemoveUninstalled: 541 _RemoveUninstalled(); 542 break; 543 544 case B_META_MIME_CHANGED: 545 { 546 const char* type; 547 int32 which; 548 if (message->FindString("be:type", &type) != B_OK 549 || message->FindInt32("be:which", &which) != B_OK) { 550 break; 551 } 552 553 if (fCurrentType.Type() == NULL) 554 break; 555 556 if (!strcasecmp(fCurrentType.Type(), type)) { 557 if (which != B_MIME_TYPE_DELETED) 558 _SetType(&fCurrentType, which); 559 else 560 _SetType(NULL); 561 } 562 break; 563 } 564 565 default: 566 BWindow::MessageReceived(message); 567 } 568 } 569 570 571 bool 572 ApplicationTypesWindow::QuitRequested() 573 { 574 BMessage update(kMsgSettingsChanged); 575 update.AddRect("app_types_frame", Frame()); 576 be_app_messenger.SendMessage(&update); 577 578 be_app->PostMessage(kMsgApplicationTypesWindowClosed); 579 return true; 580 } 581 582 583