* Copyright 2018-2024, Andrew Lindesay, <apl@lindesay.co.nz>.
* Copyright 2017, Julian Harnath, <julian.harnath@rwth-aachen.de>.
* Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>.
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2013, Rene Gollent, <rene@gollent.com>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "PackageListView.h"
#include <algorithm>
#include <stdio.h>
#include <Autolock.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <NumberFormat.h>
#include <ScrollBar.h>
#include <StringFormat.h>
#include <StringForSize.h>
#include <package/hpkg/Strings.h>
#include <Window.h>
#include "LocaleUtils.h"
#include "Logger.h"
#include "RatingUtils.h"
#include "SharedIcons.h"
#include "WorkStatusView.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PackageListView"
static const char* skPackageStateAvailable = B_TRANSLATE_MARK("Available");
static const char* skPackageStateUninstalled = B_TRANSLATE_MARK("Uninstalled");
static const char* skPackageStateActive = B_TRANSLATE_MARK("Active");
static const char* skPackageStateInactive = B_TRANSLATE_MARK("Inactive");
static const char* skPackageStatePending = B_TRANSLATE_MARK(
"Pending" B_UTF8_ELLIPSIS);
inline BString
package_state_to_string(PackageInfoRef ref)
{
static BNumberFormat numberFormat;
switch (ref->State()) {
case NONE:
return B_TRANSLATE(skPackageStateAvailable);
case INSTALLED:
return B_TRANSLATE(skPackageStateInactive);
case ACTIVATED:
return B_TRANSLATE(skPackageStateActive);
case UNINSTALLED:
return B_TRANSLATE(skPackageStateUninstalled);
case DOWNLOADING:
{
BString data;
float fraction = ref->DownloadProgress();
if (numberFormat.FormatPercent(data, fraction) != B_OK) {
HDERROR("unable to format the percentage");
data = "???";
}
return data;
}
case PENDING:
return B_TRANSLATE(skPackageStatePending);
}
return B_TRANSLATE("Unknown");
}
class PackageIconAndTitleField : public BStringField {
typedef BStringField Inherited;
public:
PackageIconAndTitleField(
const char* packageName,
const char* string);
virtual ~PackageIconAndTitleField();
const BString PackageName() const
{ return fPackageName; }
private:
const BString fPackageName;
};
class RatingField : public BField {
public:
RatingField(float rating);
virtual ~RatingField();
void SetRating(float rating);
float Rating() const
{ return fRating; }
private:
float fRating;
};
class SizeField : public BStringField {
public:
SizeField(double size);
virtual ~SizeField();
void SetSize(double size);
double Size() const
{ return fSize; }
private:
double fSize;
};
class DateField : public BStringField {
public:
DateField(uint64 millisSinceEpoc);
virtual ~DateField();
void SetMillisSinceEpoc(uint64 millisSinceEpoc);
uint64 MillisSinceEpoc() const
{ return fMillisSinceEpoc; }
private:
void _SetMillisSinceEpoc(uint64 millisSinceEpoc);
private:
uint64 fMillisSinceEpoc;
};
class PackageColumn : public BTitledColumn {
typedef BTitledColumn Inherited;
public:
PackageColumn(Model* model,
const char* title,
float width, float minWidth,
float maxWidth, uint32 truncateMode,
alignment align = B_ALIGN_LEFT);
virtual ~PackageColumn();
virtual void DrawField(BField* field, BRect rect,
BView* parent);
virtual int CompareFields(BField* field1, BField* field2);
virtual float GetPreferredWidth(BField* field,
BView* parent) const;
virtual bool AcceptsField(const BField* field) const;
static void InitTextMargin(BView* parent);
private:
Model* fModel;
uint32 fTruncateMode;
RatingStarsMetrics* fRatingsMetrics;
static float sTextMargin;
};
class PackageRow : public BRow {
typedef BRow Inherited;
public:
PackageRow(
const PackageInfoRef& package,
PackageListener* listener);
virtual ~PackageRow();
const PackageInfoRef& Package() const
{ return fPackage; }
void UpdateIconAndTitle();
void UpdateSummary();
void UpdateState();
void UpdateRating();
void UpdateSize();
void UpdateRepository();
void UpdateVersion();
void UpdateVersionCreateTimestamp();
PackageRow*& NextInHash()
{ return fNextInHash; }
private:
PackageInfoRef fPackage;
PackageInfoListenerRef
fPackageListener;
PackageRow* fNextInHash;
};
enum {
MSG_UPDATE_PACKAGE = 'updp'
};
class PackageListener : public PackageInfoListener {
public:
PackageListener(PackageListView* view)
:
fView(view)
{
}
virtual ~PackageListener()
{
}
virtual void PackageChanged(const PackageInfoEvent& event)
{
BMessenger messenger(fView);
if (!messenger.IsValid())
return;
const PackageInfo& package = *event.Package().Get();
BMessage message(MSG_UPDATE_PACKAGE);
message.AddString("name", package.Name());
message.AddUInt32("changes", event.Changes());
messenger.SendMessage(&message);
}
private:
PackageListView* fView;
};
PackageIconAndTitleField::PackageIconAndTitleField(const char* packageName,
const char* string)
:
Inherited(string),
fPackageName(packageName)
{
}
PackageIconAndTitleField::~PackageIconAndTitleField()
{
}
RatingField::RatingField(float rating)
:
fRating(RATING_MISSING)
{
SetRating(rating);
}
RatingField::~RatingField()
{
}
void
RatingField::SetRating(float rating)
{
fRating = rating;
}
SizeField::SizeField(double size)
:
BStringField(""),
fSize(-1.0)
{
SetSize(size);
}
SizeField::~SizeField()
{
}
void
SizeField::SetSize(double size)
{
if (size < 0.0)
size = 0.0;
if (size == fSize)
return;
BString sizeString;
if (size == 0) {
sizeString = B_TRANSLATE_CONTEXT("-", "no package size");
} else {
char buffer[256];
sizeString = string_for_size(size, buffer, sizeof(buffer));
}
fSize = size;
SetString(sizeString.String());
}
DateField::DateField(uint64 millisSinceEpoc)
:
BStringField(""),
fMillisSinceEpoc(0)
{
_SetMillisSinceEpoc(millisSinceEpoc);
}
DateField::~DateField()
{
}
void
DateField::SetMillisSinceEpoc(uint64 millisSinceEpoc)
{
if (millisSinceEpoc == fMillisSinceEpoc)
return;
_SetMillisSinceEpoc(millisSinceEpoc);
}
void
DateField::_SetMillisSinceEpoc(uint64 millisSinceEpoc)
{
BString dateString;
if (millisSinceEpoc == 0)
dateString = B_TRANSLATE_CONTEXT("-", "no package publish");
else
dateString = LocaleUtils::TimestampToDateString(millisSinceEpoc);
fMillisSinceEpoc = millisSinceEpoc;
SetString(dateString.String());
}
float PackageColumn::sTextMargin = 0.0;
PackageColumn::PackageColumn(Model* model, const char* title, float width,
float minWidth, float maxWidth, uint32 truncateMode, alignment align)
:
Inherited(title, width, minWidth, maxWidth, align),
fModel(model),
fTruncateMode(truncateMode)
{
SetWantsEvents(true);
BSize ratingStarSize = SharedIcons::IconStarBlue12Scaled()->Bitmap()->Bounds().Size();
fRatingsMetrics = new RatingStarsMetrics(ratingStarSize);
}
PackageColumn::~PackageColumn()
{
delete fRatingsMetrics;
}
void
PackageColumn::DrawField(BField* field, BRect rect, BView* parent)
{
PackageIconAndTitleField* packageIconAndTitleField
= dynamic_cast<PackageIconAndTitleField*>(field);
BStringField* stringField = dynamic_cast<BStringField*>(field);
RatingField* ratingField = dynamic_cast<RatingField*>(field);
if (packageIconAndTitleField != NULL) {
BSize iconSize = BControlLook::ComposeIconSize(16);
BRect r(BPoint(0, 0), iconSize);
float x = 0.0;
float y = rect.top + ((rect.Height() - r.Height()) / 2) - 1;
float width = 0.0;
switch (Alignment()) {
default:
case B_ALIGN_LEFT:
case B_ALIGN_CENTER:
x = rect.left + sTextMargin;
width = rect.right - (x + r.Width()) - (2 * sTextMargin);
r.Set(x + r.Width(), rect.top, rect.right - width, rect.bottom);
break;
case B_ALIGN_RIGHT:
x = rect.right - sTextMargin - r.Width();
width = (x - rect.left - (2 * sTextMargin));
r.Set(rect.left, rect.top, rect.left + width, rect.bottom);
break;
}
if (width != packageIconAndTitleField->Width()) {
BString truncatedString(packageIconAndTitleField->String());
parent->TruncateString(&truncatedString, fTruncateMode, width + 2);
packageIconAndTitleField->SetClippedString(truncatedString.String());
packageIconAndTitleField->SetWidth(width);
}
BitmapHolderRef bitmapHolderRef;
status_t bitmapResult;
bitmapResult = fModel->GetPackageIconRepository().GetIcon(
packageIconAndTitleField->PackageName(), iconSize.Width() + 1, bitmapHolderRef);
if (bitmapResult == B_OK) {
if (bitmapHolderRef.IsSet()) {
const BBitmap* bitmap = bitmapHolderRef->Bitmap();
if (bitmap != NULL && bitmap->IsValid()) {
parent->SetDrawingMode(B_OP_ALPHA);
BRect viewRect(BPoint(x, y), iconSize);
parent->DrawBitmap(bitmap, bitmap->Bounds(), viewRect);
parent->SetDrawingMode(B_OP_OVER);
}
}
}
DrawString(packageIconAndTitleField->ClippedString(), parent, r);
} else if (stringField != NULL) {
float width = rect.Width() - (2 * sTextMargin);
if (width != stringField->Width()) {
BString truncatedString(stringField->String());
parent->TruncateString(&truncatedString, fTruncateMode, width + 2);
stringField->SetClippedString(truncatedString.String());
stringField->SetWidth(width);
}
DrawString(stringField->ClippedString(), parent, rect);
} else if (ratingField != NULL) {
float width = rect.Width();
float padding = be_control_look->ComposeSpacing(B_USE_SMALL_SPACING);
bool isRatingValid = ratingField->Rating() >= RATING_MIN;
if (!isRatingValid || width < fRatingsMetrics->Size().Width() + padding * 2.0) {
BString ratingAsText = "-";
if (isRatingValid)
ratingAsText.SetToFormat("%.1f", ratingField->Rating());
float ratingAsTextWidth = parent->StringWidth(ratingAsText);
if (ratingAsTextWidth + padding * 2.0 < width) {
font_height fontHeight;
parent->GetFontHeight(&fontHeight);
float fullHeight = fontHeight.ascent + fontHeight.descent;
float y = rect.top + (rect.Height() - fullHeight) / 2 + fontHeight.ascent;
parent->DrawString(ratingAsText, BPoint(rect.left + padding, y));
}
} else {
const BBitmap* starIcon = SharedIcons::IconStarBlue12Scaled()->Bitmap();
float ratingsStarsHeight = fRatingsMetrics->Size().Height();
BPoint starsPt(floorf(rect.LeftTop().x + padding),
floorf(rect.LeftTop().y + (rect.Size().Height() / 2.0) - ratingsStarsHeight / 2.0));
RatingUtils::Draw(parent, starsPt, ratingField->Rating(), starIcon);
}
}
}
int
PackageColumn::CompareFields(BField* field1, BField* field2)
{
DateField* dateField1 = dynamic_cast<DateField*>(field1);
DateField* dateField2 = dynamic_cast<DateField*>(field2);
if (dateField1 != NULL && dateField2 != NULL) {
if (dateField1->MillisSinceEpoc() > dateField2->MillisSinceEpoc())
return -1;
else if (dateField1->MillisSinceEpoc() < dateField2->MillisSinceEpoc())
return 1;
return 0;
}
SizeField* sizeField1 = dynamic_cast<SizeField*>(field1);
SizeField* sizeField2 = dynamic_cast<SizeField*>(field2);
if (sizeField1 != NULL && sizeField2 != NULL) {
if (sizeField1->Size() > sizeField2->Size())
return -1;
else if (sizeField1->Size() < sizeField2->Size())
return 1;
return 0;
}
BStringField* stringField1 = dynamic_cast<BStringField*>(field1);
BStringField* stringField2 = dynamic_cast<BStringField*>(field2);
if (stringField1 != NULL && stringField2 != NULL) {
return strcasecmp(stringField1->String(), stringField2->String());
}
RatingField* ratingField1 = dynamic_cast<RatingField*>(field1);
RatingField* ratingField2 = dynamic_cast<RatingField*>(field2);
if (ratingField1 != NULL && ratingField2 != NULL) {
if (ratingField1->Rating() > ratingField2->Rating())
return -1;
else if (ratingField1->Rating() < ratingField2->Rating())
return 1;
return 0;
}
return Inherited::CompareFields(field1, field2);
}
float
PackageColumn::GetPreferredWidth(BField *_field, BView* parent) const
{
PackageIconAndTitleField* packageIconAndTitleField
= dynamic_cast<PackageIconAndTitleField*>(_field);
BStringField* stringField = dynamic_cast<BStringField*>(_field);
float parentWidth = Inherited::GetPreferredWidth(_field, parent);
float width = 0.0;
if (packageIconAndTitleField) {
BFont font;
parent->GetFont(&font);
width = font.StringWidth(packageIconAndTitleField->String())
+ 3 * sTextMargin;
width += 16;
} else if (stringField) {
BFont font;
parent->GetFont(&font);
width = font.StringWidth(stringField->String()) + 2 * sTextMargin;
}
return max_c(width, parentWidth);
}
bool
PackageColumn::AcceptsField(const BField* field) const
{
return dynamic_cast<const BStringField*>(field) != NULL
|| dynamic_cast<const RatingField*>(field) != NULL;
}
void
PackageColumn::InitTextMargin(BView* parent)
{
BFont font;
parent->GetFont(&font);
sTextMargin = ceilf(font.Size() * 0.8);
}
enum {
kTitleColumn,
kRatingColumn,
kDescriptionColumn,
kSizeColumn,
kStatusColumn,
kRepositoryColumn,
kVersionColumn,
kVersionCreateTimestampColumn,
};
PackageRow::PackageRow(const PackageInfoRef& packageRef,
PackageListener* packageListener)
:
Inherited(ceilf(be_plain_font->Size() * 1.8f)),
fPackage(packageRef),
fPackageListener(packageListener),
fNextInHash(NULL)
{
if (!packageRef.IsSet())
return;
UpdateIconAndTitle();
UpdateRating();
UpdateSummary();
UpdateSize();
UpdateState();
UpdateRepository();
UpdateVersion();
UpdateVersionCreateTimestamp();
packageRef->AddListener(fPackageListener);
}
PackageRow::~PackageRow()
{
if (fPackage.IsSet())
fPackage->RemoveListener(fPackageListener);
}
void
PackageRow::UpdateIconAndTitle()
{
if (!fPackage.IsSet())
return;
SetField(new PackageIconAndTitleField(
fPackage->Name(), fPackage->Title()), kTitleColumn);
}
void
PackageRow::UpdateState()
{
if (!fPackage.IsSet())
return;
SetField(new BStringField(package_state_to_string(fPackage)),
kStatusColumn);
}
void
PackageRow::UpdateSummary()
{
if (!fPackage.IsSet())
return;
SetField(new BStringField(fPackage->ShortDescription()),
kDescriptionColumn);
}
void
PackageRow::UpdateRating()
{
if (!fPackage.IsSet())
return;
RatingSummary summary = fPackage->CalculateRatingSummary();
SetField(new RatingField(summary.averageRating), kRatingColumn);
}
void
PackageRow::UpdateSize()
{
if (!fPackage.IsSet())
return;
SetField(new SizeField(fPackage->Size()), kSizeColumn);
}
void
PackageRow::UpdateRepository()
{
if (!fPackage.IsSet())
return;
SetField(new BStringField(fPackage->DepotName()), kRepositoryColumn);
}
void
PackageRow::UpdateVersion()
{
if (!fPackage.IsSet())
return;
SetField(new BStringField(fPackage->Version().ToString()), kVersionColumn);
}
void
PackageRow::UpdateVersionCreateTimestamp()
{
if (!fPackage.IsSet())
return;
SetField(new DateField(fPackage->VersionCreateTimestamp()),
kVersionCreateTimestampColumn);
}
class PackageListView::ItemCountView : public BView {
public:
ItemCountView()
:
BView("item count view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
fItemCount(0)
{
BFont font(be_plain_font);
font.SetSize(font.Size() * 0.75f);
SetFont(&font);
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
SetLowUIColor(ViewUIColor());
SetHighUIColor(LowUIColor(), B_DARKEN_4_TINT);
fMinSize = BSize(StringWidth(_DeriveLabel(999999)) + 10,
be_control_look->GetScrollBarWidth());
}
virtual BSize MinSize()
{
return fMinSize;
}
virtual BSize PreferredSize()
{
return MinSize();
}
virtual BSize MaxSize()
{
return MinSize();
}
virtual void Draw(BRect updateRect)
{
FillRect(updateRect, B_SOLID_LOW);
font_height fontHeight;
GetFontHeight(&fontHeight);
BRect bounds(Bounds());
float width = StringWidth(fLabel);
BPoint offset;
offset.x = bounds.left + (bounds.Width() - width) / 2.0f;
offset.y = bounds.top + (bounds.Height()
- (fontHeight.ascent + fontHeight.descent)) / 2.0f
+ fontHeight.ascent;
DrawString(fLabel, offset);
}
void SetItemCount(int32 count)
{
if (count == fItemCount)
return;
fItemCount = count;
fLabel = _DeriveLabel(fItemCount);
Invalidate();
}
private:
table-view are updated. Derivation of the plural for some
languages such as Russian can be slow so this method should be
called sparingly.
*/
BString _DeriveLabel(int32 count) const
{
static BStringFormat format(B_TRANSLATE("{0, plural, "
"one{# item} other{# items}}"));
BString label;
format.Format(label, count);
return label;
}
int32 fItemCount;
BString fLabel;
BSize fMinSize;
};
struct PackageListView::RowByNameHashDefinition {
typedef const char* KeyType;
typedef PackageRow ValueType;
size_t HashKey(const char* key) const
{
return BPackageKit::BHPKG::BPrivate::hash_string(key);
}
size_t Hash(PackageRow* value) const
{
return BPackageKit::BHPKG::BPrivate::hash_string(
value->Package()->Name().String());
}
bool Compare(const char* key, PackageRow* value) const
{
return value->Package()->Name() == key;
}
ValueType*& GetLink(PackageRow* value) const
{
return value->NextInHash();
}
};
PackageListView::PackageListView(Model* model)
:
BColumnListView(B_TRANSLATE("All packages"), 0, B_FANCY_BORDER, true),
fModel(model),
fPackageListener(new(std::nothrow) PackageListener(this)),
fRowByNameTable(new RowByNameTable()),
fWorkStatusView(NULL),
fIgnoreSelectionChanged(false)
{
float scale = be_plain_font->Size() / 12.f;
float spacing = be_control_look->DefaultItemSpacing() * 2;
AddColumn(new PackageColumn(fModel, B_TRANSLATE("Name"),
150 * scale, 50 * scale, 300 * scale,
B_TRUNCATE_MIDDLE), kTitleColumn);
AddColumn(new PackageColumn(fModel, B_TRANSLATE("Rating"),
80 * scale, 50 * scale, 100 * scale,
B_TRUNCATE_MIDDLE), kRatingColumn);
AddColumn(new PackageColumn(fModel, B_TRANSLATE("Description"),
300 * scale, 80 * scale, 1000 * scale,
B_TRUNCATE_MIDDLE), kDescriptionColumn);
PackageColumn* sizeColumn = new PackageColumn(fModel, B_TRANSLATE("Size"),
spacing + StringWidth("9999.99 KiB"), 50 * scale,
140 * scale, B_TRUNCATE_END);
sizeColumn->SetAlignment(B_ALIGN_RIGHT);
AddColumn(sizeColumn, kSizeColumn);
AddColumn(new PackageColumn(fModel, B_TRANSLATE("Status"),
spacing + StringWidth(B_TRANSLATE("Available")), 60 * scale,
140 * scale, B_TRUNCATE_END), kStatusColumn);
AddColumn(new PackageColumn(fModel, B_TRANSLATE("Repository"),
120 * scale, 50 * scale, 200 * scale,
B_TRUNCATE_MIDDLE), kRepositoryColumn);
SetColumnVisible(kRepositoryColumn, false);
float widthWithPlacboVersion = spacing
+ StringWidth("8.2.3176-2");
AddColumn(new PackageColumn(fModel, B_TRANSLATE("Version"),
widthWithPlacboVersion, widthWithPlacboVersion,
widthWithPlacboVersion + (50 * scale),
B_TRUNCATE_MIDDLE), kVersionColumn);
float widthWithPlaceboDate = spacing
+ StringWidth(LocaleUtils::TimestampToDateString(
static_cast<uint64>(1000)));
AddColumn(new PackageColumn(fModel, B_TRANSLATE("Date"),
widthWithPlaceboDate, widthWithPlaceboDate,
widthWithPlaceboDate + (50 * scale),
B_TRUNCATE_END), kVersionCreateTimestampColumn);
fItemCountView = new ItemCountView();
AddStatusView(fItemCountView);
}
PackageListView::~PackageListView()
{
Clear();
delete fPackageListener;
}
void
PackageListView::AttachedToWindow()
{
BColumnListView::AttachedToWindow();
PackageColumn::InitTextMargin(ScrollView());
}
void
PackageListView::AllAttached()
{
BColumnListView::AllAttached();
SetSortingEnabled(true);
SetSortColumn(ColumnAt(0), false, true);
}
void
PackageListView::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_UPDATE_PACKAGE:
{
BString name;
uint32 changes;
if (message->FindString("name", &name) != B_OK
|| message->FindUInt32("changes", &changes) != B_OK) {
break;
}
BAutolock _(fModel->Lock());
PackageRow* row = _FindRow(name);
if (row != NULL) {
if ((changes & PKG_CHANGED_TITLE) != 0)
row->UpdateIconAndTitle();
if ((changes & PKG_CHANGED_SUMMARY) != 0)
row->UpdateSummary();
if ((changes & PKG_CHANGED_RATINGS) != 0)
row->UpdateRating();
if ((changes & PKG_CHANGED_STATE) != 0)
row->UpdateState();
if ((changes & PKG_CHANGED_SIZE) != 0)
row->UpdateSize();
if ((changes & PKG_CHANGED_ICON) != 0)
row->UpdateIconAndTitle();
if ((changes & PKG_CHANGED_DEPOT) != 0)
row->UpdateRepository();
if ((changes & PKG_CHANGED_VERSION) != 0)
row->UpdateVersion();
if ((changes & PKG_CHANGED_VERSION_CREATE_TIMESTAMP) != 0)
row->UpdateVersionCreateTimestamp();
}
break;
}
default:
BColumnListView::MessageReceived(message);
break;
}
}
void
PackageListView::SelectionChanged()
{
BColumnListView::SelectionChanged();
if (fIgnoreSelectionChanged)
return;
BMessage message(MSG_PACKAGE_SELECTED);
PackageRow* selected = dynamic_cast<PackageRow*>(CurrentSelection());
if (selected != NULL)
message.AddString("name", selected->Package()->Name());
Window()->PostMessage(&message);
}
void
PackageListView::Clear()
{
fItemCountView->SetItemCount(0);
BColumnListView::Clear();
fRowByNameTable->Clear();
}
void
PackageListView::AddPackage(const PackageInfoRef& package)
{
PackageRow* packageRow = _FindRow(package);
if (packageRow != NULL)
return;
BAutolock _(fModel->Lock());
packageRow = new PackageRow(package, fPackageListener);
AddRow(packageRow);
fRowByNameTable->Insert(packageRow);
ExpandOrCollapse(packageRow, true);
fItemCountView->SetItemCount(CountRows());
}
void
PackageListView::RemovePackage(const PackageInfoRef& package)
{
PackageRow* packageRow = _FindRow(package);
if (packageRow == NULL)
return;
fRowByNameTable->Remove(packageRow);
RemoveRow(packageRow);
delete packageRow;
fItemCountView->SetItemCount(CountRows());
}
void
PackageListView::SelectPackage(const PackageInfoRef& package)
{
fIgnoreSelectionChanged = true;
PackageRow* row = _FindRow(package);
BRow* selected = CurrentSelection();
if (row != selected)
DeselectAll();
if (row != NULL) {
AddToSelection(row);
SetFocusRow(row, false);
ScrollTo(row);
}
fIgnoreSelectionChanged = false;
}
void
PackageListView::AttachWorkStatusView(WorkStatusView* view)
{
fWorkStatusView = view;
}
PackageRow*
PackageListView::_FindRow(const PackageInfoRef& package)
{
if (!package.IsSet())
return NULL;
return fRowByNameTable->Lookup(package->Name().String());
}
PackageRow*
PackageListView::_FindRow(const BString& packageName)
{
return fRowByNameTable->Lookup(packageName.String());
}