xref: /haiku/src/preferences/filetypes/MimeTypeListView.cpp (revision 2710b4f5d4251c5cf88c82b0114ea99b0ef46d22)
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 
20 const uint32 kMsgAddType = 'adtp';
21 
22 
23 bool
mimetype_is_application_signature(BMimeType & type)24 mimetype_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 
MimeTypeItem(BMimeType & type,bool showIcon,bool flat)39 MimeTypeItem::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 
MimeTypeItem(const char * type,bool showIcon,bool flat)49 MimeTypeItem::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 
~MimeTypeItem()60 MimeTypeItem::~MimeTypeItem()
61 {
62 }
63 
64 
65 void
DrawItem(BView * owner,BRect frame,bool complete)66 MimeTypeItem::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 
127 void
Update(BView * owner,const BFont * font)128 MimeTypeItem::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 
148 void
_SetTo(BMimeType & type)149 MimeTypeItem::_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 
169 void
UpdateText()170 MimeTypeItem::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 
187 void
AddSubtype()188 MimeTypeItem::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 
202 void
ShowIcon(bool showIcon)203 MimeTypeItem::ShowIcon(bool showIcon)
204 {
205 	fShowIcon = showIcon;
206 }
207 
208 
209 void
SetApplicationMode(bool applicationMode)210 MimeTypeItem::SetApplicationMode(bool applicationMode)
211 {
212 	fApplicationMode = applicationMode;
213 }
214 
215 
216 /*static*/
217 int
Compare(const BListItem * a,const BListItem * b)218 MimeTypeItem::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*/
240 int
CompareLabels(const BListItem * a,const BListItem * b)241 MimeTypeItem::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 
MimeTypeListView(const char * name,const char * supertype,bool showIcons,bool applicationMode)268 MimeTypeListView::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 
~MimeTypeListView()278 MimeTypeListView::~MimeTypeListView()
279 {
280 }
281 
282 
283 void
_CollectSubtypes(const char * supertype,MimeTypeItem * supertypeItem)284 MimeTypeListView::_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 
312 void
_CollectTypes()313 MimeTypeListView::_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 
338 void
_MakeTypesUnique(MimeTypeItem * underItem)339 MimeTypeListView::_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 
392 void
_AddNewType(const char * type)393 MimeTypeListView::_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 
440 void
AttachedToWindow()441 MimeTypeListView::AttachedToWindow()
442 {
443 	BOutlineListView::AttachedToWindow();
444 
445 	BMimeType::StartWatching(this);
446 	_CollectTypes();
447 }
448 
449 
450 void
DetachedFromWindow()451 MimeTypeListView::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 
464 void
MessageReceived(BMessage * message)465 MimeTypeListView::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 */
554 void
SelectNewType(const char * type)555 MimeTypeListView::SelectNewType(const char* type)
556 {
557 	if (SelectType(type))
558 		return;
559 
560 	fSelectNewType = type;
561 }
562 
563 
564 bool
SelectType(const char * type)565 MimeTypeListView::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 
576 void
SelectItem(MimeTypeItem * item)577 MimeTypeListView::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 
599 MimeTypeItem*
FindItem(const char * type)600 MimeTypeListView::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 
618 void
UpdateItem(MimeTypeItem * item)619 MimeTypeListView::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 
640 void
ShowIcons(bool showIcons)641 MimeTypeListView::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