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 <MessageRunner.h>
12
13
14// TODO: lazy type collecting (super types only at startup)
15
16
17const uint32 kMsgAddType = 'adtp';
18
19
20bool
21mimetype_is_application_signature(BMimeType& type)
22{
23	char preferredApp[B_MIME_TYPE_LENGTH];
24
25	// The preferred application of an application is the same
26	// as its signature.
27
28	return type.GetPreferredApp(preferredApp) == B_OK
29		&& !strcasecmp(type.Type(), preferredApp);
30}
31
32
33//	#pragma mark -
34
35
36MimeTypeItem::MimeTypeItem(BMimeType& type, bool showIcon, bool flat)
37	: BStringItem(type.Type(), !flat && !type.IsSupertypeOnly() ? 1 : 0, false),
38	fType(type.Type()),
39	fFlat(flat),
40	fShowIcon(showIcon)
41{
42	_SetTo(type);
43}
44
45
46MimeTypeItem::MimeTypeItem(const char* type, bool showIcon, bool flat)
47	: BStringItem(type, !flat && strchr(type, '/') != NULL ? 1 : 0, false),
48	fType(type),
49	fFlat(flat),
50	fShowIcon(showIcon)
51{
52	BMimeType mimeType(type);
53	_SetTo(mimeType);
54}
55
56
57MimeTypeItem::~MimeTypeItem()
58{
59}
60
61
62void
63MimeTypeItem::DrawItem(BView* owner, BRect frame, bool complete)
64{
65	BFont font;
66
67	if (IsSupertypeOnly()) {
68		owner->GetFont(&font);
69		BFont boldFont(font);
70		boldFont.SetFace(B_BOLD_FACE);
71		owner->SetFont(&boldFont);
72	}
73
74	BRect rect = frame;
75	if (fFlat) {
76		// This is where the latch would be - yet can freely consider this
77		// as an ugly hack
78		rect.left -= 11.0f;
79	}
80
81	if (fShowIcon) {
82		rgb_color highColor = owner->HighColor();
83		rgb_color lowColor = owner->LowColor();
84
85		if (IsSelected() || complete) {
86			if (IsSelected())
87				owner->SetLowColor(tint_color(lowColor, B_DARKEN_2_TINT));
88
89			owner->FillRect(rect, B_SOLID_LOW);
90		}
91
92		BBitmap bitmap(BRect(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1), B_RGBA32);
93		BMimeType mimeType(fType.String());
94		status_t status = icon_for_type(mimeType, bitmap, B_MINI_ICON);
95		if (status < B_OK) {
96			// get default generic/application icon
97			BMimeType genericType(fApplicationMode
98				? B_ELF_APP_MIME_TYPE : B_FILE_MIME_TYPE);
99			status = icon_for_type(genericType, bitmap, B_MINI_ICON);
100		}
101
102		if (status == B_OK) {
103			BPoint point(rect.left + 2.0f,
104				rect.top + (rect.Height() - B_MINI_ICON) / 2.0f);
105
106			owner->SetDrawingMode(B_OP_ALPHA);
107			owner->DrawBitmap(&bitmap, point);
108		}
109
110		owner->SetDrawingMode(B_OP_COPY);
111
112		owner->MovePenTo(rect.left + B_MINI_ICON + 8.0f, frame.top + fBaselineOffset);
113		owner->SetHighColor(0, 0, 0);
114		owner->DrawString(Text());
115
116		owner->SetHighColor(highColor);
117		owner->SetLowColor(lowColor);
118	} else
119		BStringItem::DrawItem(owner, rect, complete);
120
121	if (IsSupertypeOnly())
122		owner->SetFont(&font);
123}
124
125
126void
127MimeTypeItem::Update(BView* owner, const BFont* font)
128{
129	BStringItem::Update(owner, font);
130
131	if (fShowIcon) {
132		SetWidth(Width() + B_MINI_ICON + 2.0f);
133
134		if (Height() < B_MINI_ICON + 4.0f)
135			SetHeight(B_MINI_ICON + 4.0f);
136
137		font_height fontHeight;
138		font->GetHeight(&fontHeight);
139
140		fBaselineOffset = fontHeight.ascent
141			+ (Height() - ceilf(fontHeight.ascent + fontHeight.descent)) / 2.0f;
142	}
143}
144
145
146void
147MimeTypeItem::_SetTo(BMimeType& type)
148{
149	fIsSupertype = type.IsSupertypeOnly();
150
151	if (IsSupertypeOnly()) {
152		// this is a super type
153		fSupertype = type.Type();
154		fDescription = type.Type();
155		return;
156	}
157
158	const char* subType = strchr(type.Type(), '/');
159	fSupertype.SetTo(type.Type(), subType - type.Type());
160	fSubtype.SetTo(subType + 1);
161		// omit the slash
162
163	UpdateText();
164}
165
166
167void
168MimeTypeItem::UpdateText()
169{
170	if (IsSupertypeOnly())
171		return;
172
173	BMimeType type(fType.String());
174
175	char description[B_MIME_TYPE_LENGTH];
176	if (type.GetShortDescription(description) == B_OK)
177		SetText(description);
178	else
179		SetText(Subtype());
180
181	fDescription = Text();
182}
183
184
185void
186MimeTypeItem::AddSubtype()
187{
188	if (fSubtype == Text())
189		return;
190
191	BString text = Description();
192	text.Append(" (");
193	text.Append(fSubtype);
194	text.Append(")");
195
196	SetText(text.String());
197}
198
199
200void
201MimeTypeItem::ShowIcon(bool showIcon)
202{
203	fShowIcon = showIcon;
204}
205
206
207void
208MimeTypeItem::SetApplicationMode(bool applicationMode)
209{
210	fApplicationMode = applicationMode;
211}
212
213
214/*static*/
215int
216MimeTypeItem::Compare(const BListItem* a, const BListItem* b)
217{
218	const MimeTypeItem* typeA = dynamic_cast<const MimeTypeItem*>(a);
219	const MimeTypeItem* typeB = dynamic_cast<const MimeTypeItem*>(b);
220
221	if (typeA != NULL && typeB != NULL) {
222		int compare = strcasecmp(typeA->Supertype(), typeB->Supertype());
223		if (compare != 0)
224			return compare;
225	}
226
227	const BStringItem* stringA = dynamic_cast<const BStringItem*>(a);
228	const BStringItem* stringB = dynamic_cast<const BStringItem*>(b);
229
230	if (stringA != NULL && stringB != NULL)
231		return strcasecmp(stringA->Text(), stringB->Text());
232
233	return (int)(a - b);
234}
235
236
237/*static*/
238int
239MimeTypeItem::CompareLabels(const BListItem* a, const BListItem* b)
240{
241	if (a->OutlineLevel() != b->OutlineLevel())
242		return a->OutlineLevel() - b->OutlineLevel();
243
244	const MimeTypeItem* typeA = dynamic_cast<const MimeTypeItem*>(a);
245	const MimeTypeItem* typeB = dynamic_cast<const MimeTypeItem*>(b);
246
247	if (typeA != NULL && typeB != NULL) {
248		int compare = strcasecmp(typeA->Description(), typeB->Description());
249		if (compare != 0)
250			return compare;
251	}
252
253	const BStringItem* stringA = dynamic_cast<const BStringItem*>(a);
254	const BStringItem* stringB = dynamic_cast<const BStringItem*>(b);
255
256	if (stringA != NULL && stringB != NULL)
257		return strcasecmp(stringA->Text(), stringB->Text());
258
259	return (int)(a - b);
260}
261
262
263//	#pragma mark -
264
265
266MimeTypeListView::MimeTypeListView(const char* name,
267		const char* supertype, bool showIcons, bool applicationMode)
268	: BOutlineListView(name, B_SINGLE_SELECTION_LIST),
269	fSupertype(supertype),
270	fShowIcons(showIcons),
271	fApplicationMode(applicationMode)
272{
273}
274
275
276MimeTypeListView::~MimeTypeListView()
277{
278}
279
280
281void
282MimeTypeListView::_CollectSubtypes(const char* supertype,
283	MimeTypeItem* supertypeItem)
284{
285	BMessage types;
286	if (BMimeType::GetInstalledTypes(supertype, &types) != B_OK)
287		return;
288
289	const char* type;
290	int32 index = 0;
291	while (types.FindString("types", index++, &type) == B_OK) {
292		BMimeType mimeType(type);
293
294		bool isApp = mimetype_is_application_signature(mimeType);
295		if (fApplicationMode ^ isApp)
296			continue;
297
298		MimeTypeItem* typeItem = new MimeTypeItem(mimeType, fShowIcons,
299			supertypeItem == NULL);
300		typeItem->SetApplicationMode(isApp);
301
302		if (supertypeItem != NULL)
303			AddUnder(typeItem, supertypeItem);
304		else
305			AddItem(typeItem);
306	}
307}
308
309
310void
311MimeTypeListView::_CollectTypes()
312{
313	if (fSupertype.Type() != NULL) {
314		// only show MIME types that belong to this supertype
315		_CollectSubtypes(fSupertype.Type(), NULL);
316	} else {
317		BMessage superTypes;
318		if (BMimeType::GetInstalledSupertypes(&superTypes) != B_OK)
319			return;
320
321		const char* supertype;
322		int32 index = 0;
323		while (superTypes.FindString("super_types", index++, &supertype)
324			== B_OK) {
325			MimeTypeItem* supertypeItem = new MimeTypeItem(supertype);
326			AddItem(supertypeItem);
327
328			_CollectSubtypes(supertype, supertypeItem);
329		}
330	}
331
332	_MakeTypesUnique();
333}
334
335
336void
337MimeTypeListView::_MakeTypesUnique(MimeTypeItem* underItem)
338{
339	SortItemsUnder(underItem, underItem != NULL, &MimeTypeItem::Compare);
340
341	bool lastItemSame = false;
342	MimeTypeItem* last = NULL;
343
344	int32 index = 0;
345	uint32 level = 0;
346	if (underItem != NULL) {
347		index = FullListIndexOf(underItem) + 1;
348		level = underItem->OutlineLevel() + 1;
349	}
350
351	for (; index < FullListCountItems(); index++) {
352		MimeTypeItem* item = dynamic_cast<MimeTypeItem*>(FullListItemAt(index));
353		if (item == NULL)
354			continue;
355
356		if (item->OutlineLevel() < level) {
357			// left sub-tree
358			break;
359		}
360
361		item->SetText(item->Description());
362
363		if (last == NULL || MimeTypeItem::CompareLabels(last, item)) {
364			if (lastItemSame) {
365				last->AddSubtype();
366				if (Window())
367					InvalidateItem(IndexOf(last));
368			}
369
370			lastItemSame = false;
371			last = item;
372			continue;
373		}
374
375		lastItemSame = true;
376		last->AddSubtype();
377		if (Window())
378			InvalidateItem(IndexOf(last));
379		last = item;
380	}
381
382	if (lastItemSame) {
383		last->AddSubtype();
384		if (Window())
385			InvalidateItem(IndexOf(last));
386	}
387}
388
389
390void
391MimeTypeListView::_AddNewType(const char* type)
392{
393	MimeTypeItem* item = FindItem(type);
394
395	BMimeType mimeType(type);
396	bool isApp = mimetype_is_application_signature(mimeType);
397	if (fApplicationMode ^ isApp || !mimeType.IsInstalled()) {
398		if (item != NULL) {
399			// type doesn't belong here
400			RemoveItem(item);
401			delete item;
402		}
403		return;
404	}
405
406	if (item != NULL) {
407		// for some reason, the type already exists
408		return;
409	}
410
411	BMimeType superType;
412	MimeTypeItem* superItem = NULL;
413	if (mimeType.GetSupertype(&superType) == B_OK)
414		superItem = FindItem(superType.Type());
415
416	item = new MimeTypeItem(mimeType, fShowIcons, fSupertype.Type() != NULL);
417
418	if (item->IsSupertypeOnly())
419		item->ShowIcon(false);
420	item->SetApplicationMode(isApp);
421
422	if (superItem != NULL) {
423		AddUnder(item, superItem);
424		InvalidateItem(IndexOf(superItem));
425			// the super item is not picked up from the class (ie. bug)
426	} else
427		AddItem(item);
428
429	UpdateItem(item);
430
431	if (!fSelectNewType.ICompare(mimeType.Type())) {
432		SelectItem(item);
433		fSelectNewType = "";
434	}
435}
436
437
438void
439MimeTypeListView::AttachedToWindow()
440{
441	BOutlineListView::AttachedToWindow();
442
443	BMimeType::StartWatching(this);
444	_CollectTypes();
445}
446
447
448void
449MimeTypeListView::DetachedFromWindow()
450{
451	BOutlineListView::DetachedFromWindow();
452	BMimeType::StopWatching(this);
453
454	// free all items, they will be retrieved again in AttachedToWindow()
455
456	for (int32 i = FullListCountItems(); i-- > 0;) {
457		delete FullListItemAt(i);
458	}
459}
460
461
462void
463MimeTypeListView::MessageReceived(BMessage* message)
464{
465	switch (message->what) {
466		case B_META_MIME_CHANGED:
467		{
468			const char* type;
469			int32 which;
470			if (message->FindString("be:type", &type) != B_OK
471				|| message->FindInt32("be:which", &which) != B_OK)
472				break;
473
474			switch (which) {
475				case B_SHORT_DESCRIPTION_CHANGED:
476				{
477					// update label
478
479					MimeTypeItem* item = FindItem(type);
480					if (item != NULL)
481						UpdateItem(item);
482					break;
483				}
484				case B_MIME_TYPE_CREATED:
485				{
486					// delay creation of new item a bit, until the type is fully installed
487
488					BMessage addType(kMsgAddType);
489					addType.AddString("type", type);
490
491					if (BMessageRunner::StartSending(this, &addType, 200000ULL,
492						1) != B_OK) {
493						_AddNewType(type);
494					}
495					break;
496				}
497				case B_MIME_TYPE_DELETED:
498				{
499					// delete item
500					MimeTypeItem* item = FindItem(type);
501					if (item != NULL) {
502						RemoveItem(item);
503						delete item;
504					}
505					break;
506				}
507				case B_PREFERRED_APP_CHANGED:
508				{
509					// try to add or remove this type (changing the preferred
510					// app might change visibility in our list)
511					_AddNewType(type);
512
513					// supposed to fall through
514				}
515				case B_ICON_CHANGED:
516				// TODO: take B_ICON_FOR_TYPE_CHANGED into account, too
517				{
518					MimeTypeItem* item = FindItem(type);
519					if (item != NULL && fShowIcons) {
520						// refresh item
521						InvalidateItem(IndexOf(item));
522					}
523					break;
524				}
525
526				default:
527					break;
528			}
529			break;
530		}
531
532		case kMsgAddType:
533		{
534			const char* type;
535			if (message->FindString("type", &type) == B_OK)
536				_AddNewType(type);
537			break;
538		}
539
540		default:
541			BOutlineListView::MessageReceived(message);
542	}
543}
544
545
546/*!
547	\brief This method makes sure a new MIME type will be selected.
548
549	If it's not in the list yet, it will be selected as soon as it's
550	added.
551*/
552void
553MimeTypeListView::SelectNewType(const char* type)
554{
555	if (SelectType(type))
556		return;
557
558	fSelectNewType = type;
559}
560
561
562bool
563MimeTypeListView::SelectType(const char* type)
564{
565	MimeTypeItem* item = FindItem(type);
566	if (item == NULL)
567		return false;
568
569	SelectItem(item);
570	return true;
571}
572
573
574void
575MimeTypeListView::SelectItem(MimeTypeItem* item)
576{
577	if (item == NULL) {
578		Select(-1);
579		return;
580	}
581
582	// Make sure the item is visible
583
584	BListItem* superItem = item;
585	while ((superItem = Superitem(superItem)) != NULL) {
586		Expand(superItem);
587	}
588
589	// Select it, and make it visible
590
591	int32 index = IndexOf(item);
592	Select(index);
593	ScrollToSelection();
594}
595
596
597MimeTypeItem*
598MimeTypeListView::FindItem(const char* type)
599{
600	if (type == NULL)
601		return NULL;
602
603	for (int32 i = FullListCountItems(); i-- > 0;) {
604		MimeTypeItem* item = dynamic_cast<MimeTypeItem*>(FullListItemAt(i));
605		if (item == NULL)
606			continue;
607
608		if (!strcasecmp(item->Type(), type))
609			return item;
610	}
611
612	return NULL;
613}
614
615
616void
617MimeTypeListView::UpdateItem(MimeTypeItem* item)
618{
619	int32 selected = -1;
620	if (IndexOf(item) == CurrentSelection())
621		selected = CurrentSelection();
622
623	item->UpdateText();
624	_MakeTypesUnique(dynamic_cast<MimeTypeItem*>(Superitem(item)));
625
626	if (selected != -1) {
627		int32 index = IndexOf(item);
628		if (index != selected) {
629			Select(index);
630			ScrollToSelection();
631		}
632	}
633	if (Window())
634		InvalidateItem(IndexOf(item));
635}
636
637
638void
639MimeTypeListView::ShowIcons(bool showIcons)
640{
641	if (showIcons == fShowIcons)
642		return;
643
644	fShowIcons = showIcons;
645
646	if (Window() == NULL)
647		return;
648
649	// update items
650
651	BFont font;
652	GetFont(&font);
653
654	for (int32 i = FullListCountItems(); i-- > 0;) {
655		MimeTypeItem* item = dynamic_cast<MimeTypeItem*>(FullListItemAt(i));
656		if (item == NULL)
657			continue;
658
659		if (!item->IsSupertypeOnly())
660			item->ShowIcon(showIcons);
661
662		item->Update(this, &font);
663	}
664
665	FrameResized(Bounds().Width(), Bounds().Height());
666		// update scroller
667
668	Invalidate();
669}
670
671