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