/* * Copyright 2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved. * Distributed under the terms of the MIT License. */ #include "IconView.h" #include "MimeTypeListView.h" #include #include #include #include // TODO: lazy type collecting (super types only at startup) const uint32 kMsgAddType = 'adtp'; bool mimetype_is_application_signature(BMimeType& type) { char preferredApp[B_MIME_TYPE_LENGTH]; // The preferred application of an application is the same // as its signature. return type.GetPreferredApp(preferredApp) == B_OK && !strcasecmp(type.Type(), preferredApp); } // #pragma mark - MimeTypeItem::MimeTypeItem(BMimeType& type, bool showIcon, bool flat) : BStringItem(type.Type(), !flat && !type.IsSupertypeOnly() ? 1 : 0, false), fType(type.Type()), fFlat(flat), fShowIcon(showIcon) { _SetTo(type); } MimeTypeItem::MimeTypeItem(const char* type, bool showIcon, bool flat) : BStringItem(type, !flat && strchr(type, '/') != NULL ? 1 : 0, false), fType(type), fFlat(flat), fShowIcon(showIcon) { BMimeType mimeType(type); _SetTo(mimeType); } MimeTypeItem::~MimeTypeItem() { } void MimeTypeItem::DrawItem(BView* owner, BRect frame, bool complete) { BFont font; if (IsSupertypeOnly()) { owner->GetFont(&font); BFont boldFont(font); boldFont.SetFace(B_BOLD_FACE); owner->SetFont(&boldFont); } BRect rect = frame; if (fFlat) { // This is where the latch would be - yet can freely consider this // as an ugly hack rect.left -= 11.0f; } if (fShowIcon) { rgb_color lowColor = owner->LowColor(); if (IsSelected() || complete) { if (IsSelected()) owner->SetLowColor(ui_color(B_LIST_SELECTED_BACKGROUND_COLOR)); owner->FillRect(rect, B_SOLID_LOW); } const BRect iconRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_MINI_ICON)); BBitmap bitmap(iconRect, B_RGBA32); BMimeType mimeType(fType.String()); status_t status = icon_for_type(mimeType, bitmap, B_MINI_ICON); if (status < B_OK) { // get default generic/application icon BMimeType genericType(fApplicationMode ? B_ELF_APP_MIME_TYPE : B_FILE_MIME_TYPE); status = icon_for_type(genericType, bitmap, B_MINI_ICON); } if (status == B_OK) { BPoint point(rect.left + 2.0f, rect.top + (rect.Height() - iconRect.Height()) / 2.0f); owner->SetDrawingMode(B_OP_ALPHA); owner->DrawBitmap(&bitmap, point); } owner->SetDrawingMode(B_OP_COPY); owner->MovePenTo(rect.left + iconRect.Width() + 8.0f, frame.top + fBaselineOffset); owner->DrawString(Text()); owner->SetLowColor(lowColor); } else BStringItem::DrawItem(owner, rect, complete); if (IsSupertypeOnly()) owner->SetFont(&font); } void MimeTypeItem::Update(BView* owner, const BFont* font) { BStringItem::Update(owner, font); if (fShowIcon) { const BSize iconSize = be_control_look->ComposeIconSize(B_MINI_ICON); SetWidth(Width() + iconSize.Width() + 2.0f); if (Height() < (iconSize.Height() + 4.0f)) SetHeight(iconSize.Height() + 4.0f); font_height fontHeight; font->GetHeight(&fontHeight); fBaselineOffset = fontHeight.ascent + (Height() - ceilf(fontHeight.ascent + fontHeight.descent)) / 2.0f; } } void MimeTypeItem::_SetTo(BMimeType& type) { fIsSupertype = type.IsSupertypeOnly(); if (IsSupertypeOnly()) { // this is a super type fSupertype = type.Type(); fDescription = type.Type(); return; } const char* subType = strchr(type.Type(), '/'); fSupertype.SetTo(type.Type(), subType - type.Type()); fSubtype.SetTo(subType + 1); // omit the slash UpdateText(); } void MimeTypeItem::UpdateText() { if (IsSupertypeOnly()) return; BMimeType type(fType.String()); char description[B_MIME_TYPE_LENGTH]; if (type.GetShortDescription(description) == B_OK) SetText(description); else SetText(Subtype()); fDescription = Text(); } void MimeTypeItem::AddSubtype() { if (fSubtype == Text()) return; BString text = Description(); text.Append(" ("); text.Append(fSubtype); text.Append(")"); SetText(text.String()); } void MimeTypeItem::ShowIcon(bool showIcon) { fShowIcon = showIcon; } void MimeTypeItem::SetApplicationMode(bool applicationMode) { fApplicationMode = applicationMode; } /*static*/ int MimeTypeItem::Compare(const BListItem* a, const BListItem* b) { const MimeTypeItem* typeA = dynamic_cast(a); const MimeTypeItem* typeB = dynamic_cast(b); if (typeA != NULL && typeB != NULL) { int compare = strcasecmp(typeA->Supertype(), typeB->Supertype()); if (compare != 0) return compare; } const BStringItem* stringA = dynamic_cast(a); const BStringItem* stringB = dynamic_cast(b); if (stringA != NULL && stringB != NULL) return strcasecmp(stringA->Text(), stringB->Text()); return (int)(a - b); } /*static*/ int MimeTypeItem::CompareLabels(const BListItem* a, const BListItem* b) { if (a->OutlineLevel() != b->OutlineLevel()) return a->OutlineLevel() - b->OutlineLevel(); const MimeTypeItem* typeA = dynamic_cast(a); const MimeTypeItem* typeB = dynamic_cast(b); if (typeA != NULL && typeB != NULL) { int compare = strcasecmp(typeA->Description(), typeB->Description()); if (compare != 0) return compare; } const BStringItem* stringA = dynamic_cast(a); const BStringItem* stringB = dynamic_cast(b); if (stringA != NULL && stringB != NULL) return strcasecmp(stringA->Text(), stringB->Text()); return (int)(a - b); } // #pragma mark - MimeTypeListView::MimeTypeListView(const char* name, const char* supertype, bool showIcons, bool applicationMode) : BOutlineListView(name, B_SINGLE_SELECTION_LIST), fSupertype(supertype), fShowIcons(showIcons), fApplicationMode(applicationMode) { } MimeTypeListView::~MimeTypeListView() { } void MimeTypeListView::_CollectSubtypes(const char* supertype, MimeTypeItem* supertypeItem) { BMessage types; if (BMimeType::GetInstalledTypes(supertype, &types) != B_OK) return; const char* type; int32 index = 0; while (types.FindString("types", index++, &type) == B_OK) { BMimeType mimeType(type); bool isApp = mimetype_is_application_signature(mimeType); if (fApplicationMode ^ isApp) continue; MimeTypeItem* typeItem = new MimeTypeItem(mimeType, fShowIcons, supertypeItem == NULL); typeItem->SetApplicationMode(isApp); if (supertypeItem != NULL) AddUnder(typeItem, supertypeItem); else AddItem(typeItem); } } void MimeTypeListView::_CollectTypes() { if (fSupertype.Type() != NULL) { // only show MIME types that belong to this supertype _CollectSubtypes(fSupertype.Type(), NULL); } else { BMessage superTypes; if (BMimeType::GetInstalledSupertypes(&superTypes) != B_OK) return; const char* supertype; int32 index = 0; while (superTypes.FindString("super_types", index++, &supertype) == B_OK) { MimeTypeItem* supertypeItem = new MimeTypeItem(supertype); AddItem(supertypeItem); _CollectSubtypes(supertype, supertypeItem); } } _MakeTypesUnique(); } void MimeTypeListView::_MakeTypesUnique(MimeTypeItem* underItem) { SortItemsUnder(underItem, underItem != NULL, &MimeTypeItem::Compare); bool lastItemSame = false; MimeTypeItem* last = NULL; int32 index = 0; uint32 level = 0; if (underItem != NULL) { index = FullListIndexOf(underItem) + 1; level = underItem->OutlineLevel() + 1; } for (; index < FullListCountItems(); index++) { MimeTypeItem* item = dynamic_cast(FullListItemAt(index)); if (item == NULL) continue; if (item->OutlineLevel() < level) { // left sub-tree break; } item->SetText(item->Description()); if (last == NULL || MimeTypeItem::CompareLabels(last, item)) { if (lastItemSame) { last->AddSubtype(); if (Window()) InvalidateItem(IndexOf(last)); } lastItemSame = false; last = item; continue; } lastItemSame = true; last->AddSubtype(); if (Window()) InvalidateItem(IndexOf(last)); last = item; } if (lastItemSame) { last->AddSubtype(); if (Window()) InvalidateItem(IndexOf(last)); } } void MimeTypeListView::_AddNewType(const char* type) { MimeTypeItem* item = FindItem(type); BMimeType mimeType(type); bool isApp = mimetype_is_application_signature(mimeType); if (fApplicationMode ^ isApp || !mimeType.IsInstalled()) { if (item != NULL) { // type doesn't belong here RemoveItem(item); delete item; } return; } if (item != NULL) { // for some reason, the type already exists return; } BMimeType superType; MimeTypeItem* superItem = NULL; if (mimeType.GetSupertype(&superType) == B_OK) superItem = FindItem(superType.Type()); item = new MimeTypeItem(mimeType, fShowIcons, fSupertype.Type() != NULL); if (item->IsSupertypeOnly()) item->ShowIcon(false); item->SetApplicationMode(isApp); if (superItem != NULL) { AddUnder(item, superItem); InvalidateItem(IndexOf(superItem)); // the super item is not picked up from the class (ie. bug) } else AddItem(item); UpdateItem(item); if (!fSelectNewType.ICompare(mimeType.Type())) { SelectItem(item); fSelectNewType = ""; } } void MimeTypeListView::AttachedToWindow() { BOutlineListView::AttachedToWindow(); BMimeType::StartWatching(this); _CollectTypes(); } void MimeTypeListView::DetachedFromWindow() { BOutlineListView::DetachedFromWindow(); BMimeType::StopWatching(this); // free all items, they will be retrieved again in AttachedToWindow() for (int32 i = FullListCountItems(); i-- > 0;) { delete FullListItemAt(i); } } void MimeTypeListView::MessageReceived(BMessage* message) { switch (message->what) { case B_META_MIME_CHANGED: { const char* type; int32 which; if (message->FindString("be:type", &type) != B_OK || message->FindInt32("be:which", &which) != B_OK) break; switch (which) { case B_SHORT_DESCRIPTION_CHANGED: { // update label MimeTypeItem* item = FindItem(type); if (item != NULL) UpdateItem(item); break; } case B_MIME_TYPE_CREATED: { // delay creation of new item a bit, until the type is fully installed BMessage addType(kMsgAddType); addType.AddString("type", type); if (BMessageRunner::StartSending(this, &addType, 200000ULL, 1) != B_OK) { _AddNewType(type); } break; } case B_MIME_TYPE_DELETED: { // delete item MimeTypeItem* item = FindItem(type); if (item != NULL) { RemoveItem(item); delete item; } break; } case B_PREFERRED_APP_CHANGED: { // try to add or remove this type (changing the preferred // app might change visibility in our list) _AddNewType(type); // supposed to fall through } case B_ICON_CHANGED: // TODO: take B_ICON_FOR_TYPE_CHANGED into account, too { MimeTypeItem* item = FindItem(type); if (item != NULL && fShowIcons) { // refresh item InvalidateItem(IndexOf(item)); } break; } default: break; } break; } case kMsgAddType: { const char* type; if (message->FindString("type", &type) == B_OK) _AddNewType(type); break; } default: BOutlineListView::MessageReceived(message); } } /*! \brief This method makes sure a new MIME type will be selected. If it's not in the list yet, it will be selected as soon as it's added. */ void MimeTypeListView::SelectNewType(const char* type) { if (SelectType(type)) return; fSelectNewType = type; } bool MimeTypeListView::SelectType(const char* type) { MimeTypeItem* item = FindItem(type); if (item == NULL) return false; SelectItem(item); return true; } void MimeTypeListView::SelectItem(MimeTypeItem* item) { if (item == NULL) { Select(-1); return; } // Make sure the item is visible BListItem* superItem = item; while ((superItem = Superitem(superItem)) != NULL) { Expand(superItem); } // Select it, and make it visible int32 index = IndexOf(item); Select(index); ScrollToSelection(); } MimeTypeItem* MimeTypeListView::FindItem(const char* type) { if (type == NULL) return NULL; for (int32 i = FullListCountItems(); i-- > 0;) { MimeTypeItem* item = dynamic_cast(FullListItemAt(i)); if (item == NULL) continue; if (!strcasecmp(item->Type(), type)) return item; } return NULL; } void MimeTypeListView::UpdateItem(MimeTypeItem* item) { int32 selected = -1; if (IndexOf(item) == CurrentSelection()) selected = CurrentSelection(); item->UpdateText(); _MakeTypesUnique(dynamic_cast(Superitem(item))); if (selected != -1) { int32 index = IndexOf(item); if (index != selected) { Select(index); ScrollToSelection(); } } if (Window()) InvalidateItem(IndexOf(item)); } void MimeTypeListView::ShowIcons(bool showIcons) { if (showIcons == fShowIcons) return; fShowIcons = showIcons; if (Window() == NULL) return; // update items BFont font; GetFont(&font); for (int32 i = FullListCountItems(); i-- > 0;) { MimeTypeItem* item = dynamic_cast(FullListItemAt(i)); if (item == NULL) continue; if (!item->IsSupertypeOnly()) item->ShowIcon(showIcons); item->Update(this, &font); } FrameResized(Bounds().Width(), Bounds().Height()); // update scroller Invalidate(); }