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