* StringFromStream() did not work correctly for empty strings (messed up the stream
position). * StringFromStream() called BString::LockBuffer() with "length", but touched "length + 1" bytes. * Prepared for the new "display as" FileTypes feature. * The "DefaultQueryTemplate" folder now adds the MIME type of the folder to the attribute menu for simplified editing (before, you had to move a file with a matching file type into that folder to be able to add the attributes you likely wanted to see). git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21509 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
parent
985d6b5437
commit
eeb608e1c7
@ -78,6 +78,7 @@ All rights reserved.
|
||||
#include "Navigator.h"
|
||||
#include "NavMenu.h"
|
||||
#include "PoseView.h"
|
||||
#include "QueryContainerWindow.h"
|
||||
#include "SelectionWindow.h"
|
||||
#include "TitleView.h"
|
||||
#include "Tracker.h"
|
||||
@ -2966,18 +2967,30 @@ BContainerWindow::LoadAddOn(BMessage *message)
|
||||
}
|
||||
|
||||
|
||||
BMenuItem *
|
||||
BContainerWindow::NewAttributeMenuItem(const char *label, const char *attrName,
|
||||
int32 attrType, float attrWidth, int32 attrAlign, bool attrEditable, bool attrStatField)
|
||||
BMenuItem *
|
||||
BContainerWindow::NewAttributeMenuItem(const char *label, const char *name,
|
||||
int32 type, float width, int32 align, bool editable, bool statField)
|
||||
{
|
||||
return NewAttributeMenuItem(label, name, type, NULL, width, align,
|
||||
editable, statField);
|
||||
}
|
||||
|
||||
|
||||
BMenuItem *
|
||||
BContainerWindow::NewAttributeMenuItem(const char *label, const char *name,
|
||||
int32 type, const char* displayAs, float width, int32 align,
|
||||
bool editable, bool statField)
|
||||
{
|
||||
BMessage *message = new BMessage(kAttributeItem);
|
||||
message->AddString("attr_name", attrName);
|
||||
message->AddInt32("attr_type", attrType);
|
||||
message->AddInt32("attr_hash", (int32)AttrHashString(attrName, (uint32)attrType));
|
||||
message->AddFloat("attr_width", attrWidth);
|
||||
message->AddInt32("attr_align", attrAlign);
|
||||
message->AddBool("attr_editable", attrEditable);
|
||||
message->AddBool("attr_statfield", attrStatField);
|
||||
message->AddString("attr_name", name);
|
||||
message->AddInt32("attr_type", type);
|
||||
message->AddInt32("attr_hash", (int32)AttrHashString(name, (uint32)type));
|
||||
message->AddFloat("attr_width", width);
|
||||
message->AddInt32("attr_align", align);
|
||||
if (displayAs != NULL)
|
||||
message->AddString("attr_display_as", displayAs);
|
||||
message->AddBool("attr_editable", editable);
|
||||
message->AddBool("attr_statfield", statField);
|
||||
|
||||
BMenuItem *menuItem = new BMenuItem(label, message);
|
||||
menuItem->SetTarget(PoseView());
|
||||
@ -3110,21 +3123,33 @@ BContainerWindow::AddMimeTypesToMenu(BMenu *menu)
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove old mime menu:
|
||||
int32 removeIndex = count - 1;
|
||||
while (menu->ItemAt(removeIndex)->Submenu() != NULL) {
|
||||
// Remove old mime menu:
|
||||
int32 removeIndex = count - 1;
|
||||
while (menu->ItemAt(removeIndex)->Submenu() != NULL) {
|
||||
delete menu->RemoveItem(removeIndex);
|
||||
removeIndex--;
|
||||
}
|
||||
|
||||
// Add a separator item if there is none yet
|
||||
if (dynamic_cast<BSeparatorItem *>(menu->ItemAt(removeIndex)) == NULL)
|
||||
removeIndex--;
|
||||
}
|
||||
|
||||
// Add a separator item if there is none yet
|
||||
if (dynamic_cast<BSeparatorItem *>(menu->ItemAt(removeIndex)) == NULL)
|
||||
menu->AddSeparatorItem();
|
||||
|
||||
// Add MIME type in case we're a default query type window
|
||||
BPath path;
|
||||
if (TargetModel() != NULL) {
|
||||
TargetModel()->GetPath(&path);
|
||||
if (strstr(path.Path(), "/" kQueryTemplates "/") != NULL) {
|
||||
// demangle MIME type name
|
||||
BString name(TargetModel()->Name());
|
||||
name.ReplaceFirst('_', '/');
|
||||
|
||||
PoseView()->AddMimeType(name.String());
|
||||
}
|
||||
}
|
||||
|
||||
int32 typeCount = PoseView()->CountMimeTypes();
|
||||
|
||||
for (int32 index = 0; index < typeCount; index++) {
|
||||
|
||||
bool shouldAdd = true;
|
||||
const char *signature = PoseView()->MimeTypeAt(index);
|
||||
|
||||
@ -3144,8 +3169,8 @@ BContainerWindow::AddMimeTypesToMenu(BMenu *menu)
|
||||
}
|
||||
|
||||
if (shouldAdd) {
|
||||
BMessage attr_msg;
|
||||
char desc[B_MIME_TYPE_LENGTH];
|
||||
BMessage attrInfo;
|
||||
char description[B_MIME_TYPE_LENGTH];
|
||||
const char *nameToAdd = signature;
|
||||
|
||||
BMimeType mimetype(signature);
|
||||
@ -3154,43 +3179,39 @@ BContainerWindow::AddMimeTypesToMenu(BMenu *menu)
|
||||
continue;
|
||||
|
||||
// only add things to menu which have "user-visible" data
|
||||
if (mimetype.GetAttrInfo(&attr_msg) != B_OK)
|
||||
if (mimetype.GetAttrInfo(&attrInfo) != B_OK)
|
||||
continue;
|
||||
|
||||
if (mimetype.GetShortDescription(desc) == B_OK && desc[0])
|
||||
nameToAdd = desc;
|
||||
if (mimetype.GetShortDescription(description) == B_OK
|
||||
&& description[0])
|
||||
nameToAdd = description;
|
||||
|
||||
// go through each field in meta mime and add it to a menu
|
||||
// go through each field in meta mime and add it to a menu
|
||||
BMenu *localMenu = 0;
|
||||
int32 index = -1;
|
||||
const char *str;
|
||||
|
||||
while (attr_msg.FindString("attr:public_name", ++index, &str) == B_OK) {
|
||||
if (!attr_msg.FindBool("attr:viewable", index))
|
||||
while (attrInfo.FindString("attr:public_name", ++index, &str) == B_OK) {
|
||||
if (!attrInfo.FindBool("attr:viewable", index)) {
|
||||
// don't add if attribute not viewable
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
int32 type;
|
||||
int32 align;
|
||||
int32 width;
|
||||
bool editable;
|
||||
|
||||
const char *attrName;
|
||||
|
||||
if (attr_msg.FindString("attr:name", index, &attrName) != B_OK)
|
||||
|
||||
if (attrInfo.FindString("attr:name", index, &attrName) != B_OK
|
||||
|| attrInfo.FindInt32("attr:type", index, &type) != B_OK
|
||||
|| attrInfo.FindBool("attr:editable", index, &editable) != B_OK
|
||||
|| attrInfo.FindInt32("attr:width", index, &width) != B_OK
|
||||
|| attrInfo.FindInt32("attr:alignment", index, &align) != B_OK)
|
||||
continue;
|
||||
|
||||
if (attr_msg.FindInt32("attr:type", index, &type) != B_OK)
|
||||
continue;
|
||||
|
||||
if (attr_msg.FindBool("attr:editable", index, &editable) != B_OK)
|
||||
continue;
|
||||
|
||||
if (attr_msg.FindInt32("attr:width", index, &width) != B_OK)
|
||||
continue;
|
||||
|
||||
if (attr_msg.FindInt32("attr:alignment", index, &align) != B_OK)
|
||||
continue;
|
||||
BString displayAs;
|
||||
attrInfo.FindString("attr:display_as", index, &displayAs);
|
||||
|
||||
if (!localMenu) {
|
||||
// do a lazy allocation of the menu
|
||||
@ -3199,8 +3220,8 @@ BContainerWindow::AddMimeTypesToMenu(BMenu *menu)
|
||||
menu->GetFont(&font);
|
||||
localMenu->SetFont(&font);
|
||||
}
|
||||
localMenu->AddItem(NewAttributeMenuItem (str, attrName, type,
|
||||
width, align, editable, false));
|
||||
localMenu->AddItem(NewAttributeMenuItem(str, attrName, type,
|
||||
displayAs.String(), width, align, editable, false));
|
||||
}
|
||||
if (localMenu) {
|
||||
BMessage *message = new BMessage(kMIMETypeItem);
|
||||
@ -3279,7 +3300,6 @@ BContainerWindow::DefaultStateSourceNode(const char *name, BNode *result,
|
||||
BPath path(settingsPath);
|
||||
path.Append(name);
|
||||
if (!BEntry(path.Path()).Exists()) {
|
||||
|
||||
if (!createNew)
|
||||
return false;
|
||||
|
||||
@ -3289,22 +3309,20 @@ BContainerWindow::DefaultStateSourceNode(const char *name, BNode *result,
|
||||
const char *nextSlash = strchr(name, '/');
|
||||
if (!nextSlash)
|
||||
break;
|
||||
|
||||
|
||||
BString tmp;
|
||||
tmp.SetTo(name, nextSlash - name);
|
||||
tmpPath.Append(tmp.String());
|
||||
|
||||
|
||||
mkdir(tmpPath.Path(), 0777);
|
||||
|
||||
|
||||
name = nextSlash + 1;
|
||||
if (!name[0]) {
|
||||
// can't deal with a slash at end
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (createFolder) {
|
||||
if (mkdir(path.Path(), 0777) < 0)
|
||||
return false;
|
||||
@ -3314,7 +3332,7 @@ BContainerWindow::DefaultStateSourceNode(const char *name, BNode *result,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// PRINT(("using default state from %s\n", path.Path()));
|
||||
result->SetTo(path.Path());
|
||||
return result->InitCheck() == B_OK;
|
||||
|
@ -150,8 +150,11 @@ class BContainerWindow : public BWindow {
|
||||
void AddMimeTypesToMenu();
|
||||
virtual void MarkAttributeMenu(BMenu *);
|
||||
void MarkAttributeMenu();
|
||||
BMenuItem *NewAttributeMenuItem (const char *label, const char *attrName, int32 attrType,
|
||||
float attrWidth, int32 attrAlign, bool attrEditable, bool attrStatField);
|
||||
BMenuItem *NewAttributeMenuItem(const char *label, const char *name,
|
||||
int32 type, float width, int32 align, bool editable, bool statField);
|
||||
BMenuItem *NewAttributeMenuItem(const char *label, const char *name,
|
||||
int32 type, const char* displayAs, float width, int32 align,
|
||||
bool editable, bool statField);
|
||||
virtual void NewAttributeMenu(BMenu *);
|
||||
|
||||
void HideAttributeMenu();
|
||||
|
@ -1808,7 +1808,7 @@ BPoseView::RefreshMimeTypeList()
|
||||
if (pose->TargetModel())
|
||||
AddMimeType(pose->TargetModel()->MimeType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
@ -2505,8 +2505,11 @@ BPoseView::HandleAttrMenuItemSelected(BMessage *message)
|
||||
if (message->FindBool("attr_statfield", &isStatfield) != B_OK)
|
||||
return;
|
||||
|
||||
const char* displayAs;
|
||||
message->FindString("attr_display_as", &displayAs);
|
||||
|
||||
column = new BColumn(item->Label(), 0, attrWidth, attrAlign,
|
||||
attrName, attrType, isStatfield, isEditable);
|
||||
attrName, attrType, displayAs, isStatfield, isEditable);
|
||||
AddColumn(column);
|
||||
if (item->Menu()->Supermenu() == NULL)
|
||||
delete item->Menu();
|
||||
|
@ -315,7 +315,8 @@ class BPoseView : public BView {
|
||||
BRefFilter *RefFilter() const;
|
||||
|
||||
// access for mime types represented in the pose view
|
||||
const char *MimeTypeAt(int32);
|
||||
void AddMimeType(const char* mimeType);
|
||||
const char *MimeTypeAt(int32 index);
|
||||
int32 CountMimeTypes();
|
||||
void RefreshMimeTypeList();
|
||||
|
||||
@ -553,7 +554,6 @@ class BPoseView : public BView {
|
||||
virtual void EditQueries();
|
||||
virtual void AddCountView();
|
||||
|
||||
void AddMimeType(const char *);
|
||||
void HandleAttrMenuItemSelected(BMessage *);
|
||||
void TryUpdatingBrokenLinks();
|
||||
// ran a little after a volume gets mounted
|
||||
|
@ -127,14 +127,14 @@ AttrHashString(const char *string, uint32 type)
|
||||
bool
|
||||
ValidateStream(BMallocIO *stream, uint32 key, int32 version)
|
||||
{
|
||||
uint32 test_key;
|
||||
int32 test_version;
|
||||
uint32 testKey;
|
||||
int32 testVersion;
|
||||
|
||||
if (stream->Read(&test_key, sizeof(uint32)) <= 0
|
||||
|| stream->Read(&test_version, sizeof(int32)) <=0)
|
||||
if (stream->Read(&testKey, sizeof(uint32)) <= 0
|
||||
|| stream->Read(&testVersion, sizeof(int32)) <=0)
|
||||
return false;
|
||||
|
||||
return test_key == key && test_version == version;
|
||||
return testKey == key && testVersion == version;
|
||||
}
|
||||
|
||||
|
||||
@ -1075,14 +1075,13 @@ StringFromStream(BString *string, BMallocIO *stream, bool endianSwap)
|
||||
if (endianSwap)
|
||||
length = SwapInt32(length);
|
||||
|
||||
if (length <= 0 || length > 10000) {
|
||||
// ToDo:
|
||||
// should fail here
|
||||
if (length < 0 || length > 10000) {
|
||||
// TODO: should fail here
|
||||
PRINT(("problems instatiating a string, length probably wrong %d\n", length));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
char *buffer = string->LockBuffer(length);
|
||||
char *buffer = string->LockBuffer(length + 1);
|
||||
stream->Read(buffer, (size_t)length + 1);
|
||||
string->UnlockBuffer(length);
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ const char *kColumnAlignmentName = "BColumn:fAlignment";
|
||||
const char *kColumnAttrName = "BColumn:fAttrName";
|
||||
const char *kColumnAttrHashName = "BColumn:fAttrHash";
|
||||
const char *kColumnAttrTypeName = "BColumn:fAttrType";
|
||||
const char *kColumnDisplayAsName = "BColumn:fDisplayAs";
|
||||
const char *kColumnStatFieldName = "BColumn:fStatField";
|
||||
const char *kColumnEditableName = "BColumn:fEditable";
|
||||
|
||||
@ -71,20 +72,25 @@ const char *kViewStateReverseSortName = "ViewState:fReverseSort";
|
||||
const char *kViewStateIconSizeName = "ViewState:fIconSize";
|
||||
|
||||
|
||||
BColumn::BColumn(const char *title, float offset, float width, alignment align,
|
||||
const char *attributeName, uint32 attrType, bool statField,
|
||||
bool editable)
|
||||
:
|
||||
fTitle(title),
|
||||
fAttrName(attributeName)
|
||||
static const int32 kColumnStateMinArchiveVersion = 21;
|
||||
// bump version when layout changes
|
||||
|
||||
|
||||
BColumn::BColumn(const char *title, float offset, float width,
|
||||
alignment align, const char *attributeName, uint32 attrType,
|
||||
const char* displayAs, bool statField, bool editable)
|
||||
{
|
||||
fOffset = offset;
|
||||
fWidth = width;
|
||||
fAlignment = align;
|
||||
fAttrHash = AttrHashString(attributeName, attrType);
|
||||
fAttrType = attrType;
|
||||
fStatField = statField;
|
||||
fEditable = editable;
|
||||
_Init(title, offset, width, align, attributeName, attrType, displayAs,
|
||||
statField, editable);
|
||||
}
|
||||
|
||||
|
||||
BColumn::BColumn(const char *title, float offset, float width,
|
||||
alignment align, const char *attributeName, uint32 attrType,
|
||||
bool statField, bool editable)
|
||||
{
|
||||
_Init(title, offset, width, align, attributeName, attrType, NULL,
|
||||
statField, editable);
|
||||
}
|
||||
|
||||
|
||||
@ -93,7 +99,7 @@ BColumn::~BColumn()
|
||||
}
|
||||
|
||||
|
||||
BColumn::BColumn(BMallocIO *stream, bool endianSwap)
|
||||
BColumn::BColumn(BMallocIO *stream, int32 version, bool endianSwap)
|
||||
{
|
||||
StringFromStream(&fTitle, stream, endianSwap);
|
||||
stream->Read(&fOffset, sizeof(float));
|
||||
@ -104,7 +110,9 @@ BColumn::BColumn(BMallocIO *stream, bool endianSwap)
|
||||
stream->Read(&fAttrType, sizeof(uint32));
|
||||
stream->Read(&fStatField, sizeof(bool));
|
||||
stream->Read(&fEditable, sizeof(bool));
|
||||
|
||||
if (version == kColumnStateArchiveVersion)
|
||||
StringFromStream(&fDisplayAs, stream, endianSwap);
|
||||
|
||||
if (endianSwap) {
|
||||
PRINT(("endian swapping column\n"));
|
||||
fOffset = B_SWAP_FLOAT(fOffset);
|
||||
@ -126,29 +134,53 @@ BColumn::BColumn(const BMessage &message, int32 index)
|
||||
message.FindString(kColumnAttrName, index, &fAttrName);
|
||||
message.FindInt32(kColumnAttrHashName, index, (int32 *)&fAttrHash);
|
||||
message.FindInt32(kColumnAttrTypeName, index, (int32 *)&fAttrType);
|
||||
message.FindString(kColumnDisplayAsName, index, &fDisplayAs);
|
||||
message.FindBool(kColumnStatFieldName, index, &fStatField);
|
||||
message.FindBool(kColumnEditableName, index, &fEditable);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BColumn::_Init(const char *title, float offset, float width,
|
||||
alignment align, const char *attributeName, uint32 attrType,
|
||||
const char* displayAs, bool statField, bool editable)
|
||||
{
|
||||
fTitle = title;
|
||||
fAttrName = attributeName;
|
||||
fDisplayAs = displayAs;
|
||||
fOffset = offset;
|
||||
fWidth = width;
|
||||
fAlignment = align;
|
||||
fAttrHash = AttrHashString(attributeName, attrType);
|
||||
fAttrType = attrType;
|
||||
fStatField = statField;
|
||||
fEditable = editable;
|
||||
}
|
||||
|
||||
|
||||
BColumn *
|
||||
BColumn::InstantiateFromStream(BMallocIO *stream, bool endianSwap)
|
||||
{
|
||||
// compare stream header in canonical form
|
||||
uint32 key = AttrHashString("BColumn", B_OBJECT_TYPE);
|
||||
int32 version = kColumnStateArchiveVersion;
|
||||
|
||||
// we can't use ValidateStream(), as we preserve backwards compatibility
|
||||
int32 version;
|
||||
uint32 key;
|
||||
if (stream->Read(&key, sizeof(uint32)) <= 0
|
||||
|| stream->Read(&version, sizeof(int32)) <=0)
|
||||
return 0;
|
||||
|
||||
if (endianSwap) {
|
||||
key = SwapUInt32(key);
|
||||
version = SwapInt32(version);
|
||||
}
|
||||
|
||||
// PRINT(("validating key %x, version %d\n", key, version));
|
||||
if (!ValidateStream(stream, key, version))
|
||||
if (key != AttrHashString("BColumn", B_OBJECT_TYPE)
|
||||
|| version < kColumnStateMinArchiveVersion)
|
||||
return 0;
|
||||
|
||||
// PRINT(("instantiating column, %s\n", endianSwap ? "endian swapping," : ""));
|
||||
return _Sanitize(new (std::nothrow) BColumn(stream, endianSwap));
|
||||
return _Sanitize(new (std::nothrow) BColumn(stream, version, endianSwap));
|
||||
}
|
||||
|
||||
|
||||
@ -188,6 +220,7 @@ BColumn::ArchiveToStream(BMallocIO *stream) const
|
||||
stream->Write(&fAttrType, sizeof(uint32));
|
||||
stream->Write(&fStatField, sizeof(bool));
|
||||
stream->Write(&fEditable, sizeof(bool));
|
||||
StringToStream(&fDisplayAs, stream);
|
||||
}
|
||||
|
||||
|
||||
@ -203,6 +236,8 @@ BColumn::ArchiveToMessage(BMessage &message) const
|
||||
message.AddString(kColumnAttrName, fAttrName);
|
||||
message.AddInt32(kColumnAttrHashName, static_cast<int32>(fAttrHash));
|
||||
message.AddInt32(kColumnAttrTypeName, static_cast<int32>(fAttrType));
|
||||
if (fDisplayAs.Length() > 0)
|
||||
message.AddString(kColumnDisplayAsName, fDisplayAs.String());
|
||||
message.AddBool(kColumnStatFieldName, fStatField);
|
||||
message.AddBool(kColumnEditableName, fEditable);
|
||||
}
|
||||
|
@ -41,17 +41,20 @@ All rights reserved.
|
||||
|
||||
namespace BPrivate {
|
||||
|
||||
const int32 kColumnStateArchiveVersion = 21;
|
||||
const int32 kColumnStateArchiveVersion = 22;
|
||||
// bump version when layout or size changes
|
||||
|
||||
class BColumn {
|
||||
public:
|
||||
BColumn(const char *title, float offset, float width,
|
||||
alignment align, const char *attributeName, uint32 attrType,
|
||||
const char* displayAs, bool statField, bool editable);
|
||||
BColumn(const char *title, float offset, float width,
|
||||
alignment align, const char *attributeName, uint32 attrType,
|
||||
bool statField, bool editable);
|
||||
~BColumn();
|
||||
|
||||
BColumn(BMallocIO *stream, bool endianSwap = false);
|
||||
BColumn(BMallocIO *stream, int32 version, bool endianSwap = false);
|
||||
BColumn(const BMessage &, int32 index = 0);
|
||||
static BColumn *InstantiateFromStream(BMallocIO *stream,
|
||||
bool endianSwap = false);
|
||||
@ -60,12 +63,13 @@ class BColumn {
|
||||
void ArchiveToStream(BMallocIO *stream) const;
|
||||
void ArchiveToMessage(BMessage &) const;
|
||||
|
||||
const char *Title() const;
|
||||
const char* Title() const;
|
||||
float Offset() const;
|
||||
float Width() const;
|
||||
alignment Alignment() const;
|
||||
const char *AttrName() const;
|
||||
const char* AttrName() const;
|
||||
uint32 AttrType() const;
|
||||
const char* DisplayAs() const;
|
||||
uint32 AttrHash() const;
|
||||
bool StatField() const;
|
||||
bool Editable() const;
|
||||
@ -74,13 +78,17 @@ class BColumn {
|
||||
void SetWidth(float);
|
||||
|
||||
private:
|
||||
static BColumn *_Sanitize(BColumn *column);
|
||||
void _Init(const char *title, float offset, float width,
|
||||
alignment align, const char *attributeName, uint32 attrType,
|
||||
const char* displayAs, bool statField, bool editable);
|
||||
static BColumn* _Sanitize(BColumn* column);
|
||||
|
||||
BString fTitle;
|
||||
float fOffset;
|
||||
float fWidth;
|
||||
alignment fAlignment;
|
||||
BString fAttrName;
|
||||
BString fDisplayAs;
|
||||
uint32 fAttrHash;
|
||||
uint32 fAttrType;
|
||||
bool fStatField;
|
||||
@ -186,6 +194,12 @@ BColumn::AttrType() const
|
||||
return fAttrType;
|
||||
}
|
||||
|
||||
inline const char *
|
||||
BColumn::DisplayAs() const
|
||||
{
|
||||
return fDisplayAs.String();
|
||||
}
|
||||
|
||||
inline bool
|
||||
BColumn::StatField() const
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user