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 7#include "IconView.h" 8#include "MimeTypeListView.h" 9 10#include <Bitmap.h> 11#include <ControlLook.h> 12#include <MessageRunner.h> 13 14#include <strings.h> 15 16 17// TODO: lazy type collecting (super types only at startup) 18 19 20const uint32 kMsgAddType = 'adtp'; 21 22 23bool 24mimetype_is_application_signature(BMimeType& type) 25{ 26 char preferredApp[B_MIME_TYPE_LENGTH]; 27 28 // The preferred application of an application is the same 29 // as its signature. 30 31 return type.GetPreferredApp(preferredApp) == B_OK 32 && !strcasecmp(type.Type(), preferredApp); 33} 34 35 36// #pragma mark - 37 38 39MimeTypeItem::MimeTypeItem(BMimeType& type, bool showIcon, bool flat) 40 : BStringItem(type.Type(), !flat && !type.IsSupertypeOnly() ? 1 : 0, false), 41 fType(type.Type()), 42 fFlat(flat), 43 fShowIcon(showIcon) 44{ 45 _SetTo(type); 46} 47 48 49MimeTypeItem::MimeTypeItem(const char* type, bool showIcon, bool flat) 50 : BStringItem(type, !flat && strchr(type, '/') != NULL ? 1 : 0, false), 51 fType(type), 52 fFlat(flat), 53 fShowIcon(showIcon) 54{ 55 BMimeType mimeType(type); 56 _SetTo(mimeType); 57} 58 59 60MimeTypeItem::~MimeTypeItem() 61{ 62} 63 64 65void 66MimeTypeItem::DrawItem(BView* owner, BRect frame, bool complete) 67{ 68 BFont font; 69 70 if (IsSupertypeOnly()) { 71 owner->GetFont(&font); 72 BFont boldFont(font); 73 boldFont.SetFace(B_BOLD_FACE); 74 owner->SetFont(&boldFont); 75 } 76 77 BRect rect = frame; 78 if (fFlat) { 79 // This is where the latch would be - yet can freely consider this 80 // as an ugly hack 81 rect.left -= 11.0f; 82 } 83 84 if (fShowIcon) { 85 rgb_color lowColor = owner->LowColor(); 86 87 if (IsSelected() || complete) { 88 if (IsSelected()) 89 owner->SetLowColor(ui_color(B_LIST_SELECTED_BACKGROUND_COLOR)); 90 91 owner->FillRect(rect, B_SOLID_LOW); 92 } 93 94 const BRect iconRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_MINI_ICON)); 95 BBitmap bitmap(iconRect, B_RGBA32); 96 BMimeType mimeType(fType.String()); 97 status_t status = icon_for_type(mimeType, bitmap, B_MINI_ICON); 98 if (status < B_OK) { 99 // get default generic/application icon 100 BMimeType genericType(fApplicationMode 101 ? B_ELF_APP_MIME_TYPE : B_FILE_MIME_TYPE); 102 status = icon_for_type(genericType, bitmap, B_MINI_ICON); 103 } 104 105 if (status == B_OK) { 106 BPoint point(rect.left + 2.0f, 107 rect.top + (rect.Height() - iconRect.Height()) / 2.0f); 108 109 owner->SetDrawingMode(B_OP_ALPHA); 110 owner->DrawBitmap(&bitmap, point); 111 } 112 113 owner->SetDrawingMode(B_OP_COPY); 114 115 owner->MovePenTo(rect.left + iconRect.Width() + 8.0f, frame.top + fBaselineOffset); 116 owner->DrawString(Text()); 117 118 owner->SetLowColor(lowColor); 119 } else 120 BStringItem::DrawItem(owner, rect, complete); 121 122 if (IsSupertypeOnly()) 123 owner->SetFont(&font); 124} 125 126 127void 128MimeTypeItem::Update(BView* owner, const BFont* font) 129{ 130 BStringItem::Update(owner, font); 131 132 if (fShowIcon) { 133 const BSize iconSize = be_control_look->ComposeIconSize(B_MINI_ICON); 134 SetWidth(Width() + iconSize.Width() + 2.0f); 135 136 if (Height() < (iconSize.Height() + 4.0f)) 137 SetHeight(iconSize.Height() + 4.0f); 138 139 font_height fontHeight; 140 font->GetHeight(&fontHeight); 141 142 fBaselineOffset = fontHeight.ascent 143 + (Height() - ceilf(fontHeight.ascent + fontHeight.descent)) / 2.0f; 144 } 145} 146 147 148void 149MimeTypeItem::_SetTo(BMimeType& type) 150{ 151 fIsSupertype = type.IsSupertypeOnly(); 152 153 if (IsSupertypeOnly()) { 154 // this is a super type 155 fSupertype = type.Type(); 156 fDescription = type.Type(); 157 return; 158 } 159 160 const char* subType = strchr(type.Type(), '/'); 161 fSupertype.SetTo(type.Type(), subType - type.Type()); 162 fSubtype.SetTo(subType + 1); 163 // omit the slash 164 165 UpdateText(); 166} 167 168 169void 170MimeTypeItem::UpdateText() 171{ 172 if (IsSupertypeOnly()) 173 return; 174 175 BMimeType type(fType.String()); 176 177 char description[B_MIME_TYPE_LENGTH]; 178 if (type.GetShortDescription(description) == B_OK) 179 SetText(description); 180 else 181 SetText(Subtype()); 182 183 fDescription = Text(); 184} 185 186 187void 188MimeTypeItem::AddSubtype() 189{ 190 if (fSubtype == Text()) 191 return; 192 193 BString text = Description(); 194 text.Append(" ("); 195 text.Append(fSubtype); 196 text.Append(")"); 197 198 SetText(text.String()); 199} 200 201 202void 203MimeTypeItem::ShowIcon(bool showIcon) 204{ 205 fShowIcon = showIcon; 206} 207 208 209void 210MimeTypeItem::SetApplicationMode(bool applicationMode) 211{ 212 fApplicationMode = applicationMode; 213} 214 215 216/*static*/ 217int 218MimeTypeItem::Compare(const BListItem* a, const BListItem* b) 219{ 220 const MimeTypeItem* typeA = dynamic_cast<const MimeTypeItem*>(a); 221 const MimeTypeItem* typeB = dynamic_cast<const MimeTypeItem*>(b); 222 223 if (typeA != NULL && typeB != NULL) { 224 int compare = strcasecmp(typeA->Supertype(), typeB->Supertype()); 225 if (compare != 0) 226 return compare; 227 } 228 229 const BStringItem* stringA = dynamic_cast<const BStringItem*>(a); 230 const BStringItem* stringB = dynamic_cast<const BStringItem*>(b); 231 232 if (stringA != NULL && stringB != NULL) 233 return strcasecmp(stringA->Text(), stringB->Text()); 234 235 return (int)(a - b); 236} 237 238 239/*static*/ 240int 241MimeTypeItem::CompareLabels(const BListItem* a, const BListItem* b) 242{ 243 if (a->OutlineLevel() != b->OutlineLevel()) 244 return a->OutlineLevel() - b->OutlineLevel(); 245 246 const MimeTypeItem* typeA = dynamic_cast<const MimeTypeItem*>(a); 247 const MimeTypeItem* typeB = dynamic_cast<const MimeTypeItem*>(b); 248 249 if (typeA != NULL && typeB != NULL) { 250 int compare = strcasecmp(typeA->Description(), typeB->Description()); 251 if (compare != 0) 252 return compare; 253 } 254 255 const BStringItem* stringA = dynamic_cast<const BStringItem*>(a); 256 const BStringItem* stringB = dynamic_cast<const BStringItem*>(b); 257 258 if (stringA != NULL && stringB != NULL) 259 return strcasecmp(stringA->Text(), stringB->Text()); 260 261 return (int)(a - b); 262} 263 264 265// #pragma mark - 266 267 268MimeTypeListView::MimeTypeListView(const char* name, 269 const char* supertype, bool showIcons, bool applicationMode) 270 : BOutlineListView(name, B_SINGLE_SELECTION_LIST), 271 fSupertype(supertype), 272 fShowIcons(showIcons), 273 fApplicationMode(applicationMode) 274{ 275} 276 277 278MimeTypeListView::~MimeTypeListView() 279{ 280} 281 282 283void 284MimeTypeListView::_CollectSubtypes(const char* supertype, 285 MimeTypeItem* supertypeItem) 286{ 287 BMessage types; 288 if (BMimeType::GetInstalledTypes(supertype, &types) != B_OK) 289 return; 290 291 const char* type; 292 int32 index = 0; 293 while (types.FindString("types", index++, &type) == B_OK) { 294 BMimeType mimeType(type); 295 296 bool isApp = mimetype_is_application_signature(mimeType); 297 if (fApplicationMode ^ isApp) 298 continue; 299 300 MimeTypeItem* typeItem = new MimeTypeItem(mimeType, fShowIcons, 301 supertypeItem == NULL); 302 typeItem->SetApplicationMode(isApp); 303 304 if (supertypeItem != NULL) 305 AddUnder(typeItem, supertypeItem); 306 else 307 AddItem(typeItem); 308 } 309} 310 311 312void 313MimeTypeListView::_CollectTypes() 314{ 315 if (fSupertype.Type() != NULL) { 316 // only show MIME types that belong to this supertype 317 _CollectSubtypes(fSupertype.Type(), NULL); 318 } else { 319 BMessage superTypes; 320 if (BMimeType::GetInstalledSupertypes(&superTypes) != B_OK) 321 return; 322 323 const char* supertype; 324 int32 index = 0; 325 while (superTypes.FindString("super_types", index++, &supertype) 326 == B_OK) { 327 MimeTypeItem* supertypeItem = new MimeTypeItem(supertype); 328 AddItem(supertypeItem); 329 330 _CollectSubtypes(supertype, supertypeItem); 331 } 332 } 333 334 _MakeTypesUnique(); 335} 336 337 338void 339MimeTypeListView::_MakeTypesUnique(MimeTypeItem* underItem) 340{ 341 SortItemsUnder(underItem, underItem != NULL, &MimeTypeItem::Compare); 342 343 bool lastItemSame = false; 344 MimeTypeItem* last = NULL; 345 346 int32 index = 0; 347 uint32 level = 0; 348 if (underItem != NULL) { 349 index = FullListIndexOf(underItem) + 1; 350 level = underItem->OutlineLevel() + 1; 351 } 352 353 for (; index < FullListCountItems(); index++) { 354 MimeTypeItem* item = dynamic_cast<MimeTypeItem*>(FullListItemAt(index)); 355 if (item == NULL) 356 continue; 357 358 if (item->OutlineLevel() < level) { 359 // left sub-tree 360 break; 361 } 362 363 item->SetText(item->Description()); 364 365 if (last == NULL || MimeTypeItem::CompareLabels(last, item)) { 366 if (lastItemSame) { 367 last->AddSubtype(); 368 if (Window()) 369 InvalidateItem(IndexOf(last)); 370 } 371 372 lastItemSame = false; 373 last = item; 374 continue; 375 } 376 377 lastItemSame = true; 378 last->AddSubtype(); 379 if (Window()) 380 InvalidateItem(IndexOf(last)); 381 last = item; 382 } 383 384 if (lastItemSame) { 385 last->AddSubtype(); 386 if (Window()) 387 InvalidateItem(IndexOf(last)); 388 } 389} 390 391 392void 393MimeTypeListView::_AddNewType(const char* type) 394{ 395 MimeTypeItem* item = FindItem(type); 396 397 BMimeType mimeType(type); 398 bool isApp = mimetype_is_application_signature(mimeType); 399 if (fApplicationMode ^ isApp || !mimeType.IsInstalled()) { 400 if (item != NULL) { 401 // type doesn't belong here 402 RemoveItem(item); 403 delete item; 404 } 405 return; 406 } 407 408 if (item != NULL) { 409 // for some reason, the type already exists 410 return; 411 } 412 413 BMimeType superType; 414 MimeTypeItem* superItem = NULL; 415 if (mimeType.GetSupertype(&superType) == B_OK) 416 superItem = FindItem(superType.Type()); 417 418 item = new MimeTypeItem(mimeType, fShowIcons, fSupertype.Type() != NULL); 419 420 if (item->IsSupertypeOnly()) 421 item->ShowIcon(false); 422 item->SetApplicationMode(isApp); 423 424 if (superItem != NULL) { 425 AddUnder(item, superItem); 426 InvalidateItem(IndexOf(superItem)); 427 // the super item is not picked up from the class (ie. bug) 428 } else 429 AddItem(item); 430 431 UpdateItem(item); 432 433 if (!fSelectNewType.ICompare(mimeType.Type())) { 434 SelectItem(item); 435 fSelectNewType = ""; 436 } 437} 438 439 440void 441MimeTypeListView::AttachedToWindow() 442{ 443 BOutlineListView::AttachedToWindow(); 444 445 BMimeType::StartWatching(this); 446 _CollectTypes(); 447} 448 449 450void 451MimeTypeListView::DetachedFromWindow() 452{ 453 BOutlineListView::DetachedFromWindow(); 454 BMimeType::StopWatching(this); 455 456 // free all items, they will be retrieved again in AttachedToWindow() 457 458 for (int32 i = FullListCountItems(); i-- > 0;) { 459 delete FullListItemAt(i); 460 } 461} 462 463 464void 465MimeTypeListView::MessageReceived(BMessage* message) 466{ 467 switch (message->what) { 468 case B_META_MIME_CHANGED: 469 { 470 const char* type; 471 int32 which; 472 if (message->FindString("be:type", &type) != B_OK 473 || message->FindInt32("be:which", &which) != B_OK) 474 break; 475 476 switch (which) { 477 case B_SHORT_DESCRIPTION_CHANGED: 478 { 479 // update label 480 481 MimeTypeItem* item = FindItem(type); 482 if (item != NULL) 483 UpdateItem(item); 484 break; 485 } 486 case B_MIME_TYPE_CREATED: 487 { 488 // delay creation of new item a bit, until the type is fully installed 489 490 BMessage addType(kMsgAddType); 491 addType.AddString("type", type); 492 493 if (BMessageRunner::StartSending(this, &addType, 200000ULL, 494 1) != B_OK) { 495 _AddNewType(type); 496 } 497 break; 498 } 499 case B_MIME_TYPE_DELETED: 500 { 501 // delete item 502 MimeTypeItem* item = FindItem(type); 503 if (item != NULL) { 504 RemoveItem(item); 505 delete item; 506 } 507 break; 508 } 509 case B_PREFERRED_APP_CHANGED: 510 { 511 // try to add or remove this type (changing the preferred 512 // app might change visibility in our list) 513 _AddNewType(type); 514 515 // supposed to fall through 516 } 517 case B_ICON_CHANGED: 518 // TODO: take B_ICON_FOR_TYPE_CHANGED into account, too 519 { 520 MimeTypeItem* item = FindItem(type); 521 if (item != NULL && fShowIcons) { 522 // refresh item 523 InvalidateItem(IndexOf(item)); 524 } 525 break; 526 } 527 528 default: 529 break; 530 } 531 break; 532 } 533 534 case kMsgAddType: 535 { 536 const char* type; 537 if (message->FindString("type", &type) == B_OK) 538 _AddNewType(type); 539 break; 540 } 541 542 default: 543 BOutlineListView::MessageReceived(message); 544 } 545} 546 547 548/*! 549 \brief This method makes sure a new MIME type will be selected. 550 551 If it's not in the list yet, it will be selected as soon as it's 552 added. 553*/ 554void 555MimeTypeListView::SelectNewType(const char* type) 556{ 557 if (SelectType(type)) 558 return; 559 560 fSelectNewType = type; 561} 562 563 564bool 565MimeTypeListView::SelectType(const char* type) 566{ 567 MimeTypeItem* item = FindItem(type); 568 if (item == NULL) 569 return false; 570 571 SelectItem(item); 572 return true; 573} 574 575 576void 577MimeTypeListView::SelectItem(MimeTypeItem* item) 578{ 579 if (item == NULL) { 580 Select(-1); 581 return; 582 } 583 584 // Make sure the item is visible 585 586 BListItem* superItem = item; 587 while ((superItem = Superitem(superItem)) != NULL) { 588 Expand(superItem); 589 } 590 591 // Select it, and make it visible 592 593 int32 index = IndexOf(item); 594 Select(index); 595 ScrollToSelection(); 596} 597 598 599MimeTypeItem* 600MimeTypeListView::FindItem(const char* type) 601{ 602 if (type == NULL) 603 return NULL; 604 605 for (int32 i = FullListCountItems(); i-- > 0;) { 606 MimeTypeItem* item = dynamic_cast<MimeTypeItem*>(FullListItemAt(i)); 607 if (item == NULL) 608 continue; 609 610 if (!strcasecmp(item->Type(), type)) 611 return item; 612 } 613 614 return NULL; 615} 616 617 618void 619MimeTypeListView::UpdateItem(MimeTypeItem* item) 620{ 621 int32 selected = -1; 622 if (IndexOf(item) == CurrentSelection()) 623 selected = CurrentSelection(); 624 625 item->UpdateText(); 626 _MakeTypesUnique(dynamic_cast<MimeTypeItem*>(Superitem(item))); 627 628 if (selected != -1) { 629 int32 index = IndexOf(item); 630 if (index != selected) { 631 Select(index); 632 ScrollToSelection(); 633 } 634 } 635 if (Window()) 636 InvalidateItem(IndexOf(item)); 637} 638 639 640void 641MimeTypeListView::ShowIcons(bool showIcons) 642{ 643 if (showIcons == fShowIcons) 644 return; 645 646 fShowIcons = showIcons; 647 648 if (Window() == NULL) 649 return; 650 651 // update items 652 653 BFont font; 654 GetFont(&font); 655 656 for (int32 i = FullListCountItems(); i-- > 0;) { 657 MimeTypeItem* item = dynamic_cast<MimeTypeItem*>(FullListItemAt(i)); 658 if (item == NULL) 659 continue; 660 661 if (!item->IsSupertypeOnly()) 662 item->ShowIcon(showIcons); 663 664 item->Update(this, &font); 665 } 666 667 FrameResized(Bounds().Width(), Bounds().Height()); 668 // update scroller 669 670 Invalidate(); 671} 672 673