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