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