2 * Copyright 2017, Julian Harnath, <julian.harnath@rwth-aachen.de>.
3 * Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>.
4 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
5 * Copyright 2013, Rene Gollent, <rene@gollent.com>.
6 * All rights reserved. Distributed under the terms of the MIT License.
9 #include "PackageListView.h"
16 #include <ControlLook.h>
17 #include <MessageFormat.h>
18 #include <ScrollBar.h>
19 #include <StringForSize.h>
20 #include <package/hpkg/Strings.h>
23 #include "MainWindow.h"
24 #include "WorkStatusView.h"
27 #undef B_TRANSLATION_CONTEXT
28 #define B_TRANSLATION_CONTEXT "PackageListView"
31 static const char* skPackageStateAvailable
= B_TRANSLATE_MARK("Available");
32 static const char* skPackageStateUninstalled
= B_TRANSLATE_MARK("Uninstalled");
33 static const char* skPackageStateActive
= B_TRANSLATE_MARK("Active");
34 static const char* skPackageStateInactive
= B_TRANSLATE_MARK("Inactive");
35 static const char* skPackageStatePending
= B_TRANSLATE_MARK(
36 "Pending" B_UTF8_ELLIPSIS
);
40 package_state_to_string(PackageInfoRef ref
)
42 switch (ref
->State()) {
44 return B_TRANSLATE(skPackageStateAvailable
);
46 return B_TRANSLATE(skPackageStateInactive
);
48 return B_TRANSLATE(skPackageStateActive
);
50 return B_TRANSLATE(skPackageStateUninstalled
);
54 data
.SetToFormat("%3.2f%%", ref
->DownloadProgress() * 100.0);
58 return B_TRANSLATE(skPackageStatePending
);
61 return B_TRANSLATE("Unknown");
65 // A field type displaying both a bitmap and a string so that the
66 // tree display looks nicer (both text and bitmap are indented)
67 class SharedBitmapStringField
: public BStringField
{
68 typedef BStringField Inherited
;
70 SharedBitmapStringField(SharedBitmap
* bitmap
,
71 SharedBitmap::Size size
,
73 virtual ~SharedBitmapStringField();
75 void SetBitmap(SharedBitmap
* bitmap
,
76 SharedBitmap::Size size
);
77 const BBitmap
* Bitmap() const
82 const BBitmap
* fBitmap
;
86 class RatingField
: public BField
{
88 RatingField(float rating
);
89 virtual ~RatingField();
91 void SetRating(float rating
);
99 class SizeField
: public BStringField
{
101 SizeField(double size
);
102 virtual ~SizeField();
104 void SetSize(double size
);
112 // BColumn for PackageListView which knows how to render
113 // a SharedBitmapStringField
114 // TODO: Code-duplication with DriveSetup PartitionList.h
115 class PackageColumn
: public BTitledColumn
{
116 typedef BTitledColumn Inherited
;
118 PackageColumn(const char* title
,
119 float width
, float minWidth
,
120 float maxWidth
, uint32 truncateMode
,
121 alignment align
= B_ALIGN_LEFT
);
123 virtual void DrawField(BField
* field
, BRect rect
,
125 virtual int CompareFields(BField
* field1
, BField
* field2
);
126 virtual float GetPreferredWidth(BField
* field
,
127 BView
* parent
) const;
129 virtual bool AcceptsField(const BField
* field
) const;
131 static void InitTextMargin(BView
* parent
);
134 uint32 fTruncateMode
;
135 static float sTextMargin
;
139 // BRow for the PartitionListView
140 class PackageRow
: public BRow
{
141 typedef BRow Inherited
;
143 PackageRow(const PackageInfoRef
& package
,
144 PackageListener
* listener
);
145 virtual ~PackageRow();
147 const PackageInfoRef
& Package() const
151 void UpdateSummary();
155 void UpdateRepository();
157 PackageRow
*& NextInHash()
158 { return fNextInHash
; }
161 PackageInfoRef fPackage
;
162 PackageInfoListenerRef fPackageListener
;
164 PackageRow
* fNextInHash
;
165 // link for BOpenHashTable
170 MSG_UPDATE_PACKAGE
= 'updp'
174 class PackageListener
: public PackageInfoListener
{
176 PackageListener(PackageListView
* view
)
182 virtual ~PackageListener()
186 virtual void PackageChanged(const PackageInfoEvent
& event
)
188 BMessenger
messenger(fView
);
189 if (!messenger
.IsValid())
192 const PackageInfo
& package
= *event
.Package().Get();
194 BMessage
message(MSG_UPDATE_PACKAGE
);
195 message
.AddString("name", package
.Name());
196 message
.AddUInt32("changes", event
.Changes());
198 messenger
.SendMessage(&message
);
202 PackageListView
* fView
;
206 // #pragma mark - SharedBitmapStringField
209 SharedBitmapStringField::SharedBitmapStringField(SharedBitmap
* bitmap
,
210 SharedBitmap::Size size
, const char* string
)
215 SetBitmap(bitmap
, size
);
219 SharedBitmapStringField::~SharedBitmapStringField()
225 SharedBitmapStringField::SetBitmap(SharedBitmap
* bitmap
,
226 SharedBitmap::Size size
)
229 fBitmap
= bitmap
!= NULL
? bitmap
->Bitmap(size
) : NULL
;
230 // TODO: cause a redraw?
234 // #pragma mark - RatingField
237 RatingField::RatingField(float rating
)
245 RatingField::~RatingField()
251 RatingField::SetRating(float rating
)
258 if (rating
== fRating
)
265 // #pragma mark - SizeField
268 SizeField::SizeField(double size
)
277 SizeField::~SizeField()
283 SizeField::SetSize(double size
)
293 sizeString
= B_TRANSLATE_CONTEXT("-", "no package size");
296 sizeString
= string_for_size(size
, buffer
, sizeof(buffer
));
300 SetString(sizeString
.String());
304 // #pragma mark - PackageColumn
307 // TODO: Code-duplication with DriveSetup PartitionList.cpp
310 float PackageColumn::sTextMargin
= 0.0;
313 PackageColumn::PackageColumn(const char* title
, float width
, float minWidth
,
314 float maxWidth
, uint32 truncateMode
, alignment align
)
316 Inherited(title
, width
, minWidth
, maxWidth
, align
),
317 fTruncateMode(truncateMode
)
319 SetWantsEvents(true);
324 PackageColumn::DrawField(BField
* field
, BRect rect
, BView
* parent
)
326 SharedBitmapStringField
* bitmapField
327 = dynamic_cast<SharedBitmapStringField
*>(field
);
328 BStringField
* stringField
= dynamic_cast<BStringField
*>(field
);
329 RatingField
* ratingField
= dynamic_cast<RatingField
*>(field
);
331 if (bitmapField
!= NULL
) {
332 const BBitmap
* bitmap
= bitmapField
->Bitmap();
334 // figure out the placement
336 BRect r
= bitmap
? bitmap
->Bounds() : BRect(0, 0, 15, 15);
337 float y
= rect
.top
+ ((rect
.Height() - r
.Height()) / 2);
340 switch (Alignment()) {
344 x
= rect
.left
+ sTextMargin
;
345 width
= rect
.right
- (x
+ r
.Width()) - (2 * sTextMargin
);
346 r
.Set(x
+ r
.Width(), rect
.top
, rect
.right
- width
, rect
.bottom
);
350 x
= rect
.right
- sTextMargin
- r
.Width();
351 width
= (x
- rect
.left
- (2 * sTextMargin
));
352 r
.Set(rect
.left
, rect
.top
, rect
.left
+ width
, rect
.bottom
);
356 if (width
!= bitmapField
->Width()) {
357 BString
truncatedString(bitmapField
->String());
358 parent
->TruncateString(&truncatedString
, fTruncateMode
, width
+ 2);
359 bitmapField
->SetClippedString(truncatedString
.String());
360 bitmapField
->SetWidth(width
);
364 if (bitmap
!= NULL
) {
365 parent
->SetDrawingMode(B_OP_ALPHA
);
366 parent
->DrawBitmap(bitmap
, BPoint(x
, y
));
367 parent
->SetDrawingMode(B_OP_OVER
);
371 DrawString(bitmapField
->ClippedString(), parent
, r
);
373 } else if (stringField
!= NULL
) {
375 float width
= rect
.Width() - (2 * sTextMargin
);
377 if (width
!= stringField
->Width()) {
378 BString
truncatedString(stringField
->String());
380 parent
->TruncateString(&truncatedString
, fTruncateMode
, width
+ 2);
381 stringField
->SetClippedString(truncatedString
.String());
382 stringField
->SetWidth(width
);
385 DrawString(stringField
->ClippedString(), parent
, rect
);
387 } else if (ratingField
!= NULL
) {
389 const float kDefaultTextMargin
= 8;
391 float width
= rect
.Width() - (2 * kDefaultTextMargin
);
393 BString string
= "★★★★★";
394 float stringWidth
= parent
->StringWidth(string
);
395 bool drawOverlay
= true;
397 if (width
< stringWidth
) {
398 string
.SetToFormat("%.1f", ratingField
->Rating());
400 stringWidth
= parent
->StringWidth(string
);
403 switch (Alignment()) {
406 rect
.left
+= kDefaultTextMargin
;
409 rect
.left
= rect
.left
+ (width
- stringWidth
) / 2.0f
;
413 rect
.left
= rect
.right
- (stringWidth
+ kDefaultTextMargin
);
417 rect
.left
= floorf(rect
.left
);
418 rect
.right
= rect
.left
+ stringWidth
;
421 parent
->SetHighColor(0, 170, 255);
423 font_height fontHeight
;
424 parent
->GetFontHeight(&fontHeight
);
425 float y
= rect
.top
+ (rect
.Height()
426 - (fontHeight
.ascent
+ fontHeight
.descent
)) / 2
429 parent
->DrawString(string
, BPoint(rect
.left
, y
));
432 rect
.left
= ceilf(rect
.left
433 + (ratingField
->Rating() / 5.0f
) * rect
.Width());
435 rgb_color color
= parent
->LowColor();
437 parent
->SetHighColor(color
);
439 parent
->SetDrawingMode(B_OP_ALPHA
);
440 parent
->FillRect(rect
, B_SOLID_HIGH
);
448 PackageColumn::CompareFields(BField
* field1
, BField
* field2
)
450 SizeField
* sizeField1
= dynamic_cast<SizeField
*>(field1
);
451 SizeField
* sizeField2
= dynamic_cast<SizeField
*>(field2
);
452 if (sizeField1
!= NULL
&& sizeField2
!= NULL
) {
453 if (sizeField1
->Size() > sizeField2
->Size())
455 else if (sizeField1
->Size() < sizeField2
->Size())
460 BStringField
* stringField1
= dynamic_cast<BStringField
*>(field1
);
461 BStringField
* stringField2
= dynamic_cast<BStringField
*>(field2
);
462 if (stringField1
!= NULL
&& stringField2
!= NULL
) {
463 // TODO: Locale aware string compare... not too important if
464 // package names are not translated.
465 return strcasecmp(stringField1
->String(), stringField2
->String());
468 RatingField
* ratingField1
= dynamic_cast<RatingField
*>(field1
);
469 RatingField
* ratingField2
= dynamic_cast<RatingField
*>(field2
);
470 if (ratingField1
!= NULL
&& ratingField2
!= NULL
) {
471 if (ratingField1
->Rating() > ratingField2
->Rating())
473 else if (ratingField1
->Rating() < ratingField2
->Rating())
478 return Inherited::CompareFields(field1
, field2
);
483 PackageColumn::GetPreferredWidth(BField
*_field
, BView
* parent
) const
485 SharedBitmapStringField
* bitmapField
486 = dynamic_cast<SharedBitmapStringField
*>(_field
);
487 BStringField
* stringField
= dynamic_cast<BStringField
*>(_field
);
489 float parentWidth
= Inherited::GetPreferredWidth(_field
, parent
);
493 const BBitmap
* bitmap
= bitmapField
->Bitmap();
495 parent
->GetFont(&font
);
496 width
= font
.StringWidth(bitmapField
->String()) + 3 * sTextMargin
;
498 width
+= bitmap
->Bounds().Width();
501 } else if (stringField
) {
503 parent
->GetFont(&font
);
504 width
= font
.StringWidth(stringField
->String()) + 2 * sTextMargin
;
506 return max_c(width
, parentWidth
);
511 PackageColumn::AcceptsField(const BField
* field
) const
513 return dynamic_cast<const BStringField
*>(field
) != NULL
514 || dynamic_cast<const RatingField
*>(field
) != NULL
;
519 PackageColumn::InitTextMargin(BView
* parent
)
522 parent
->GetFont(&font
);
523 sTextMargin
= ceilf(font
.Size() * 0.8);
527 // #pragma mark - PackageRow
540 PackageRow::PackageRow(const PackageInfoRef
& packageRef
,
541 PackageListener
* packageListener
)
543 Inherited(ceilf(be_plain_font
->Size() * 1.8f
)),
544 fPackage(packageRef
),
545 fPackageListener(packageListener
),
548 if (packageRef
.Get() == NULL
)
551 PackageInfo
& package
= *packageRef
.Get();
553 // Package icon and title
554 // NOTE: The icon BBitmap is referenced by the fPackage member.
572 package
.AddListener(fPackageListener
);
576 PackageRow::~PackageRow()
578 if (fPackage
.Get() != NULL
)
579 fPackage
->RemoveListener(fPackageListener
);
584 PackageRow::UpdateTitle()
586 if (fPackage
.Get() == NULL
)
589 SetField(new SharedBitmapStringField(fPackage
->Icon(),
590 SharedBitmap::SIZE_16
, fPackage
->Title()), kTitleColumn
);
595 PackageRow::UpdateState()
597 if (fPackage
.Get() == NULL
)
600 SetField(new BStringField(package_state_to_string(fPackage
)),
606 PackageRow::UpdateSummary()
608 if (fPackage
.Get() == NULL
)
611 SetField(new BStringField(fPackage
->ShortDescription()),
617 PackageRow::UpdateRating()
619 if (fPackage
.Get() == NULL
)
621 RatingSummary summary
= fPackage
->CalculateRatingSummary();
622 SetField(new RatingField(summary
.averageRating
), kRatingColumn
);
627 PackageRow::UpdateSize()
629 if (fPackage
.Get() == NULL
)
632 SetField(new SizeField(fPackage
->Size()), kSizeColumn
);
637 PackageRow::UpdateRepository()
639 if (fPackage
.Get() == NULL
)
642 SetField(new BStringField(fPackage
->DepotName()), kRepositoryColumn
);
646 // #pragma mark - ItemCountView
649 class PackageListView::ItemCountView
: public BView
{
653 BView("item count view", B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
),
656 BFont
font(be_plain_font
);
660 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
661 SetLowUIColor(ViewUIColor());
662 SetHighUIColor(LowUIColor(), B_DARKEN_4_TINT
);
665 virtual BSize
MinSize()
667 BString
label(_GetLabel());
668 return BSize(StringWidth(label
) + 10, B_H_SCROLL_BAR_HEIGHT
);
671 virtual BSize
PreferredSize()
676 virtual BSize
MaxSize()
681 virtual void Draw(BRect updateRect
)
683 FillRect(updateRect
, B_SOLID_LOW
);
685 BString
label(_GetLabel());
687 font_height fontHeight
;
688 GetFontHeight(&fontHeight
);
690 BRect
bounds(Bounds());
691 float width
= StringWidth(label
);
694 offset
.x
= bounds
.left
+ (bounds
.Width() - width
) / 2.0f
;
695 offset
.y
= bounds
.top
+ (bounds
.Height()
696 - (fontHeight
.ascent
+ fontHeight
.descent
)) / 2.0f
699 DrawString(label
, offset
);
702 void SetItemCount(int32 count
)
704 if (count
== fItemCount
)
706 BSize minSize
= MinSize();
708 if (minSize
!= MinSize())
714 BString
_GetLabel() const
716 static BMessageFormat
format(B_TRANSLATE("{0, plural, "
717 "one{# item} other{# items}}"));
720 format
.Format(label
, fItemCount
);
728 // #pragma mark - PackageListView::RowByNameHashDefinition
731 struct PackageListView::RowByNameHashDefinition
{
732 typedef const char* KeyType
;
733 typedef PackageRow ValueType
;
735 size_t HashKey(const char* key
) const
737 return BPackageKit::BHPKG::BPrivate::hash_string(key
);
740 size_t Hash(PackageRow
* value
) const
742 return BPackageKit::BHPKG::BPrivate::hash_string(
743 value
->Package()->Name().String());
746 bool Compare(const char* key
, PackageRow
* value
) const
748 return value
->Package()->Name() == key
;
751 ValueType
*& GetLink(PackageRow
* value
) const
753 return value
->NextInHash();
758 // #pragma mark - PackageListView
761 PackageListView::PackageListView(BLocker
* modelLock
)
763 BColumnListView("package list view", 0, B_FANCY_BORDER
, true),
764 fModelLock(modelLock
),
765 fPackageListener(new(std::nothrow
) PackageListener(this)),
766 fRowByNameTable(new RowByNameTable()),
767 fWorkStatusView(NULL
)
769 float scale
= be_plain_font
->Size() / 12.f
;
770 float spacing
= be_control_look
->DefaultItemSpacing() * 2;
772 AddColumn(new PackageColumn(B_TRANSLATE("Name"), 150 * scale
, 50 * scale
,
773 300 * scale
, B_TRUNCATE_MIDDLE
), kTitleColumn
);
774 AddColumn(new PackageColumn(B_TRANSLATE("Rating"), 80 * scale
, 50 * scale
,
775 100 * scale
, B_TRUNCATE_MIDDLE
), kRatingColumn
);
776 AddColumn(new PackageColumn(B_TRANSLATE("Description"), 300 * scale
,
777 80 * scale
, 1000 * scale
,
778 B_TRUNCATE_MIDDLE
), kDescriptionColumn
);
779 PackageColumn
* sizeColumn
= new PackageColumn(B_TRANSLATE("Size"),
780 spacing
+ StringWidth(B_TRANSLATE("9999.99 KiB")), 50 * scale
,
781 140 * scale
, B_TRUNCATE_END
);
782 sizeColumn
->SetAlignment(B_ALIGN_RIGHT
);
783 AddColumn(sizeColumn
, kSizeColumn
);
784 AddColumn(new PackageColumn(B_TRANSLATE("Status"),
785 spacing
+ StringWidth(B_TRANSLATE("Available")), 60 * scale
,
786 140 * scale
, B_TRUNCATE_END
), kStatusColumn
);
788 AddColumn(new PackageColumn(B_TRANSLATE("Repository"), 120 * scale
,
789 50 * scale
, 200 * scale
, B_TRUNCATE_MIDDLE
), kRepositoryColumn
);
790 SetColumnVisible(kRepositoryColumn
, false);
791 // invisible by default
793 fItemCountView
= new ItemCountView();
794 AddStatusView(fItemCountView
);
798 PackageListView::~PackageListView()
801 delete fPackageListener
;
806 PackageListView::AttachedToWindow()
808 BColumnListView::AttachedToWindow();
810 PackageColumn::InitTextMargin(ScrollView());
815 PackageListView::AllAttached()
817 BColumnListView::AllAttached();
819 SetSortingEnabled(true);
820 SetSortColumn(ColumnAt(0), false, true);
825 PackageListView::MessageReceived(BMessage
* message
)
827 switch (message
->what
) {
828 case MSG_UPDATE_PACKAGE
:
832 if (message
->FindString("name", &name
) != B_OK
833 || message
->FindUInt32("changes", &changes
) != B_OK
) {
837 BAutolock
_(fModelLock
);
838 PackageRow
* row
= _FindRow(name
);
840 if ((changes
& PKG_CHANGED_TITLE
) != 0)
842 if ((changes
& PKG_CHANGED_SUMMARY
) != 0)
843 row
->UpdateSummary();
844 if ((changes
& PKG_CHANGED_RATINGS
) != 0)
846 if ((changes
& PKG_CHANGED_STATE
) != 0) {
848 if (fWorkStatusView
!= NULL
) {
849 fWorkStatusView
->PackageStatusChanged(
853 if ((changes
& PKG_CHANGED_SIZE
) != 0)
855 if ((changes
& PKG_CHANGED_ICON
) != 0)
857 if ((changes
& PKG_CHANGED_DEPOT
) != 0)
858 row
->UpdateRepository();
864 BColumnListView::MessageReceived(message
);
871 PackageListView::SelectionChanged()
873 BColumnListView::SelectionChanged();
875 BMessage
message(MSG_PACKAGE_SELECTED
);
877 PackageRow
* selected
= dynamic_cast<PackageRow
*>(CurrentSelection());
878 if (selected
!= NULL
)
879 message
.AddString("name", selected
->Package()->Name());
881 Window()->PostMessage(&message
);
886 PackageListView::Clear()
888 fItemCountView
->SetItemCount(0);
889 BColumnListView::Clear();
890 fRowByNameTable
->Clear();
895 PackageListView::AddPackage(const PackageInfoRef
& package
)
897 PackageRow
* packageRow
= _FindRow(package
);
899 // forget about it if this package is already in the listview
900 if (packageRow
!= NULL
)
903 BAutolock
_(fModelLock
);
905 // create the row for this package
906 packageRow
= new PackageRow(package
, fPackageListener
);
908 // add the row, parent may be NULL (add at top level)
911 // add to hash table for quick lookup of row by package name
912 fRowByNameTable
->Insert(packageRow
);
914 // make sure the row is initially expanded
915 ExpandOrCollapse(packageRow
, true);
917 fItemCountView
->SetItemCount(CountRows());
922 PackageListView::RemovePackage(const PackageInfoRef
& package
)
924 PackageRow
* packageRow
= _FindRow(package
);
925 if (packageRow
== NULL
)
928 fRowByNameTable
->Remove(packageRow
);
930 RemoveRow(packageRow
);
933 fItemCountView
->SetItemCount(CountRows());
938 PackageListView::SelectPackage(const PackageInfoRef
& package
)
940 PackageRow
* row
= _FindRow(package
);
941 BRow
* selected
= CurrentSelection();
946 SetFocusRow(row
, false);
953 PackageListView::AttachWorkStatusView(WorkStatusView
* view
)
955 fWorkStatusView
= view
;
960 PackageListView::_FindRow(const PackageInfoRef
& package
)
962 if (package
.Get() == NULL
)
964 return fRowByNameTable
->Lookup(package
->Name().String());
969 PackageListView::_FindRow(const BString
& packageName
)
971 return fRowByNameTable
->Lookup(packageName
.String());