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