* 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 <Bitmap.h>
#include <ControlLook.h>
#include <MessageRunner.h>
#include <strings.h>
const uint32 kMsgAddType = 'adtp';
bool
mimetype_is_application_signature(BMimeType& type)
{
char preferredApp[B_MIME_TYPE_LENGTH];
return type.GetPreferredApp(preferredApp) == B_OK
&& !strcasecmp(type.Type(), preferredApp);
}
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) {
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) {
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()) {
fSupertype = type.Type();
fDescription = type.Type();
return;
}
const char* subType = strchr(type.Type(), '/');
fSupertype.SetTo(type.Type(), subType - type.Type());
fSubtype.SetTo(subType + 1);
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;
}
int
MimeTypeItem::Compare(const BListItem* a, const BListItem* b)
{
const MimeTypeItem* typeA = dynamic_cast<const MimeTypeItem*>(a);
const MimeTypeItem* typeB = dynamic_cast<const MimeTypeItem*>(b);
if (typeA != NULL && typeB != NULL) {
int compare = strcasecmp(typeA->Supertype(), typeB->Supertype());
if (compare != 0)
return compare;
}
const BStringItem* stringA = dynamic_cast<const BStringItem*>(a);
const BStringItem* stringB = dynamic_cast<const BStringItem*>(b);
if (stringA != NULL && stringB != NULL)
return strcasecmp(stringA->Text(), stringB->Text());
return (int)(a - b);
}
int
MimeTypeItem::CompareLabels(const BListItem* a, const BListItem* b)
{
if (a->OutlineLevel() != b->OutlineLevel())
return a->OutlineLevel() - b->OutlineLevel();
const MimeTypeItem* typeA = dynamic_cast<const MimeTypeItem*>(a);
const MimeTypeItem* typeB = dynamic_cast<const MimeTypeItem*>(b);
if (typeA != NULL && typeB != NULL) {
int compare = strcasecmp(typeA->Description(), typeB->Description());
if (compare != 0)
return compare;
}
const BStringItem* stringA = dynamic_cast<const BStringItem*>(a);
const BStringItem* stringB = dynamic_cast<const BStringItem*>(b);
if (stringA != NULL && stringB != NULL)
return strcasecmp(stringA->Text(), stringB->Text());
return (int)(a - b);
}
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) {
_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<MimeTypeItem*>(FullListItemAt(index));
if (item == NULL)
continue;
if (item->OutlineLevel() < level) {
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) {
RemoveItem(item);
delete item;
}
return;
}
if (item != NULL) {
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));
} 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);
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:
{
MimeTypeItem* item = FindItem(type);
if (item != NULL)
UpdateItem(item);
break;
}
case B_MIME_TYPE_CREATED:
{
BMessage addType(kMsgAddType);
addType.AddString("type", type);
if (BMessageRunner::StartSending(this, &addType, 200000ULL,
1) != B_OK) {
_AddNewType(type);
}
break;
}
case B_MIME_TYPE_DELETED:
{
MimeTypeItem* item = FindItem(type);
if (item != NULL) {
RemoveItem(item);
delete item;
}
break;
}
case B_PREFERRED_APP_CHANGED:
{
_AddNewType(type);
}
case B_ICON_CHANGED:
{
MimeTypeItem* item = FindItem(type);
if (item != NULL && fShowIcons) {
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;
}
BListItem* superItem = item;
while ((superItem = Superitem(superItem)) != NULL) {
Expand(superItem);
}
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<MimeTypeItem*>(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<MimeTypeItem*>(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;
BFont font;
GetFont(&font);
for (int32 i = FullListCountItems(); i-- > 0;) {
MimeTypeItem* item = dynamic_cast<MimeTypeItem*>(FullListItemAt(i));
if (item == NULL)
continue;
if (!item->IsSupertypeOnly())
item->ShowIcon(showIcons);
item->Update(this, &font);
}
FrameResized(Bounds().Width(), Bounds().Height());
Invalidate();
}