1160814Ssimon/*
2160814Ssimon * Copyright 2006, Axel D��rfler, axeld@pinc-software.de. All rights reserved.
3160814Ssimon * Distributed under the terms of the MIT License.
4160814Ssimon */
5160814Ssimon
6160814Ssimon
7160814Ssimon#include "IconView.h"
8160814Ssimon#include "MimeTypeListView.h"
9160814Ssimon
10160814Ssimon#include <Bitmap.h>
11160814Ssimon#include <ControlLook.h>
12160814Ssimon#include <MessageRunner.h>
13160814Ssimon
14160814Ssimon#include <strings.h>
15160814Ssimon
16160814Ssimon
17160814Ssimon// TODO: lazy type collecting (super types only at startup)
18160814Ssimon
19160814Ssimon
20160814Ssimonconst uint32 kMsgAddType = 'adtp';
21160814Ssimon
22160814Ssimon
23160814Ssimonbool
24160814Ssimonmimetype_is_application_signature(BMimeType& type)
25160814Ssimon{
26160814Ssimon	char preferredApp[B_MIME_TYPE_LENGTH];
27160814Ssimon
28160814Ssimon	// The preferred application of an application is the same
29160814Ssimon	// as its signature.
30160814Ssimon
31160814Ssimon	return type.GetPreferredApp(preferredApp) == B_OK
32160814Ssimon		&& !strcasecmp(type.Type(), preferredApp);
33160814Ssimon}
34160814Ssimon
35160814Ssimon
36160814Ssimon//	#pragma mark -
37160814Ssimon
38160814Ssimon
39160814SsimonMimeTypeItem::MimeTypeItem(BMimeType& type, bool showIcon, bool flat)
40160814Ssimon	: BStringItem(type.Type(), !flat && !type.IsSupertypeOnly() ? 1 : 0, false),
41160814Ssimon	fType(type.Type()),
42160814Ssimon	fFlat(flat),
43160814Ssimon	fShowIcon(showIcon)
44160814Ssimon{
45160814Ssimon	_SetTo(type);
46160814Ssimon}
47160814Ssimon
48160814Ssimon
49160814SsimonMimeTypeItem::MimeTypeItem(const char* type, bool showIcon, bool flat)
50160814Ssimon	: BStringItem(type, !flat && strchr(type, '/') != NULL ? 1 : 0, false),
51160814Ssimon	fType(type),
52160814Ssimon	fFlat(flat),
53160814Ssimon	fShowIcon(showIcon)
54160814Ssimon{
55160814Ssimon	BMimeType mimeType(type);
56160814Ssimon	_SetTo(mimeType);
57160814Ssimon}
58160814Ssimon
59160814Ssimon
60160814SsimonMimeTypeItem::~MimeTypeItem()
61160814Ssimon{
62160814Ssimon}
63160814Ssimon
64160814Ssimon
65160814Ssimonvoid
66160814SsimonMimeTypeItem::DrawItem(BView* owner, BRect frame, bool complete)
67279265Sdelphij{
68279265Sdelphij	BFont font;
69279265Sdelphij
70279265Sdelphij	if (IsSupertypeOnly()) {
71279265Sdelphij		owner->GetFont(&font);
72279265Sdelphij		BFont boldFont(font);
73279265Sdelphij		boldFont.SetFace(B_BOLD_FACE);
74279265Sdelphij		owner->SetFont(&boldFont);
75279265Sdelphij	}
76279265Sdelphij
77160814Ssimon	BRect rect = frame;
78160814Ssimon	if (fFlat) {
79160814Ssimon		// This is where the latch would be - yet can freely consider this
80160814Ssimon		// as an ugly hack
81160814Ssimon		rect.left -= 11.0f;
82160814Ssimon	}
83160814Ssimon
84160814Ssimon	if (fShowIcon) {
85279265Sdelphij		rgb_color lowColor = owner->LowColor();
86279265Sdelphij
87279265Sdelphij		if (IsSelected() || complete) {
88279265Sdelphij			if (IsSelected())
89279265Sdelphij				owner->SetLowColor(ui_color(B_LIST_SELECTED_BACKGROUND_COLOR));
90279265Sdelphij
91279265Sdelphij			owner->FillRect(rect, B_SOLID_LOW);
92279265Sdelphij		}
93279265Sdelphij
94279265Sdelphij		const BRect iconRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_MINI_ICON));
95279265Sdelphij		BBitmap bitmap(iconRect, B_RGBA32);
96279265Sdelphij		BMimeType mimeType(fType.String());
97279265Sdelphij		status_t status = icon_for_type(mimeType, bitmap, B_MINI_ICON);
98279265Sdelphij		if (status < B_OK) {
99279265Sdelphij			// get default generic/application icon
100279265Sdelphij			BMimeType genericType(fApplicationMode
101160814Ssimon				? B_ELF_APP_MIME_TYPE : B_FILE_MIME_TYPE);
102160814Ssimon			status = icon_for_type(genericType, bitmap, B_MINI_ICON);
103160814Ssimon		}
104160814Ssimon
105279265Sdelphij		if (status == B_OK) {
106160814Ssimon			BPoint point(rect.left + 2.0f,
107279265Sdelphij				rect.top + (rect.Height() - iconRect.Height()) / 2.0f);
108160814Ssimon
109160814Ssimon			owner->SetDrawingMode(B_OP_ALPHA);
110279265Sdelphij			owner->DrawBitmap(&bitmap, point);
111279265Sdelphij		}
112279265Sdelphij
113279265Sdelphij		owner->SetDrawingMode(B_OP_COPY);
114160814Ssimon
115160814Ssimon		owner->MovePenTo(rect.left + iconRect.Width() + 8.0f, frame.top + fBaselineOffset);
116160814Ssimon		owner->DrawString(Text());
117160814Ssimon
118160814Ssimon		owner->SetLowColor(lowColor);
119160814Ssimon	} else
120160814Ssimon		BStringItem::DrawItem(owner, rect, complete);
121279265Sdelphij
122160814Ssimon	if (IsSupertypeOnly())
123160814Ssimon		owner->SetFont(&font);
124279265Sdelphij}
125160814Ssimon
126160814Ssimon
127160814Ssimonvoid
128160814SsimonMimeTypeItem::Update(BView* owner, const BFont* font)
129160814Ssimon{
130160814Ssimon	BStringItem::Update(owner, font);
131279265Sdelphij
132279265Sdelphij	if (fShowIcon) {
133279265Sdelphij		const BSize iconSize = be_control_look->ComposeIconSize(B_MINI_ICON);
134279265Sdelphij		SetWidth(Width() + iconSize.Width() + 2.0f);
135279265Sdelphij
136279265Sdelphij		if (Height() < (iconSize.Height() + 4.0f))
137279265Sdelphij			SetHeight(iconSize.Height() + 4.0f);
138160814Ssimon
139160814Ssimon		font_height fontHeight;
140160814Ssimon		font->GetHeight(&fontHeight);
141160814Ssimon
142160814Ssimon		fBaselineOffset = fontHeight.ascent
143160814Ssimon			+ (Height() - ceilf(fontHeight.ascent + fontHeight.descent)) / 2.0f;
144160814Ssimon	}
145160814Ssimon}
146160814Ssimon
147160814Ssimon
148160814Ssimonvoid
149160814SsimonMimeTypeItem::_SetTo(BMimeType& type)
150160814Ssimon{
151160814Ssimon	fIsSupertype = type.IsSupertypeOnly();
152160814Ssimon
153160814Ssimon	if (IsSupertypeOnly()) {
154160814Ssimon		// this is a super type
155160814Ssimon		fSupertype = type.Type();
156160814Ssimon		fDescription = type.Type();
157160814Ssimon		return;
158160814Ssimon	}
159160814Ssimon
160160814Ssimon	const char* subType = strchr(type.Type(), '/');
161160814Ssimon	fSupertype.SetTo(type.Type(), subType - type.Type());
162160814Ssimon	fSubtype.SetTo(subType + 1);
163160814Ssimon		// omit the slash
164160814Ssimon
165160814Ssimon	UpdateText();
166160814Ssimon}
167160814Ssimon
168160814Ssimon
169160814Ssimonvoid
170160814SsimonMimeTypeItem::UpdateText()
171160814Ssimon{
172160814Ssimon	if (IsSupertypeOnly())
173160814Ssimon		return;
174160814Ssimon
175160814Ssimon	BMimeType type(fType.String());
176160814Ssimon
177160814Ssimon	char description[B_MIME_TYPE_LENGTH];
178160814Ssimon	if (type.GetShortDescription(description) == B_OK)
179160814Ssimon		SetText(description);
180160814Ssimon	else
181160814Ssimon		SetText(Subtype());
182160814Ssimon
183160814Ssimon	fDescription = Text();
184160814Ssimon}
185160814Ssimon
186160814Ssimon
187160814Ssimonvoid
188160814SsimonMimeTypeItem::AddSubtype()
189160814Ssimon{
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