1160814Ssimon/* 2160814Ssimon * Copyright 2006, Axel D��rfler, axeld@pinc-software.de. All rights reserved. 3160814Ssimon * Distributed under the terms of the MIT License. 4160814Ssimon */ 5160814Ssimon 6160814Ssimon 7160814Ssimon#include "IconView.h" 8160814Ssimon#include "MimeTypeListView.h" 9160814Ssimon 10160814Ssimon#include <Bitmap.h> 11160814Ssimon#include <ControlLook.h> 12160814Ssimon#include <MessageRunner.h> 13160814Ssimon 14160814Ssimon#include <strings.h> 15160814Ssimon 16160814Ssimon 17160814Ssimon// TODO: lazy type collecting (super types only at startup) 18160814Ssimon 19160814Ssimon 20160814Ssimonconst uint32 kMsgAddType = 'adtp'; 21160814Ssimon 22160814Ssimon 23160814Ssimonbool 24160814Ssimonmimetype_is_application_signature(BMimeType& type) 25160814Ssimon{ 26160814Ssimon char preferredApp[B_MIME_TYPE_LENGTH]; 27160814Ssimon 28160814Ssimon // The preferred application of an application is the same 29160814Ssimon // as its signature. 30160814Ssimon 31160814Ssimon return type.GetPreferredApp(preferredApp) == B_OK 32160814Ssimon && !strcasecmp(type.Type(), preferredApp); 33160814Ssimon} 34160814Ssimon 35160814Ssimon 36160814Ssimon// #pragma mark - 37160814Ssimon 38160814Ssimon 39160814SsimonMimeTypeItem::MimeTypeItem(BMimeType& type, bool showIcon, bool flat) 40160814Ssimon : BStringItem(type.Type(), !flat && !type.IsSupertypeOnly() ? 1 : 0, false), 41160814Ssimon fType(type.Type()), 42160814Ssimon fFlat(flat), 43160814Ssimon fShowIcon(showIcon) 44160814Ssimon{ 45160814Ssimon _SetTo(type); 46160814Ssimon} 47160814Ssimon 48160814Ssimon 49160814SsimonMimeTypeItem::MimeTypeItem(const char* type, bool showIcon, bool flat) 50160814Ssimon : BStringItem(type, !flat && strchr(type, '/') != NULL ? 1 : 0, false), 51160814Ssimon fType(type), 52160814Ssimon fFlat(flat), 53160814Ssimon fShowIcon(showIcon) 54160814Ssimon{ 55160814Ssimon BMimeType mimeType(type); 56160814Ssimon _SetTo(mimeType); 57160814Ssimon} 58160814Ssimon 59160814Ssimon 60160814SsimonMimeTypeItem::~MimeTypeItem() 61160814Ssimon{ 62160814Ssimon} 63160814Ssimon 64160814Ssimon 65160814Ssimonvoid 66160814SsimonMimeTypeItem::DrawItem(BView* owner, BRect frame, bool complete) 67279265Sdelphij{ 68279265Sdelphij BFont font; 69279265Sdelphij 70279265Sdelphij if (IsSupertypeOnly()) { 71279265Sdelphij owner->GetFont(&font); 72279265Sdelphij BFont boldFont(font); 73279265Sdelphij boldFont.SetFace(B_BOLD_FACE); 74279265Sdelphij owner->SetFont(&boldFont); 75279265Sdelphij } 76279265Sdelphij 77160814Ssimon BRect rect = frame; 78160814Ssimon if (fFlat) { 79160814Ssimon // This is where the latch would be - yet can freely consider this 80160814Ssimon // as an ugly hack 81160814Ssimon rect.left -= 11.0f; 82160814Ssimon } 83160814Ssimon 84160814Ssimon if (fShowIcon) { 85279265Sdelphij rgb_color lowColor = owner->LowColor(); 86279265Sdelphij 87279265Sdelphij if (IsSelected() || complete) { 88279265Sdelphij if (IsSelected()) 89279265Sdelphij owner->SetLowColor(ui_color(B_LIST_SELECTED_BACKGROUND_COLOR)); 90279265Sdelphij 91279265Sdelphij owner->FillRect(rect, B_SOLID_LOW); 92279265Sdelphij } 93279265Sdelphij 94279265Sdelphij const BRect iconRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_MINI_ICON)); 95279265Sdelphij BBitmap bitmap(iconRect, B_RGBA32); 96279265Sdelphij BMimeType mimeType(fType.String()); 97279265Sdelphij status_t status = icon_for_type(mimeType, bitmap, B_MINI_ICON); 98279265Sdelphij if (status < B_OK) { 99279265Sdelphij // get default generic/application icon 100279265Sdelphij BMimeType genericType(fApplicationMode 101160814Ssimon ? B_ELF_APP_MIME_TYPE : B_FILE_MIME_TYPE); 102160814Ssimon status = icon_for_type(genericType, bitmap, B_MINI_ICON); 103160814Ssimon } 104160814Ssimon 105279265Sdelphij if (status == B_OK) { 106160814Ssimon BPoint point(rect.left + 2.0f, 107279265Sdelphij rect.top + (rect.Height() - iconRect.Height()) / 2.0f); 108160814Ssimon 109160814Ssimon owner->SetDrawingMode(B_OP_ALPHA); 110279265Sdelphij owner->DrawBitmap(&bitmap, point); 111279265Sdelphij } 112279265Sdelphij 113279265Sdelphij owner->SetDrawingMode(B_OP_COPY); 114160814Ssimon 115160814Ssimon owner->MovePenTo(rect.left + iconRect.Width() + 8.0f, frame.top + fBaselineOffset); 116160814Ssimon owner->DrawString(Text()); 117160814Ssimon 118160814Ssimon owner->SetLowColor(lowColor); 119160814Ssimon } else 120160814Ssimon BStringItem::DrawItem(owner, rect, complete); 121279265Sdelphij 122160814Ssimon if (IsSupertypeOnly()) 123160814Ssimon owner->SetFont(&font); 124279265Sdelphij} 125160814Ssimon 126160814Ssimon 127160814Ssimonvoid 128160814SsimonMimeTypeItem::Update(BView* owner, const BFont* font) 129160814Ssimon{ 130160814Ssimon BStringItem::Update(owner, font); 131279265Sdelphij 132279265Sdelphij if (fShowIcon) { 133279265Sdelphij const BSize iconSize = be_control_look->ComposeIconSize(B_MINI_ICON); 134279265Sdelphij SetWidth(Width() + iconSize.Width() + 2.0f); 135279265Sdelphij 136279265Sdelphij if (Height() < (iconSize.Height() + 4.0f)) 137279265Sdelphij SetHeight(iconSize.Height() + 4.0f); 138160814Ssimon 139160814Ssimon font_height fontHeight; 140160814Ssimon font->GetHeight(&fontHeight); 141160814Ssimon 142160814Ssimon fBaselineOffset = fontHeight.ascent 143160814Ssimon + (Height() - ceilf(fontHeight.ascent + fontHeight.descent)) / 2.0f; 144160814Ssimon } 145160814Ssimon} 146160814Ssimon 147160814Ssimon 148160814Ssimonvoid 149160814SsimonMimeTypeItem::_SetTo(BMimeType& type) 150160814Ssimon{ 151160814Ssimon fIsSupertype = type.IsSupertypeOnly(); 152160814Ssimon 153160814Ssimon if (IsSupertypeOnly()) { 154160814Ssimon // this is a super type 155160814Ssimon fSupertype = type.Type(); 156160814Ssimon fDescription = type.Type(); 157160814Ssimon return; 158160814Ssimon } 159160814Ssimon 160160814Ssimon const char* subType = strchr(type.Type(), '/'); 161160814Ssimon fSupertype.SetTo(type.Type(), subType - type.Type()); 162160814Ssimon fSubtype.SetTo(subType + 1); 163160814Ssimon // omit the slash 164160814Ssimon 165160814Ssimon UpdateText(); 166160814Ssimon} 167160814Ssimon 168160814Ssimon 169160814Ssimonvoid 170160814SsimonMimeTypeItem::UpdateText() 171160814Ssimon{ 172160814Ssimon if (IsSupertypeOnly()) 173160814Ssimon return; 174160814Ssimon 175160814Ssimon BMimeType type(fType.String()); 176160814Ssimon 177160814Ssimon char description[B_MIME_TYPE_LENGTH]; 178160814Ssimon if (type.GetShortDescription(description) == B_OK) 179160814Ssimon SetText(description); 180160814Ssimon else 181160814Ssimon SetText(Subtype()); 182160814Ssimon 183160814Ssimon fDescription = Text(); 184160814Ssimon} 185160814Ssimon 186160814Ssimon 187160814Ssimonvoid 188160814SsimonMimeTypeItem::AddSubtype() 189160814Ssimon{ 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