⛏️ index : haiku.git

/*
 * 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>


// 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<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);
}


/*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<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);
}


//	#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<MimeTypeItem*>(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<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;

	// update items

	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());
		// update scroller

	Invalidate();
}