1 /*
2 * Copyright 2018-2024, Andrew Lindesay, <apl@lindesay.co.nz>.
3 * Copyright 2017, Julian Harnath, <julian.harnath@rwth-aachen.de>.
4 * Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>.
5 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
6 * Copyright 2013, Rene Gollent, <rene@gollent.com>.
7 * All rights reserved. Distributed under the terms of the MIT License.
8 */
9
10 #include "PackageListView.h"
11
12 #include <algorithm>
13 #include <stdio.h>
14
15 #include <Autolock.h>
16 #include <Catalog.h>
17 #include <ControlLook.h>
18 #include <NumberFormat.h>
19 #include <ScrollBar.h>
20 #include <StringFormat.h>
21 #include <StringForSize.h>
22 #include <package/hpkg/Strings.h>
23 #include <Window.h>
24
25 #include "LocaleUtils.h"
26 #include "Logger.h"
27 #include "PackageUtils.h"
28 #include "RatingUtils.h"
29 #include "SharedIcons.h"
30 #include "WorkStatusView.h"
31
32
33 #undef B_TRANSLATION_CONTEXT
34 #define B_TRANSLATION_CONTEXT "PackageListView"
35
36
37 static const char* skPackageStateAvailable = B_TRANSLATE_MARK("Available");
38 static const char* skPackageStateUninstalled = B_TRANSLATE_MARK("Uninstalled");
39 static const char* skPackageStateActive = B_TRANSLATE_MARK("Active");
40 static const char* skPackageStateInactive = B_TRANSLATE_MARK("Inactive");
41 static const char* skPackageStatePending = B_TRANSLATE_MARK(
42 "Pending" B_UTF8_ELLIPSIS);
43
44
45 inline BString
package_state_to_string(PackageInfoRef package)46 package_state_to_string(PackageInfoRef package)
47 {
48 static BNumberFormat numberFormat;
49
50 switch (PackageUtils::State(package)) {
51 case NONE:
52 return B_TRANSLATE(skPackageStateAvailable);
53 case INSTALLED:
54 return B_TRANSLATE(skPackageStateInactive);
55 case ACTIVATED:
56 return B_TRANSLATE(skPackageStateActive);
57 case UNINSTALLED:
58 return B_TRANSLATE(skPackageStateUninstalled);
59 case DOWNLOADING:
60 {
61 BString data;
62 float fraction = PackageUtils::DownloadProgress(package);
63 if (numberFormat.FormatPercent(data, fraction) != B_OK) {
64 HDERROR("unable to format the percentage");
65 data = "???";
66 }
67 return data;
68 }
69 case PENDING:
70 return B_TRANSLATE(skPackageStatePending);
71 }
72
73 return B_TRANSLATE("Unknown");
74 }
75
76
77 class PackageIconAndTitleField : public BStringField {
78 typedef BStringField Inherited;
79 public:
80 PackageIconAndTitleField(
81 const char* packageName,
82 const char* string,
83 bool isActivated,
84 bool isNativeDesktop);
85 virtual ~PackageIconAndTitleField();
86
PackageName() const87 const BString PackageName() const
88 { return fPackageName; }
89
IsActivated() const90 bool IsActivated() const
91 { return fIsActivated; }
92
IsNativeDesktop() const93 bool IsNativeDesktop() const
94 { return fIsNativeDesktop; }
95
96 private:
97 const BString fPackageName;
98 const bool fIsActivated;
99 const bool fIsNativeDesktop;
100 };
101
102
103 class RatingField : public BField {
104 public:
105 RatingField(float rating);
106 virtual ~RatingField();
107
108 void SetRating(float rating);
Rating() const109 float Rating() const
110 { return fRating; }
111 private:
112 float fRating;
113 };
114
115
116 class SizeField : public BStringField {
117 public:
118 SizeField(double size);
119 virtual ~SizeField();
120
121 void SetSize(double size);
Size() const122 double Size() const
123 { return fSize; }
124 private:
125 double fSize;
126 };
127
128
129 class DateField : public BStringField {
130 public:
131 DateField(uint64 millisSinceEpoc);
132 virtual ~DateField();
133
134 void SetMillisSinceEpoc(uint64 millisSinceEpoc);
MillisSinceEpoc() const135 uint64 MillisSinceEpoc() const
136 { return fMillisSinceEpoc; }
137
138 private:
139 void _SetMillisSinceEpoc(uint64 millisSinceEpoc);
140
141 private:
142 uint64 fMillisSinceEpoc;
143 };
144
145
146 // BColumn for PackageListView which knows how to render
147 // a PackageIconAndTitleField
148 class PackageColumn : public BTitledColumn {
149 typedef BTitledColumn Inherited;
150 public:
151 PackageColumn(Model* model,
152 const char* title,
153 float width, float minWidth,
154 float maxWidth, uint32 truncateMode,
155 alignment align = B_ALIGN_LEFT);
156 virtual ~PackageColumn();
157
158 virtual void DrawField(BField* field, BRect rect,
159 BView* parent);
160 virtual int CompareFields(BField* field1, BField* field2);
161 virtual float GetPreferredWidth(BField* field,
162 BView* parent) const;
163
164 virtual bool AcceptsField(const BField* field) const;
165
166 static void InitTextMargin(BView* parent);
167
168 private:
169 Model* fModel;
170 uint32 fTruncateMode;
171 RatingStarsMetrics* fRatingsMetrics;
172 static float sTextMargin;
173 };
174
175
176 // BRow for the PackageListView
177 class PackageRow : public BRow {
178 typedef BRow Inherited;
179 public:
180 PackageRow(
181 const PackageInfoRef& package,
182 PackageListener* listener);
183 virtual ~PackageRow();
184
Package() const185 const PackageInfoRef& Package() const
186 { return fPackage; }
187
188 void UpdateIconAndTitle();
189 void UpdateSummary();
190 void UpdateState();
191 void UpdateRating();
192 void UpdateSize();
193 void UpdateRepository();
194 void UpdateVersion();
195 void UpdateVersionCreateTimestamp();
196
NextInHash()197 PackageRow*& NextInHash()
198 { return fNextInHash; }
199
200 private:
201 PackageInfoRef fPackage;
202 PackageInfoListenerRef
203 fPackageListener;
204
205 PackageRow* fNextInHash;
206 // link for BOpenHashTable
207 };
208
209
210 enum {
211 MSG_UPDATE_PACKAGE = 'updp'
212 };
213
214
215 class PackageListener : public PackageInfoListener {
216 public:
PackageListener(PackageListView * view)217 PackageListener(PackageListView* view)
218 :
219 fView(view)
220 {
221 }
222
~PackageListener()223 virtual ~PackageListener()
224 {
225 }
226
PackageChanged(const PackageInfoEvent & event)227 virtual void PackageChanged(const PackageInfoEvent& event)
228 {
229 BMessenger messenger(fView);
230 if (!messenger.IsValid())
231 return;
232
233 const PackageInfo& package = *event.Package().Get();
234
235 BMessage message(MSG_UPDATE_PACKAGE);
236 message.AddString("name", package.Name());
237 message.AddUInt32("changes", event.Changes());
238
239 messenger.SendMessage(&message);
240 }
241
242 private:
243 PackageListView* fView;
244 };
245
246
247 // #pragma mark - PackageIconAndTitleField
248
249
PackageIconAndTitleField(const char * packageName,const char * string,bool isActivated,bool isNativeDesktop)250 PackageIconAndTitleField::PackageIconAndTitleField(const char* packageName, const char* string,
251 bool isActivated, bool isNativeDesktop)
252 :
253 Inherited(string),
254 fPackageName(packageName),
255 fIsActivated(isActivated),
256 fIsNativeDesktop(isNativeDesktop)
257 {
258 }
259
260
~PackageIconAndTitleField()261 PackageIconAndTitleField::~PackageIconAndTitleField()
262 {
263 }
264
265
266 // #pragma mark - RatingField
267
268
RatingField(float rating)269 RatingField::RatingField(float rating)
270 :
271 fRating(RATING_MISSING)
272 {
273 SetRating(rating);
274 }
275
276
~RatingField()277 RatingField::~RatingField()
278 {
279 }
280
281
282 void
SetRating(float rating)283 RatingField::SetRating(float rating)
284 {
285 fRating = rating;
286 }
287
288
289 // #pragma mark - SizeField
290
291
SizeField(double size)292 SizeField::SizeField(double size)
293 :
294 BStringField(""),
295 fSize(-1.0)
296 {
297 SetSize(size);
298 }
299
300
~SizeField()301 SizeField::~SizeField()
302 {
303 }
304
305
306 void
SetSize(double size)307 SizeField::SetSize(double size)
308 {
309 if (size < 0.0)
310 size = 0.0;
311
312 if (size == fSize)
313 return;
314
315 BString sizeString;
316 if (size == 0) {
317 sizeString = B_TRANSLATE_CONTEXT("-", "no package size");
318 } else {
319 char buffer[256];
320 sizeString = string_for_size(size, buffer, sizeof(buffer));
321 }
322
323 fSize = size;
324 SetString(sizeString.String());
325 }
326
327
328 // #pragma mark - DateField
329
330
DateField(uint64 millisSinceEpoc)331 DateField::DateField(uint64 millisSinceEpoc)
332 :
333 BStringField(""),
334 fMillisSinceEpoc(0)
335 {
336 _SetMillisSinceEpoc(millisSinceEpoc);
337 }
338
339
~DateField()340 DateField::~DateField()
341 {
342 }
343
344
345 void
SetMillisSinceEpoc(uint64 millisSinceEpoc)346 DateField::SetMillisSinceEpoc(uint64 millisSinceEpoc)
347 {
348 if (millisSinceEpoc == fMillisSinceEpoc)
349 return;
350 _SetMillisSinceEpoc(millisSinceEpoc);
351 }
352
353
354 void
_SetMillisSinceEpoc(uint64 millisSinceEpoc)355 DateField::_SetMillisSinceEpoc(uint64 millisSinceEpoc)
356 {
357 BString dateString;
358
359 if (millisSinceEpoc == 0)
360 dateString = B_TRANSLATE_CONTEXT("-", "no package publish");
361 else
362 dateString = LocaleUtils::TimestampToDateString(millisSinceEpoc);
363
364 fMillisSinceEpoc = millisSinceEpoc;
365 SetString(dateString.String());
366 }
367
368
369 // #pragma mark - PackageColumn
370
371
372 // TODO: Code-duplication with DriveSetup PartitionList.cpp
373
374
375 float PackageColumn::sTextMargin = 0.0;
376
377
PackageColumn(Model * model,const char * title,float width,float minWidth,float maxWidth,uint32 truncateMode,alignment align)378 PackageColumn::PackageColumn(Model* model, const char* title, float width,
379 float minWidth, float maxWidth, uint32 truncateMode, alignment align)
380 :
381 Inherited(title, width, minWidth, maxWidth, align),
382 fModel(model),
383 fTruncateMode(truncateMode)
384 {
385 SetWantsEvents(true);
386
387 BSize ratingStarSize = SharedIcons::IconStarBlue12Scaled()->Bitmap()->Bounds().Size();
388 fRatingsMetrics = new RatingStarsMetrics(ratingStarSize);
389 }
390
391
~PackageColumn()392 PackageColumn::~PackageColumn()
393 {
394 delete fRatingsMetrics;
395 }
396
397
398 void
DrawField(BField * field,BRect rect,BView * parent)399 PackageColumn::DrawField(BField* field, BRect rect, BView* parent)
400 {
401 PackageIconAndTitleField* packageIconAndTitleField
402 = dynamic_cast<PackageIconAndTitleField*>(field);
403 BStringField* stringField = dynamic_cast<BStringField*>(field);
404 RatingField* ratingField = dynamic_cast<RatingField*>(field);
405
406 if (packageIconAndTitleField != NULL) {
407
408 // TODO (andponlin) factor this out as this method is getting too large.
409
410 BSize iconSize = BControlLook::ComposeIconSize(16);
411 BSize trailingIconSize = BControlLook::ComposeIconSize(8);
412 float trailingIconPaddingFactor = 0.2f;
413 BRect iconRect;
414 BRect titleRect;
415 float titleTextWidth = 0.0f;
416 float textMargin = 8.0f; // copied from ColumnTypes.cpp
417
418 std::vector<BitmapHolderRef> trailingIconBitmaps;
419
420 if (packageIconAndTitleField->IsActivated())
421 trailingIconBitmaps.push_back(SharedIcons::IconInstalled16Scaled());
422
423 if (packageIconAndTitleField->IsNativeDesktop())
424 trailingIconBitmaps.push_back(SharedIcons::IconNative16Scaled());
425
426 // If there is not enough space then drop the "activated" indicator in order to make more
427 // room for the title.
428
429 float trailingIconsWidth = static_cast<float>(trailingIconBitmaps.size())
430 * (trailingIconSize.Width() * (1.0 + trailingIconPaddingFactor));
431
432 if (!trailingIconBitmaps.empty()) {
433 static float sMinimalTextPart = -1.0;
434
435 if (sMinimalTextPart < 0.0)
436 sMinimalTextPart = parent->StringWidth("M") * 5.0;
437
438 float minimalWidth
439 = iconSize.Width() + trailingIconsWidth + sTextMargin + sMinimalTextPart;
440
441 if (rect.Width() <= minimalWidth)
442 trailingIconBitmaps.clear();
443 }
444
445 // Calculate the location of the icon.
446
447 iconRect = BRect(BPoint(rect.left + sTextMargin,
448 rect.top + ((rect.Height() - iconSize.Height()) / 2) - 1),
449 iconSize);
450
451 // Calculate the location of the title text.
452
453 titleRect = rect;
454 titleRect.left = iconRect.right;
455 titleRect.right -= trailingIconsWidth;
456
457 // Figure out if the text needs to be truncated.
458
459 float textWidth = titleRect.Width() - (2.0 * textMargin);
460 BString truncatedString(packageIconAndTitleField->String());
461 parent->TruncateString(&truncatedString, fTruncateMode, textWidth);
462 packageIconAndTitleField->SetClippedString(truncatedString.String());
463 titleTextWidth = parent->StringWidth(truncatedString);
464
465 // Draw the icon.
466
467 BitmapHolderRef bitmapHolderRef;
468 status_t bitmapResult;
469
470 bitmapResult = fModel->GetPackageIconRepository().GetIcon(
471 packageIconAndTitleField->PackageName(), iconSize.Width() + 1, bitmapHolderRef);
472
473 if (bitmapResult == B_OK) {
474 if (bitmapHolderRef.IsSet()) {
475 const BBitmap* bitmap = bitmapHolderRef->Bitmap();
476 if (bitmap != NULL && bitmap->IsValid()) {
477 parent->SetDrawingMode(B_OP_ALPHA);
478 parent->DrawBitmap(bitmap, bitmap->Bounds(), iconRect,
479 B_FILTER_BITMAP_BILINEAR);
480 parent->SetDrawingMode(B_OP_OVER);
481 }
482 }
483 }
484
485 // Draw the title.
486
487 DrawString(packageIconAndTitleField->ClippedString(), parent, titleRect);
488
489 // Draw the trailing icons
490
491 if (!trailingIconBitmaps.empty()) {
492
493 BRect trailingIconRect(
494 BPoint(titleRect.left + titleTextWidth + textMargin, iconRect.top),
495 trailingIconSize);
496
497 parent->SetDrawingMode(B_OP_ALPHA);
498
499 for (std::vector<BitmapHolderRef>::iterator it = trailingIconBitmaps.begin();
500 it != trailingIconBitmaps.end(); it++) {
501 const BBitmap* bitmap = (*it)->Bitmap();
502 BRect bitmapBounds = bitmap->Bounds();
503
504 BRect trailingIconAlignedRect
505 = BRect(BPoint(ceilf(trailingIconRect.LeftTop().x) + 0.5,
506 ceilf(trailingIconRect.LeftTop().y) + 0.5),
507 trailingIconRect.Size());
508
509 parent->DrawBitmap(bitmap, bitmapBounds, trailingIconAlignedRect,
510 B_FILTER_BITMAP_BILINEAR);
511
512 trailingIconRect.OffsetBy(
513 trailingIconSize.Width() * (1.0 + trailingIconPaddingFactor), 0);
514 }
515
516 parent->SetDrawingMode(B_OP_OVER);
517 }
518
519 } else if (stringField != NULL) {
520
521 float width = rect.Width() - (2 * sTextMargin);
522
523 if (width != stringField->Width()) {
524 BString truncatedString(stringField->String());
525
526 parent->TruncateString(&truncatedString, fTruncateMode, width + 2);
527 stringField->SetClippedString(truncatedString.String());
528 stringField->SetWidth(width);
529 }
530
531 DrawString(stringField->ClippedString(), parent, rect);
532
533 } else if (ratingField != NULL) {
534 float width = rect.Width();
535 float padding = be_control_look->ComposeSpacing(B_USE_SMALL_SPACING);
536 bool isRatingValid = ratingField->Rating() >= RATING_MIN;
537
538 if (!isRatingValid || width < fRatingsMetrics->Size().Width() + padding * 2.0) {
539 BString ratingAsText = "-";
540
541 if (isRatingValid)
542 ratingAsText.SetToFormat("%.1f", ratingField->Rating());
543
544 float ratingAsTextWidth = parent->StringWidth(ratingAsText);
545
546 if (ratingAsTextWidth + padding * 2.0 < width) {
547 font_height fontHeight;
548 parent->GetFontHeight(&fontHeight);
549 float fullHeight = fontHeight.ascent + fontHeight.descent;
550 float y = rect.top + (rect.Height() - fullHeight) / 2 + fontHeight.ascent;
551 parent->DrawString(ratingAsText, BPoint(rect.left + padding, y));
552 }
553 } else {
554 const BBitmap* starIcon = SharedIcons::IconStarBlue12Scaled()->Bitmap();
555 float ratingsStarsHeight = fRatingsMetrics->Size().Height();
556 BPoint starsPt(floorf(rect.LeftTop().x + padding),
557 floorf(rect.LeftTop().y + (rect.Size().Height() / 2.0) - ratingsStarsHeight / 2.0));
558 RatingUtils::Draw(parent, starsPt, ratingField->Rating(), starIcon);
559 }
560 }
561 }
562
563
564 int
CompareFields(BField * field1,BField * field2)565 PackageColumn::CompareFields(BField* field1, BField* field2)
566 {
567 DateField* dateField1 = dynamic_cast<DateField*>(field1);
568 DateField* dateField2 = dynamic_cast<DateField*>(field2);
569 if (dateField1 != NULL && dateField2 != NULL) {
570 if (dateField1->MillisSinceEpoc() > dateField2->MillisSinceEpoc())
571 return -1;
572 else if (dateField1->MillisSinceEpoc() < dateField2->MillisSinceEpoc())
573 return 1;
574 return 0;
575 }
576
577 SizeField* sizeField1 = dynamic_cast<SizeField*>(field1);
578 SizeField* sizeField2 = dynamic_cast<SizeField*>(field2);
579 if (sizeField1 != NULL && sizeField2 != NULL) {
580 if (sizeField1->Size() > sizeField2->Size())
581 return -1;
582 else if (sizeField1->Size() < sizeField2->Size())
583 return 1;
584 return 0;
585 }
586
587 BStringField* stringField1 = dynamic_cast<BStringField*>(field1);
588 BStringField* stringField2 = dynamic_cast<BStringField*>(field2);
589 if (stringField1 != NULL && stringField2 != NULL) {
590 // TODO: Locale aware string compare... not too important if
591 // package names are not translated.
592 return strcasecmp(stringField1->String(), stringField2->String());
593 }
594
595 RatingField* ratingField1 = dynamic_cast<RatingField*>(field1);
596 RatingField* ratingField2 = dynamic_cast<RatingField*>(field2);
597 if (ratingField1 != NULL && ratingField2 != NULL) {
598 if (ratingField1->Rating() > ratingField2->Rating())
599 return -1;
600 else if (ratingField1->Rating() < ratingField2->Rating())
601 return 1;
602 return 0;
603 }
604
605 return Inherited::CompareFields(field1, field2);
606 }
607
608
609 float
GetPreferredWidth(BField * _field,BView * parent) const610 PackageColumn::GetPreferredWidth(BField *_field, BView* parent) const
611 {
612 PackageIconAndTitleField* packageIconAndTitleField
613 = dynamic_cast<PackageIconAndTitleField*>(_field);
614 BStringField* stringField = dynamic_cast<BStringField*>(_field);
615
616 float parentWidth = Inherited::GetPreferredWidth(_field, parent);
617 float width = 0.0;
618
619 if (packageIconAndTitleField) {
620 BFont font;
621 parent->GetFont(&font);
622 width = font.StringWidth(packageIconAndTitleField->String())
623 + 3 * sTextMargin;
624 width += 16;
625 // for the icon; always 16px
626 } else if (stringField) {
627 BFont font;
628 parent->GetFont(&font);
629 width = font.StringWidth(stringField->String()) + 2 * sTextMargin;
630 }
631 return max_c(width, parentWidth);
632 }
633
634
635 bool
AcceptsField(const BField * field) const636 PackageColumn::AcceptsField(const BField* field) const
637 {
638 return dynamic_cast<const BStringField*>(field) != NULL
639 || dynamic_cast<const RatingField*>(field) != NULL;
640 }
641
642
643 void
InitTextMargin(BView * parent)644 PackageColumn::InitTextMargin(BView* parent)
645 {
646 BFont font;
647 parent->GetFont(&font);
648 sTextMargin = ceilf(font.Size() * 0.8);
649 }
650
651
652 // #pragma mark - PackageRow
653
654
655 enum {
656 kTitleColumn,
657 kRatingColumn,
658 kDescriptionColumn,
659 kSizeColumn,
660 kStatusColumn,
661 kRepositoryColumn,
662 kVersionColumn,
663 kVersionCreateTimestampColumn,
664 };
665
666
PackageRow(const PackageInfoRef & packageRef,PackageListener * packageListener)667 PackageRow::PackageRow(const PackageInfoRef& packageRef,
668 PackageListener* packageListener)
669 :
670 Inherited(ceilf(be_plain_font->Size() * 1.8f)),
671 fPackage(packageRef),
672 fPackageListener(packageListener),
673 fNextInHash(NULL)
674 {
675 if (!packageRef.IsSet())
676 return;
677
678 // Package icon and title
679 // NOTE: The icon BBitmap is referenced by the fPackage member.
680 UpdateIconAndTitle();
681
682 UpdateRating();
683 UpdateSummary();
684 UpdateSize();
685 UpdateState();
686 UpdateRepository();
687 UpdateVersion();
688 UpdateVersionCreateTimestamp();
689
690 packageRef->AddListener(fPackageListener);
691 }
692
693
~PackageRow()694 PackageRow::~PackageRow()
695 {
696 if (fPackage.IsSet())
697 fPackage->RemoveListener(fPackageListener);
698 }
699
700
701 void
UpdateIconAndTitle()702 PackageRow::UpdateIconAndTitle()
703 {
704 if (!fPackage.IsSet())
705 return;
706
707 BString title;
708 PackageUtils::TitleOrName(fPackage, title);
709
710 BField* field = new PackageIconAndTitleField(fPackage->Name(), title,
711 PackageUtils::State(fPackage) == ACTIVATED, PackageUtils::IsNativeDesktop(fPackage));
712 SetField(field, kTitleColumn);
713 }
714
715
716 void
UpdateState()717 PackageRow::UpdateState()
718 {
719 if (!fPackage.IsSet())
720 return;
721 SetField(new BStringField(package_state_to_string(fPackage)),
722 kStatusColumn);
723 }
724
725
726 void
UpdateSummary()727 PackageRow::UpdateSummary()
728 {
729 if (!fPackage.IsSet())
730 return;
731
732 BString summary;
733 PackageUtils::Summary(fPackage, summary);
734 // TODO; `kDescriptionColumn` seems wrong here?
735 SetField(new BStringField(summary), kDescriptionColumn);
736 }
737
738
739 void
UpdateRating()740 PackageRow::UpdateRating()
741 {
742 if (!fPackage.IsSet())
743 return;
744
745 UserRatingInfoRef userRatingInfo = fPackage->UserRatingInfo();
746 UserRatingSummaryRef userRatingSummary;
747 float averageRating = RATING_MISSING;
748
749 if (userRatingInfo.IsSet())
750 userRatingSummary = userRatingInfo->Summary();
751
752 if (userRatingSummary.IsSet())
753 averageRating = userRatingSummary->AverageRating();
754
755 SetField(new RatingField(averageRating), kRatingColumn);
756 }
757
758
759 void
UpdateSize()760 PackageRow::UpdateSize()
761 {
762 if (!fPackage.IsSet())
763 return;
764 SetField(new SizeField(PackageUtils::Size(fPackage)), kSizeColumn);
765 }
766
767
768 void
UpdateRepository()769 PackageRow::UpdateRepository()
770 {
771 if (!fPackage.IsSet())
772 return;
773 SetField(new BStringField(fPackage->DepotName()), kRepositoryColumn);
774 }
775
776
777 void
UpdateVersion()778 PackageRow::UpdateVersion()
779 {
780 if (!fPackage.IsSet())
781 return;
782 SetField(new BStringField(fPackage->Version().ToString()), kVersionColumn);
783 }
784
785
786 void
UpdateVersionCreateTimestamp()787 PackageRow::UpdateVersionCreateTimestamp()
788 {
789 if (!fPackage.IsSet())
790 return;
791 SetField(new DateField(fPackage->VersionCreateTimestamp()),
792 kVersionCreateTimestampColumn);
793 }
794
795
796 // #pragma mark - ItemCountView
797
798
799 class PackageListView::ItemCountView : public BView {
800 public:
ItemCountView()801 ItemCountView()
802 :
803 BView("item count view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
804 fItemCount(0)
805 {
806 BFont font(be_plain_font);
807 font.SetSize(font.Size() * 0.75f);
808 SetFont(&font);
809
810 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
811 SetLowUIColor(ViewUIColor());
812 SetHighUIColor(LowUIColor(), B_DARKEN_4_TINT);
813
814 // constantly calculating the size is expensive so here a sensible
815 // upper limit on the number of packages is arbitrarily chosen.
816 fMinSize = BSize(StringWidth(_DeriveLabel(999999)) + 10,
817 be_control_look->GetScrollBarWidth());
818 }
819
MinSize()820 virtual BSize MinSize()
821 {
822 return fMinSize;
823 }
824
PreferredSize()825 virtual BSize PreferredSize()
826 {
827 return MinSize();
828 }
829
MaxSize()830 virtual BSize MaxSize()
831 {
832 return MinSize();
833 }
834
Draw(BRect updateRect)835 virtual void Draw(BRect updateRect)
836 {
837 FillRect(updateRect, B_SOLID_LOW);
838
839 font_height fontHeight;
840 GetFontHeight(&fontHeight);
841
842 BRect bounds(Bounds());
843 float width = StringWidth(fLabel);
844
845 BPoint offset;
846 offset.x = bounds.left + (bounds.Width() - width) / 2.0f;
847 offset.y = bounds.top + (bounds.Height()
848 - (fontHeight.ascent + fontHeight.descent)) / 2.0f
849 + fontHeight.ascent;
850
851 DrawString(fLabel, offset);
852 }
853
SetItemCount(int32 count)854 void SetItemCount(int32 count)
855 {
856 if (count == fItemCount)
857 return;
858 fItemCount = count;
859 fLabel = _DeriveLabel(fItemCount);
860 Invalidate();
861 }
862
863 private:
864
865 /*! This method is hit quite often when the list of packages in the
866 table-view are updated. Derivation of the plural for some
867 languages such as Russian can be slow so this method should be
868 called sparingly.
869 */
870
_DeriveLabel(int32 count) const871 BString _DeriveLabel(int32 count) const
872 {
873 static BStringFormat format(B_TRANSLATE("{0, plural, "
874 "one{# item} other{# items}}"));
875 BString label;
876 format.Format(label, count);
877 return label;
878 }
879
880 int32 fItemCount;
881 BString fLabel;
882 BSize fMinSize;
883 };
884
885
886 // #pragma mark - PackageListView::RowByNameHashDefinition
887
888
889 struct PackageListView::RowByNameHashDefinition {
890 typedef const char* KeyType;
891 typedef PackageRow ValueType;
892
HashKeyPackageListView::RowByNameHashDefinition893 size_t HashKey(const char* key) const
894 {
895 return BString::HashValue(key);
896 }
897
HashPackageListView::RowByNameHashDefinition898 size_t Hash(PackageRow* value) const
899 {
900 return HashKey(value->Package()->Name().String());
901 }
902
ComparePackageListView::RowByNameHashDefinition903 bool Compare(const char* key, PackageRow* value) const
904 {
905 return value->Package()->Name() == key;
906 }
907
GetLinkPackageListView::RowByNameHashDefinition908 ValueType*& GetLink(PackageRow* value) const
909 {
910 return value->NextInHash();
911 }
912 };
913
914
915 // #pragma mark - PackageListView
916
917
PackageListView(Model * model)918 PackageListView::PackageListView(Model* model)
919 :
920 BColumnListView(B_TRANSLATE("All packages"), 0, B_FANCY_BORDER, true),
921 fModel(model),
922 fPackageListener(new(std::nothrow) PackageListener(this)),
923 fRowByNameTable(new RowByNameTable()),
924 fWorkStatusView(NULL),
925 fIgnoreSelectionChanged(false)
926 {
927 float scale = be_plain_font->Size() / 12.f;
928 float spacing = be_control_look->DefaultItemSpacing() * 2;
929
930 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Name"),
931 150 * scale, 50 * scale, 300 * scale,
932 B_TRUNCATE_MIDDLE), kTitleColumn);
933 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Rating"),
934 80 * scale, 50 * scale, 100 * scale,
935 B_TRUNCATE_MIDDLE), kRatingColumn);
936 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Description"),
937 300 * scale, 80 * scale, 1000 * scale,
938 B_TRUNCATE_MIDDLE), kDescriptionColumn);
939 PackageColumn* sizeColumn = new PackageColumn(fModel, B_TRANSLATE("Size"),
940 spacing + StringWidth("9999.99 KiB"), 50 * scale,
941 140 * scale, B_TRUNCATE_END);
942 sizeColumn->SetAlignment(B_ALIGN_RIGHT);
943 AddColumn(sizeColumn, kSizeColumn);
944 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Status"),
945 spacing + StringWidth(B_TRANSLATE("Available")), 60 * scale,
946 140 * scale, B_TRUNCATE_END), kStatusColumn);
947
948 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Repository"),
949 120 * scale, 50 * scale, 200 * scale,
950 B_TRUNCATE_MIDDLE), kRepositoryColumn);
951 SetColumnVisible(kRepositoryColumn, false);
952 // invisible by default
953
954 float widthWithPlacboVersion = spacing
955 + StringWidth("8.2.3176-2");
956 // average sort of version length as model
957 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Version"),
958 widthWithPlacboVersion, widthWithPlacboVersion,
959 widthWithPlacboVersion + (50 * scale),
960 B_TRUNCATE_MIDDLE), kVersionColumn);
961
962 float widthWithPlaceboDate = spacing
963 + StringWidth(LocaleUtils::TimestampToDateString(
964 static_cast<uint64>(1000)));
965 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Date"),
966 widthWithPlaceboDate, widthWithPlaceboDate,
967 widthWithPlaceboDate + (50 * scale),
968 B_TRUNCATE_END), kVersionCreateTimestampColumn);
969
970 fItemCountView = new ItemCountView();
971 AddStatusView(fItemCountView);
972 }
973
974
~PackageListView()975 PackageListView::~PackageListView()
976 {
977 Clear();
978 delete fPackageListener;
979 }
980
981
982 void
AttachedToWindow()983 PackageListView::AttachedToWindow()
984 {
985 BColumnListView::AttachedToWindow();
986
987 PackageColumn::InitTextMargin(ScrollView());
988 }
989
990
991 void
AllAttached()992 PackageListView::AllAttached()
993 {
994 BColumnListView::AllAttached();
995
996 SetSortingEnabled(true);
997 SetSortColumn(ColumnAt(0), false, true);
998 }
999
1000
1001 void
MessageReceived(BMessage * message)1002 PackageListView::MessageReceived(BMessage* message)
1003 {
1004 switch (message->what) {
1005 case MSG_UPDATE_PACKAGE:
1006 {
1007 BString name;
1008 uint32 changes;
1009 if (message->FindString("name", &name) != B_OK
1010 || message->FindUInt32("changes", &changes) != B_OK) {
1011 break;
1012 }
1013 BAutolock _(fModel->Lock());
1014 PackageRow* row = _FindRow(name);
1015 if (row != NULL) {
1016 if ((changes & PKG_CHANGED_LOCALIZED_TEXT) != 0
1017 || (changes & PKG_CHANGED_LOCAL_INFO) != 0) {
1018 row->UpdateIconAndTitle();
1019 row->UpdateSummary();
1020 }
1021 if ((changes & PKG_CHANGED_RATINGS) != 0)
1022 row->UpdateRating();
1023 if ((changes & PKG_CHANGED_LOCAL_INFO) != 0) {
1024 row->UpdateState();
1025 row->UpdateSize();
1026 }
1027 if ((changes & PKG_CHANGED_ICON) != 0)
1028 row->UpdateIconAndTitle();
1029 if ((changes & PKG_CHANGED_VERSION_CREATE_TIMESTAMP) != 0)
1030 row->UpdateVersionCreateTimestamp();
1031 }
1032 break;
1033 }
1034
1035 default:
1036 BColumnListView::MessageReceived(message);
1037 break;
1038 }
1039 }
1040
1041
1042 void
SelectionChanged()1043 PackageListView::SelectionChanged()
1044 {
1045 BColumnListView::SelectionChanged();
1046
1047 if (fIgnoreSelectionChanged)
1048 return;
1049
1050 BMessage message(MSG_PACKAGE_SELECTED);
1051
1052 PackageRow* selected = dynamic_cast<PackageRow*>(CurrentSelection());
1053 if (selected != NULL)
1054 message.AddString("name", selected->Package()->Name());
1055
1056 Window()->PostMessage(&message);
1057 }
1058
1059
1060 void
Clear()1061 PackageListView::Clear()
1062 {
1063 fItemCountView->SetItemCount(0);
1064 BColumnListView::Clear();
1065 fRowByNameTable->Clear();
1066 }
1067
1068
1069 void
AddPackage(const PackageInfoRef & package)1070 PackageListView::AddPackage(const PackageInfoRef& package)
1071 {
1072 PackageRow* packageRow = _FindRow(package);
1073
1074 // forget about it if this package is already in the listview
1075 if (packageRow != NULL)
1076 return;
1077
1078 BAutolock _(fModel->Lock());
1079
1080 // create the row for this package
1081 packageRow = new PackageRow(package, fPackageListener);
1082
1083 // add the row, parent may be NULL (add at top level)
1084 AddRow(packageRow);
1085
1086 // add to hash table for quick lookup of row by package name
1087 fRowByNameTable->Insert(packageRow);
1088
1089 // make sure the row is initially expanded
1090 ExpandOrCollapse(packageRow, true);
1091
1092 fItemCountView->SetItemCount(CountRows());
1093 }
1094
1095
1096 void
RemovePackage(const PackageInfoRef & package)1097 PackageListView::RemovePackage(const PackageInfoRef& package)
1098 {
1099 PackageRow* packageRow = _FindRow(package);
1100 if (packageRow == NULL)
1101 return;
1102
1103 fRowByNameTable->Remove(packageRow);
1104
1105 RemoveRow(packageRow);
1106 delete packageRow;
1107
1108 fItemCountView->SetItemCount(CountRows());
1109 }
1110
1111
1112 void
SelectPackage(const PackageInfoRef & package)1113 PackageListView::SelectPackage(const PackageInfoRef& package)
1114 {
1115 fIgnoreSelectionChanged = true;
1116
1117 PackageRow* row = _FindRow(package);
1118 BRow* selected = CurrentSelection();
1119 if (row != selected)
1120 DeselectAll();
1121 if (row != NULL) {
1122 AddToSelection(row);
1123 SetFocusRow(row, false);
1124 ScrollTo(row);
1125 }
1126
1127 fIgnoreSelectionChanged = false;
1128 }
1129
1130
1131 void
AttachWorkStatusView(WorkStatusView * view)1132 PackageListView::AttachWorkStatusView(WorkStatusView* view)
1133 {
1134 fWorkStatusView = view;
1135 }
1136
1137
1138 PackageRow*
_FindRow(const PackageInfoRef & package)1139 PackageListView::_FindRow(const PackageInfoRef& package)
1140 {
1141 if (!package.IsSet())
1142 return NULL;
1143 return fRowByNameTable->Lookup(package->Name().String());
1144 }
1145
1146
1147 PackageRow*
_FindRow(const BString & packageName)1148 PackageListView::_FindRow(const BString& packageName)
1149 {
1150 return fRowByNameTable->Lookup(packageName.String());
1151 }
1152