Tracker: Redraw Trash icon when pose attribute updates.

* Don't set Trash icon in FSGetTrashDir anymore, do it in TrashWatcher.
* Write vector icon to attr or large/mini icon attrs, but not both.
* Only update Trash icon attribute on boot volume, not the hidden trash
  folder on other volumes.
* Monitor Trash icon attribute changes by TrashWatcher in BPoseView.
* Get rid of ShouldUseWellKnownIcon(), update ResetIconFrom().
* Remove model param from BPose::UpdateWidgetAndModel() and always use
  the ResolvedModel() of the pose instead.
* Pass poseLoc into BPose::UpdateWidgetAndModel() in icon-mode.
* Workaround for GetAttrInfo() failing to update removed icon attrs.
* Check fNode not NULL in Model::StatChanged() to prevent crash when
  unmounting volumes with trashed items.
* Filter out 0 capacity volumes checking for Trash directory.

Fixes #8696

Change-Id: Ief2efc07c85866cc0e49468c668e47ec90b855af
Reviewed-on: https://review.haiku-os.org/c/haiku/+/9458
Reviewed-by: waddlesplash <[email protected]>
Reviewed-by: John Scipione <[email protected]>
Tested-by: Commit checker robot <[email protected]>
This commit is contained in:
John Scipione
2025-07-30 04:00:12 +00:00
parent d4b0e30b56
commit 64334a1580
8 changed files with 96 additions and 85 deletions
+3 -25
View File
@@ -2679,32 +2679,10 @@ FSGetTrashDir(BDirectory* trashDir, dev_t dev)
PoseInfo poseInfo; PoseInfo poseInfo;
poseInfo.fInvisible = true; poseInfo.fInvisible = true;
poseInfo.fInitedDirectory = sbuf.st_ino; poseInfo.fInitedDirectory = sbuf.st_ino;
trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, trashDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, sizeof(PoseInfo));
sizeof(PoseInfo));
} }
// set Trash icons (if they haven't already been set) // icon attributes set by TrashWatcher
attr_info attrInfo;
size_t size;
const void* data;
if (trashDir->GetAttrInfo(kAttrLargeIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
data = GetTrackerResources()->LoadResource('ICON', R_TrashIcon, &size);
if (data != NULL)
trashDir->WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
}
if (trashDir->GetAttrInfo(kAttrMiniIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
data = GetTrackerResources()->LoadResource('MICN', R_TrashIcon, &size);
if (data != NULL)
trashDir->WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
}
if (trashDir->GetAttrInfo(kAttrIcon, &attrInfo) == B_ENTRY_NOT_FOUND) {
data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE,
R_TrashIcon, &size);
if (data != NULL)
trashDir->WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
}
return B_OK; return B_OK;
} }
@@ -3233,7 +3211,7 @@ FSCreateTrashDirs()
roster.Rewind(); roster.Rewind();
while (roster.GetNextVolume(&volume) == B_OK) { while (roster.GetNextVolume(&volume) == B_OK) {
if (volume.IsReadOnly() || !volume.IsPersistent()) if (volume.IsReadOnly() || !volume.IsPersistent() || volume.Capacity() == 0)
continue; continue;
BDirectory trashDir; BDirectory trashDir;
+12 -11
View File
@@ -727,16 +727,6 @@ Model::FinishSettingUpType()
} }
bool
Model::ShouldUseWellKnownIcon() const
{
if (fBaseType == kDirectoryNode || fBaseType == kVolumeNode
|| fBaseType == kTrashNode || fBaseType == kDesktopNode)
return !CheckAppIconHint();
return false;
}
bool bool
Model::CheckAppIconHint() const Model::CheckAppIconHint() const
{ {
@@ -770,7 +760,14 @@ Model::ResetIconFrom()
if (InitCheck() != B_OK) if (InitCheck() != B_OK)
return; return;
if (ShouldUseWellKnownIcon()) { bool hasAttrIcon = CheckAppIconHint();
if (hasAttrIcon && (fBaseType == kDesktopNode || fBaseType == kTrashNode)) {
// Desktop or Trash with an icon attribute
fIconFrom = kNode;
return;
} else if (!hasAttrIcon && (fBaseType == kDirectoryNode || fBaseType == kVolumeNode)) {
// No icon attribute override, check if well-known or root directory
BDirectory* directory = dynamic_cast<BDirectory*>(fNode); BDirectory* directory = dynamic_cast<BDirectory*>(fNode);
if (WellKnowEntryList::Match(NodeRef()) > (directory_which)-1) { if (WellKnowEntryList::Match(NodeRef()) > (directory_which)-1) {
fIconFrom = kTrackerSupplied; fIconFrom = kTrackerSupplied;
@@ -780,6 +777,7 @@ Model::ResetIconFrom()
return; return;
} }
} }
fIconFrom = kUnknownSource; fIconFrom = kUnknownSource;
} }
@@ -966,6 +964,9 @@ Model::AttrChanged(const char* attrName)
bool bool
Model::StatChanged() Model::StatChanged()
{ {
if (fNode == NULL)
return false;
ASSERT(IsNodeOpen()); ASSERT(IsNodeOpen());
mode_t oldMode = fStatBuf.st_mode; mode_t oldMode = fStatBuf.st_mode;
fStatus = fNode->GetStat(&fStatBuf); fStatus = fNode->GetStat(&fStatBuf);
-1
View File
@@ -215,7 +215,6 @@ private:
status_t OpenNodeCommon(bool writable); status_t OpenNodeCommon(bool writable);
void SetupBaseType(); void SetupBaseType();
void FinishSettingUpType(); void FinishSettingUpType();
bool ShouldUseWellKnownIcon() const;
bool CheckAppIconHint() const; bool CheckAppIconHint() const;
void DeletePreferredAppVolumeNameLinkTo(); void DeletePreferredAppVolumeNameLinkTo();
void CacheLocalizedName(); void CacheLocalizedName();
+8 -11
View File
@@ -241,18 +241,18 @@ BPose::UpdateAllWidgets(int32, BPoint poseLoc, BPoseView* poseView)
void void
BPose::UpdateWidgetAndModel(Model* resolvedModel, const char* attrName, BPose::UpdateWidgetAndModel(const char* attrName, uint32 attrType, int32, BPoint poseLoc,
uint32 attrType, int32, BPoint poseLoc, BPoseView* poseView, bool visible) BPoseView* poseView, bool visible)
{ {
if (poseView->ViewMode() != kListMode) Model* resolvedModel = ResolvedModel();
poseLoc = Location(poseView);
ASSERT(resolvedModel == NULL || resolvedModel->IsNodeOpen()); ASSERT(resolvedModel == NULL || resolvedModel->IsNodeOpen());
if (attrName != NULL) { if (attrName != NULL) {
// pick up new attributes and find out if icon needs updating // pick up new attributes and find out if icon needs updating
if (resolvedModel->AttrChanged(attrName) && visible) if (visible && resolvedModel != NULL && resolvedModel->InitCheck() == B_OK
&& resolvedModel->AttrChanged(attrName)) {
UpdateIcon(poseLoc, poseView); UpdateIcon(poseLoc, poseView);
}
// ToDo: the following code is wrong, because this sort of hashing // ToDo: the following code is wrong, because this sort of hashing
// may overlap and we get aliasing // may overlap and we get aliasing
@@ -280,11 +280,8 @@ BPose::UpdateWidgetAndModel(Model* resolvedModel, const char* attrName,
// no attr name means check all widgets for stat info changes // no attr name means check all widgets for stat info changes
// pick up stat changes // pick up stat changes
if (resolvedModel && resolvedModel->StatChanged()) { if (visible && resolvedModel != NULL && resolvedModel->InitCheck() == B_OK
if (resolvedModel->InitCheck() != B_OK) && resolvedModel->StatChanged()) {
return;
if (visible)
UpdateIcon(poseLoc, poseView); UpdateIcon(poseLoc, poseView);
} }
+2 -3
View File
@@ -95,9 +95,8 @@ public:
BRect CalcRect(BPoint loc, const BPoseView*, bool minimal_rect = false) const; BRect CalcRect(BPoint loc, const BPoseView*, bool minimal_rect = false) const;
BRect CalcRect(const BPoseView*) const; BRect CalcRect(const BPoseView*) const;
void UpdateAllWidgets(int32 poseIndex, BPoint poseLoc, BPoseView*); void UpdateAllWidgets(int32 poseIndex, BPoint poseLoc, BPoseView*);
void UpdateWidgetAndModel(Model* resolvedModel, const char* attrName, void UpdateWidgetAndModel(const char* attrName, uint32 attrType, int32 poseIndex,
uint32 attrType, int32 poseIndex, BPoint poseLoc, BPoint poseLoc, BPoseView* view, bool visible);
BPoseView* view, bool visible);
bool UpdateVolumeSpaceBar(BVolume* volume); bool UpdateVolumeSpaceBar(BVolume* volume);
void UpdateIcon(BPoint poseLoc, BPoseView*); void UpdateIcon(BPoint poseLoc, BPoseView*);
+45 -13
View File
@@ -1797,15 +1797,16 @@ BPoseView::RemoveRootPose()
void void
BPoseView::CreateTrashPose() BPoseView::CreateTrashPose()
{ {
BVolume volume; BVolume boot;
if (BVolumeRoster().GetBootVolume(&volume) == B_OK) { if (BVolumeRoster().GetBootVolume(&boot) == B_OK) {
BDirectory trash; BDirectory trash;
BEntry entry; BEntry entry;
node_ref ref; node_ref nref;
if (FSGetTrashDir(&trash, volume.Device()) == B_OK if (FSGetTrashDir(&trash, boot.Device()) == B_OK
&& trash.GetEntry(&entry) == B_OK && trash.GetEntry(&entry) == B_OK
&& entry.GetNodeRef(&ref) == B_OK) { && entry.GetNodeRef(&nref) == B_OK) {
WatchNewNode(&ref); WatchNewNode(&nref, B_WATCH_ATTR, BMessenger(this));
// redraw Trash icon when attribute changes
Model* model = new Model(&entry); Model* model = new Model(&entry);
PoseInfo info; PoseInfo info;
ReadPoseInfo(model, &info); ReadPoseInfo(model, &info);
@@ -5873,8 +5874,7 @@ BPoseView::AttributeChanged(const BMessage* message)
if (result == B_OK || result != B_BUSY) if (result == B_OK || result != B_BUSY)
break; break;
PRINT(("poseModel %s busy, retrying in a bit\n", PRINT(("poseModel %s busy, retrying in a bit\n", poseModel->Name()));
poseModel->Name()));
snooze(10000); snooze(10000);
} }
if (result != B_OK) { if (result != B_OK) {
@@ -5888,18 +5888,50 @@ BPoseView::AttributeChanged(const BMessage* message)
if (IsFiltering()) if (IsFiltering())
visible = fFilteredPoseList->FindPose(poseModel->NodeRef(), &index) != NULL; visible = fFilteredPoseList->FindPose(poseModel->NodeRef(), &index) != NULL;
BPoint loc(0, index * fListElemHeight); status_t infoStatus = B_ERROR;
if (attrName != NULL && poseModel->Node() != NULL) { if (attrName != NULL) {
memset(&info, 0, sizeof(attr_info)); memset(&info, 0, sizeof(attr_info));
if (strcmp(attrName, kAttrIcon) == 0
|| strcmp(attrName, kAttrLargeIcon) == 0
|| strcmp(attrName, kAttrMiniIcon) == 0
|| strcmp(attrName, kAttrThumbnail) == 0) {
// set icon type manually in case attribute was removed
if (strcmp(attrName, kAttrIcon) == 0)
info.type = B_VECTOR_ICON_TYPE;
else if (strcmp(attrName, kAttrLargeIcon) == 0)
info.type = 'ICON';
else if (strcmp(attrName, kAttrMiniIcon) == 0)
info.type = 'MICN';
else if (strcmp(attrName, kAttrThumbnail) == 0)
info.type = B_RAW_TYPE;
info.size = 0;
// old size not needed, writing new attr
infoStatus = B_OK;
} else if (poseModel->Node() != NULL) {
// the call below might fail if the attribute has been removed // the call below might fail if the attribute has been removed
poseModel->Node()->GetAttrInfo(attrName, &info); infoStatus = poseModel->Node()->GetAttrInfo(attrName, &info);
pose->UpdateWidgetAndModel(poseModel, attrName, info.type, index, loc, this, visible); }
}
BPoint poseLoc;
if (ViewMode() == kListMode)
poseLoc.Set(0, index * fListElemHeight);
else
poseLoc = pose->Location(this);
if (attrName != NULL) {
// update attr
pose->UpdateWidgetAndModel(attrName, infoStatus == B_OK ? info.type : 0,
index, poseLoc, this, visible);
if (strcmp(attrName, kAttrMIMEType) == 0) if (strcmp(attrName, kAttrMIMEType) == 0)
RefreshMimeTypeList(); RefreshMimeTypeList();
} else { } else {
pose->UpdateWidgetAndModel(poseModel, 0, 0, index, loc, this, visible); // update stat
pose->UpdateWidgetAndModel(0, 0, index, poseLoc, this, visible);
} }
poseModel->CloseNode(); poseModel->CloseNode();
if (IsFiltering()) { if (IsFiltering()) {
if (!visible && FilterPose(pose)) { if (!visible && FilterPose(pose)) {
visible = true; visible = true;
+3 -1
View File
@@ -296,6 +296,9 @@ TTracker::TTracker()
// init Desktop now that pose view is created and window is locked // init Desktop now that pose view is created and window is locked
deskWindow->Init(); deskWindow->Init();
// create this before ReadyToRun() so that the Trash icon gets set
fTrashWatcher = new BTrashWatcher();
} }
@@ -1550,7 +1553,6 @@ TTracker::ReadyToRun()
InstallIndices(); InstallIndices();
InstallTemporaryBackgroundImages(); InstallTemporaryBackgroundImages();
fTrashWatcher = new BTrashWatcher();
fTrashWatcher->Run(); fTrashWatcher->Run();
fClipboardRefsWatcher = new BClipboardRefsWatcher(); fClipboardRefsWatcher = new BClipboardRefsWatcher();
+19 -16
View File
@@ -154,25 +154,28 @@ BTrashWatcher::MessageReceived(BMessage* message)
void void
BTrashWatcher::UpdateTrashIcons() BTrashWatcher::UpdateTrashIcons()
{ {
BVolumeRoster roster; // only update Trash icon attributes on boot volume
BVolume volume; BVolume boot;
roster.Rewind();
BDirectory trashDir; BDirectory trashDir;
while (roster.GetNextVolume(&volume) == B_OK) { if (BVolumeRoster().GetBootVolume(&boot) == B_OK
if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) { && FSGetTrashDir(&trashDir, boot.Device()) == B_OK) {
// pull out the icons for the current trash state from resources // pull out the icons for the current trash state from resources
// and apply them onto the trash directory node // and apply them onto the trash directory node
size_t vectorSize = 0; int32 id = fTrashFull ? R_TrashFullIcon : R_TrashIcon;
const void* vectorData = GetTrackerResources()->LoadResource( size_t size = 0;
B_VECTOR_ICON_TYPE, const void* data = GetTrackerResources()->LoadResource(B_VECTOR_ICON_TYPE, id, &size);
fTrashFull ? R_TrashFullIcon : R_TrashIcon, &vectorSize); if (data != NULL && size > 0) {
// write vector icon attribute
trashDir.WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, data, size);
} else {
// write large and mini icon attributes
data = GetTrackerResources()->LoadResource('ICON', id, &size);
if (data != NULL && size > 0)
trashDir.WriteAttr(kAttrLargeIcon, 'ICON', 0, data, size);
if (vectorData) { data = GetTrackerResources()->LoadResource('MICN', id, &size);
trashDir.WriteAttr(kAttrIcon, B_VECTOR_ICON_TYPE, 0, if (data != NULL && size > 0)
vectorData, vectorSize); trashDir.WriteAttr(kAttrMiniIcon, 'MICN', 0, data, size);
} else
TRESPASS();
} }
} }
} }
@@ -206,7 +209,7 @@ BTrashWatcher::CheckTrashDirs()
volRoster.Rewind(); volRoster.Rewind();
BVolume volume; BVolume volume;
while (volRoster.GetNextVolume(&volume) == B_OK) { while (volRoster.GetNextVolume(&volume) == B_OK) {
if (volume.IsReadOnly() || !volume.IsPersistent()) if (volume.IsReadOnly() || !volume.IsPersistent() || volume.Capacity() == 0)
continue; continue;
BDirectory trashDir; BDirectory trashDir;