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(BRect frame) 162 : BWindow(frame, "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 void 299 ApplicationTypesWindow::_RemoveUninstalled() 300 { 301 // Note: this runs in the looper's thread, which isn't that nice 302 303 int32 removed = 0; 304 volatile bool quit = false; 305 306 BWindow* progressWindow = new ProgressWindow("Removing uninstalled application types", 307 fTypeListView->FullListCountItems(), &quit); 308 progressWindow->AddToSubset(this); 309 progressWindow->Show(); 310 311 for (int32 i = fTypeListView->FullListCountItems(); i-- > 0 && !quit;) { 312 MimeTypeItem* item = dynamic_cast<MimeTypeItem*>(fTypeListView->FullListItemAt(i)); 313 progressWindow->PostMessage(B_UPDATE_STATUS_BAR); 314 315 if (item == NULL) 316 continue; 317 318 // search for application on all volumes 319 320 bool found = false; 321 322 BVolumeRoster volumeRoster; 323 BVolume volume; 324 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 325 if (!volume.KnowsQuery()) 326 continue; 327 328 BQuery query; 329 query.PushAttr("BEOS:APP_SIG"); 330 query.PushString(item->Type()); 331 query.PushOp(B_EQ); 332 333 query.SetVolume(&volume); 334 query.Fetch(); 335 336 entry_ref ref; 337 if (query.GetNextRef(&ref) == B_OK) { 338 found = true; 339 break; 340 } 341 } 342 343 if (!found) { 344 BMimeType mimeType(item->Type()); 345 mimeType.Delete(); 346 347 removed++; 348 349 // We're blocking the message loop that received the MIME changes, 350 // so we dequeue all waiting messages from time to time 351 if (removed % 10 == 0) 352 UpdateIfNeeded(); 353 } 354 } 355 356 progressWindow->PostMessage(B_QUIT_REQUESTED); 357 358 char message[512]; 359 snprintf(message, sizeof(message), "%ld Application Type%s could be removed.", 360 removed, removed == 1 ? "" : "s"); 361 error_alert(message, B_OK, B_INFO_ALERT); 362 } 363 364 365 void 366 ApplicationTypesWindow::_SetType(BMimeType* type, int32 forceUpdate) 367 { 368 bool enabled = type != NULL; 369 bool appFound = true; 370 371 // update controls 372 373 if (type != NULL) { 374 if (fCurrentType == *type) { 375 if (!forceUpdate) 376 return; 377 } else 378 forceUpdate = B_EVERYTHING_CHANGED; 379 380 if (&fCurrentType != type) 381 fCurrentType.SetTo(type->Type()); 382 383 fSignatureView->SetText(type->Type()); 384 385 char description[B_MIME_TYPE_LENGTH]; 386 387 if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) { 388 if (type->GetShortDescription(description) != B_OK) 389 description[0] = '\0'; 390 fNameView->SetText(description); 391 } 392 393 entry_ref ref; 394 if ((forceUpdate & B_APP_HINT_CHANGED) != 0 395 && be_roster->FindApp(fCurrentType.Type(), &ref) == B_OK) { 396 // Set launch message 397 BMessenger tracker("application/x-vnd.Be-TRAK"); 398 BMessage* message = new BMessage(B_REFS_RECEIVED); 399 message->AddRef("refs", &ref); 400 401 fLaunchButton->SetMessage(message); 402 fLaunchButton->SetTarget(tracker); 403 404 // Set path 405 BPath path(&ref); 406 path.GetParent(&path); 407 fPathView->SetText(path.Path()); 408 409 // Set "Open In Tracker" message 410 BEntry entry(path.Path()); 411 entry_ref directoryRef; 412 if (entry.GetRef(&directoryRef) == B_OK) { 413 BMessenger tracker("application/x-vnd.Be-TRAK"); 414 message = new BMessage(B_REFS_RECEIVED); 415 message->AddRef("refs", &directoryRef); 416 417 fTrackerButton->SetMessage(message); 418 fTrackerButton->SetTarget(tracker); 419 } else { 420 fTrackerButton->SetMessage(NULL); 421 appFound = false; 422 } 423 } 424 425 if (forceUpdate == B_EVERYTHING_CHANGED) { 426 // update version information 427 428 BFile file(&ref, B_READ_ONLY); 429 if (file.InitCheck() == B_OK) { 430 BAppFileInfo appInfo(&file); 431 version_info versionInfo; 432 if (appInfo.InitCheck() == B_OK 433 && appInfo.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) == B_OK) { 434 char version[256]; 435 snprintf(version, sizeof(version), "%lu.%lu.%lu, %s/%lu", 436 versionInfo.major, versionInfo.middle, versionInfo.minor, 437 variety_to_text(versionInfo.variety), versionInfo.internal); 438 fVersionView->SetText(version); 439 fDescriptionView->SetText(versionInfo.long_info); 440 } else { 441 fVersionView->SetText(NULL); 442 fDescriptionView->SetText(NULL); 443 } 444 } 445 } 446 } else { 447 fNameView->SetText(NULL); 448 fNameView->SetText(NULL); 449 fPathView->SetText(NULL); 450 451 fVersionView->SetText(NULL); 452 fDescriptionView->SetText(NULL); 453 } 454 455 fNameView->SetEnabled(enabled); 456 fSignatureView->SetEnabled(enabled); 457 fPathView->SetEnabled(enabled); 458 459 fVersionView->SetEnabled(enabled); 460 fDescriptionLabel->SetEnabled(enabled); 461 462 fTrackerButton->SetEnabled(enabled && appFound); 463 fLaunchButton->SetEnabled(enabled && appFound); 464 } 465 466 467 void 468 ApplicationTypesWindow::FrameResized(float width, float height) 469 { 470 // This works around a flaw of BTextView 471 fDescriptionView->SetTextRect(fDescriptionView->Bounds()); 472 } 473 474 475 void 476 ApplicationTypesWindow::MessageReceived(BMessage* message) 477 { 478 switch (message->what) { 479 case kMsgTypeSelected: 480 { 481 int32 index; 482 if (message->FindInt32("index", &index) == B_OK) { 483 MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index); 484 if (item != NULL) { 485 BMimeType type(item->Type()); 486 _SetType(&type); 487 } else 488 _SetType(NULL); 489 } 490 break; 491 } 492 493 case kMsgTypeInvoked: 494 { 495 int32 index; 496 if (message->FindInt32("index", &index) == B_OK) { 497 MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index); 498 if (item != NULL) { 499 BMimeType type(item->Type()); 500 entry_ref ref; 501 if (type.GetAppHint(&ref) == B_OK) { 502 BMessage refs(B_REFS_RECEIVED); 503 refs.AddRef("refs", &ref); 504 505 be_app->PostMessage(&refs); 506 } 507 } 508 } 509 break; 510 } 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 if (fCurrentType.Type() == NULL) 525 break; 526 527 if (!strcasecmp(fCurrentType.Type(), type)) { 528 if (which != B_MIME_TYPE_DELETED) 529 _SetType(&fCurrentType, which); 530 else 531 _SetType(NULL); 532 } 533 break; 534 } 535 536 default: 537 BWindow::MessageReceived(message); 538 } 539 } 540 541 542 bool 543 ApplicationTypesWindow::QuitRequested() 544 { 545 be_app->PostMessage(kMsgApplicationTypesWindowClosed); 546 return true; 547 } 548 549 550