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