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 <ControlLook.h>
12#include <MessageRunner.h>
13
14#include <strings.h>
15
16
17// TODO: lazy type collecting (super types only at startup)
18
19
20const uint32 kMsgAddType = 'adtp';
21
22
23bool
24mimetype_is_application_signature(BMimeType& type)
25{
26	char preferredApp[B_MIME_TYPE_LENGTH];
27
28	// The preferred application of an application is the same
29	// as its signature.
30
31	return type.GetPreferredApp(preferredApp) == B_OK
32		&& !strcasecmp(type.Type(), preferredApp);
33}
34
35
36//	#pragma mark -
37
38
39MimeTypeItem::MimeTypeItem(BMimeType& type, bool showIcon, bool flat)
40	: BStringItem(type.Type(), !flat && !type.IsSupertypeOnly() ? 1 : 0, false),
41	fType(type.Type()),
42	fFlat(flat),
43	fShowIcon(showIcon)
44{
45	_SetTo(type);
46}
47
48
49MimeTypeItem::MimeTypeItem(const char* type, bool showIcon, bool flat)
50	: BStringItem(type, !flat && strchr(type, '/') != NULL ? 1 : 0, false),
51	fType(type),
52	fFlat(flat),
53	fShowIcon(showIcon)
54{
55	BMimeType mimeType(type);
56	_SetTo(mimeType);
57}
58
59
60MimeTypeItem::~MimeTypeItem()
61{
62}
63
64
65void
66MimeTypeItem::DrawItem(BView* owner, BRect frame, bool complete)
67{
68	BFont font;
69
70	if (IsSupertypeOnly()) {
71		owner->GetFont(&font);
72		BFont boldFont(font);
73		boldFont.SetFace(B_BOLD_FACE);
74		owner->SetFont(&boldFont);
75	}
76
77	BRect rect = frame;
78	if (fFlat) {
79		// This is where the latch would be - yet can freely consider this
80		// as an ugly hack
81		rect.left -= 11.0f;
82	}
83
84	if (fShowIcon) {
85		rgb_color lowColor = owner->LowColor();
86
87		if (IsSelected() || complete) {
88			if (IsSelected())
89				owner->SetLowColor(ui_color(B_LIST_SELECTED_BACKGROUND_COLOR));
90
91			owner->FillRect(rect, B_SOLID_LOW);
92		}
93
94		const BRect iconRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_MINI_ICON));
95		BBitmap bitmap(iconRect, B_RGBA32);
96		BMimeType mimeType(fType.String());
97		status_t status = icon_for_type(mimeType, bitmap, B_MINI_ICON);
98		if (status < B_OK) {
99			// get default generic/application icon
100			BMimeType genericType(fApplicationMode
101				? B_ELF_APP_MIME_TYPE : B_FILE_MIME_TYPE);
102			status = icon_for_type(genericType, bitmap, B_MINI_ICON);
103		}
104
105		if (status == B_OK) {
106			BPoint point(rect.left + 2.0f,
107				rect.top + (rect.Height() - iconRect.Height()) / 2.0f);
108
109			owner->SetDrawingMode(B_OP_ALPHA);
110			owner->DrawBitmap(&bitmap, point);
111		}
112
113		owner->SetDrawingMode(B_OP_COPY);
114
115		owner->MovePenTo(rect.left + iconRect.Width() + 8.0f, frame.top + fBaselineOffset);
116		owner->DrawString(Text());
117
118		owner->SetLowColor(lowColor);
119	} else
120		BStringItem::DrawItem(owner, rect, complete);
121
122	if (IsSupertypeOnly())
123		owner->SetFont(&font);
124}
125
126
127void
128MimeTypeItem::Update(BView* owner, const BFont* font)
129{
130	BStringItem::Update(owner, font);
131
132	if (fShowIcon) {
133		const BSize iconSize = be_control_look->ComposeIconSize(B_MINI_ICON);
134		SetWidth(Width() + iconSize.Width() + 2.0f);
135
136		if (Height() < (iconSize.Height() + 4.0f))
137			SetHeight(iconSize.Height() + 4.0f);
138
139		font_height fontHeight;
140		font->GetHeight(&fontHeight);
141
142		fBaselineOffset = fontHeight.ascent
143			+ (Height() - ceilf(fontHeight.ascent + fontHeight.descent)) / 2.0f;
144	}
145}
146
147
148void
149MimeTypeItem::_SetTo(BMimeType& type)
150{
151	fIsSupertype = type.IsSupertypeOnly();
152
153	if (IsSupertypeOnly()) {
154		// this is a super type
155		fSupertype = type.Type();
156		fDescription = type.Type();
157		return;
158	}
159
160	const char* subType = strchr(type.Type(), '/');
161	fSupertype.SetTo(type.Type(), subType - type.Type());
162	fSubtype.SetTo(subType + 1);
163		// omit the slash
164
165	UpdateText();
166}
167
168
169void
170MimeTypeItem::UpdateText()
171{
172	if (IsSupertypeOnly())
173		return;
174
175	BMimeType type(fType.String());
176
177	char description[B_MIME_TYPE_LENGTH];
178	if (type.GetShortDescription(description) == B_OK)
179		SetText(description);
180	else
181		SetText(Subtype());
182
183	fDescription = Text();
184}
185
186
187void
188MimeTypeItem::AddSubtype()
189{
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