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