IconView can now also be set to a MIME type instead of a file or a buffer.
Untested actually, but FileTypesWindow's TypeIconView will be merged to this one soon. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@19298 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -211,8 +211,37 @@ Icon::SetTo(entry_ref& ref, const char* type)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
Icon::SetTo(BMimeType& type, icon_source* _source)
|
||||||
|
{
|
||||||
|
Unset();
|
||||||
|
|
||||||
|
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
|
||||||
|
uint8* data;
|
||||||
|
size_t size;
|
||||||
|
if (icon_for_type(type, &data, &size, _source) == B_OK) {
|
||||||
|
// we have the vector icon, no need to get the rest
|
||||||
|
AdoptData(data, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BBitmap* icon = AllocateBitmap(B_LARGE_ICON, B_CMAP8);
|
||||||
|
if (icon && icon_for_type(type, *icon, B_LARGE_ICON, _source) == B_OK)
|
||||||
|
AdoptLarge(icon);
|
||||||
|
else
|
||||||
|
delete icon;
|
||||||
|
|
||||||
|
icon = AllocateBitmap(B_MINI_ICON, B_CMAP8);
|
||||||
|
if (icon && icon_for_type(type, *icon, B_MINI_ICON) == B_OK)
|
||||||
|
AdoptMini(icon);
|
||||||
|
else
|
||||||
|
delete icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
Icon::CopyTo(BAppFileInfo& info, const char* type, bool force)
|
Icon::CopyTo(BAppFileInfo& info, const char* type, bool force) const
|
||||||
{
|
{
|
||||||
status_t status = B_OK;
|
status_t status = B_OK;
|
||||||
|
|
||||||
@@ -229,7 +258,7 @@ Icon::CopyTo(BAppFileInfo& info, const char* type, bool force)
|
|||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
Icon::CopyTo(entry_ref& ref, const char* type, bool force)
|
Icon::CopyTo(entry_ref& ref, const char* type, bool force) const
|
||||||
{
|
{
|
||||||
BFile file;
|
BFile file;
|
||||||
status_t status = file.SetTo(&ref, B_READ_ONLY);
|
status_t status = file.SetTo(&ref, B_READ_ONLY);
|
||||||
@@ -246,7 +275,24 @@ Icon::CopyTo(entry_ref& ref, const char* type, bool force)
|
|||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
Icon::CopyTo(BMessage& message)
|
Icon::CopyTo(BMimeType& type, bool force) const
|
||||||
|
{
|
||||||
|
status_t status = B_OK;
|
||||||
|
|
||||||
|
if (fLarge != NULL || force)
|
||||||
|
status = type.SetIcon(fLarge, B_LARGE_ICON);
|
||||||
|
if (fMini != NULL || force)
|
||||||
|
status = type.SetIcon(fMini, B_MINI_ICON);
|
||||||
|
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
|
||||||
|
if (fData != NULL || force)
|
||||||
|
status = type.SetIcon(fData, fSize);
|
||||||
|
#endif
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
Icon::CopyTo(BMessage& message) const
|
||||||
{
|
{
|
||||||
status_t status = B_OK;
|
status_t status = B_OK;
|
||||||
|
|
||||||
@@ -480,9 +526,9 @@ IconView::IconView(BRect rect, const char* name, uint32 resizeMode, uint32 flags
|
|||||||
fIconSize(B_LARGE_ICON),
|
fIconSize(B_LARGE_ICON),
|
||||||
fIcon(NULL),
|
fIcon(NULL),
|
||||||
fHeapIcon(NULL),
|
fHeapIcon(NULL),
|
||||||
fIconData(NULL),
|
|
||||||
fHasRef(false),
|
fHasRef(false),
|
||||||
fIsLive(false),
|
fHasType(false),
|
||||||
|
fIconData(NULL),
|
||||||
fTracking(false),
|
fTracking(false),
|
||||||
fDragging(false),
|
fDragging(false),
|
||||||
fDropTarget(false),
|
fDropTarget(false),
|
||||||
@@ -508,7 +554,7 @@ IconView::AttachedToWindow()
|
|||||||
fTarget = this;
|
fTarget = this;
|
||||||
|
|
||||||
// SetTo() was already called before we were a valid messenger
|
// SetTo() was already called before we were a valid messenger
|
||||||
if (fIsLive)
|
if (fHasRef || fHasType)
|
||||||
_StartWatching();
|
_StartWatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,6 +606,9 @@ IconView::MessageReceived(BMessage* message)
|
|||||||
|
|
||||||
case B_NODE_MONITOR:
|
case B_NODE_MONITOR:
|
||||||
{
|
{
|
||||||
|
if (!fHasRef)
|
||||||
|
break;
|
||||||
|
|
||||||
int32 opcode;
|
int32 opcode;
|
||||||
if (message->FindInt32("opcode", &opcode) != B_OK
|
if (message->FindInt32("opcode", &opcode) != B_OK
|
||||||
|| opcode != B_ATTR_CHANGED)
|
|| opcode != B_ATTR_CHANGED)
|
||||||
@@ -574,6 +623,33 @@ IconView::MessageReceived(BMessage* message)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case B_META_MIME_CHANGED:
|
||||||
|
{
|
||||||
|
if (!fHasType)
|
||||||
|
break;
|
||||||
|
|
||||||
|
const char* type;
|
||||||
|
int32 which;
|
||||||
|
if (message->FindString("be:type", &type) != B_OK
|
||||||
|
|| !strcmp(type, fType.Type())
|
||||||
|
|| message->FindInt32("be:which", &which) != B_OK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (which) {
|
||||||
|
case B_MIME_TYPE_DELETED:
|
||||||
|
Unset();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_ICON_CHANGED:
|
||||||
|
Update();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
BView::MessageReceived(message);
|
BView::MessageReceived(message);
|
||||||
break;
|
break;
|
||||||
@@ -739,7 +815,7 @@ IconView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
|
|||||||
::Icon* icon = fIconData;
|
::Icon* icon = fIconData;
|
||||||
if (fHasRef) {
|
if (fHasRef) {
|
||||||
icon = new ::Icon;
|
icon = new ::Icon;
|
||||||
icon->SetTo(fRef, fFileType.Length() > 0 ? fFileType.String() : NULL);
|
icon->SetTo(fRef, fType.Type());
|
||||||
}
|
}
|
||||||
|
|
||||||
icon->CopyTo(message);
|
icon->CopyTo(message);
|
||||||
@@ -810,18 +886,31 @@ IconView::MakeFocus(bool focus)
|
|||||||
void
|
void
|
||||||
IconView::SetTo(entry_ref& ref, const char* fileType)
|
IconView::SetTo(entry_ref& ref, const char* fileType)
|
||||||
{
|
{
|
||||||
if (fHasRef && fIsLive)
|
Unset();
|
||||||
_StopWatching();
|
|
||||||
|
|
||||||
fHasRef = true;
|
fHasRef = true;
|
||||||
fRef = ref;
|
fRef = ref;
|
||||||
|
|
||||||
if (fileType != NULL)
|
if (fileType != NULL)
|
||||||
fFileType = fileType;
|
fType.SetTo(fileType);
|
||||||
else
|
else
|
||||||
fFileType = "";
|
fType.Unset();
|
||||||
|
|
||||||
|
_StartWatching();
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
IconView::SetTo(BMimeType& type)
|
||||||
|
{
|
||||||
|
Unset();
|
||||||
|
|
||||||
|
if (type.Type() == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fHasType = true;
|
||||||
|
fType.SetTo(type.Type());
|
||||||
|
|
||||||
fIsLive = true;
|
|
||||||
_StartWatching();
|
_StartWatching();
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
@@ -833,12 +922,73 @@ IconView::SetTo(::Icon* icon)
|
|||||||
if (fIconData == icon)
|
if (fIconData == icon)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Unset();
|
||||||
|
|
||||||
fIconData = icon;
|
fIconData = icon;
|
||||||
fHasRef = false;
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
IconView::Unset()
|
||||||
|
{
|
||||||
|
if (fHasRef || fHasType)
|
||||||
|
_StopWatching();
|
||||||
|
|
||||||
|
fHasRef = false;
|
||||||
|
fHasType = false;
|
||||||
|
|
||||||
|
fType.Unset();
|
||||||
|
fIconData = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
IconView::Update()
|
||||||
|
{
|
||||||
|
delete fIcon;
|
||||||
|
fIcon = NULL;
|
||||||
|
|
||||||
|
Invalidate();
|
||||||
|
// this will actually trigger a redraw *after* we updated the icon below
|
||||||
|
|
||||||
|
BBitmap* icon = NULL;
|
||||||
|
|
||||||
|
if (fHasRef) {
|
||||||
|
BFile file(&fRef, B_READ_ONLY);
|
||||||
|
if (file.InitCheck() != B_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BAppFileInfo info;
|
||||||
|
if (info.SetTo(&file) != B_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon = Icon::AllocateBitmap(fIconSize);
|
||||||
|
if (icon != NULL && info.GetIconForType(fType.Type(), icon,
|
||||||
|
(icon_size)fIconSize) != B_OK) {
|
||||||
|
delete icon;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (fHasType) {
|
||||||
|
icon = Icon::AllocateBitmap(fIconSize);
|
||||||
|
if (icon != NULL && icon_for_type(fType, *icon, (icon_size)fIconSize,
|
||||||
|
&fSource) != B_OK) {
|
||||||
|
delete icon;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (fIconData) {
|
||||||
|
icon = Icon::AllocateBitmap(fIconSize);
|
||||||
|
if (fIconData->GetIcon(icon) != B_OK) {
|
||||||
|
delete icon;
|
||||||
|
icon = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fIcon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
IconView::SetIconSize(int32 size)
|
IconView::SetIconSize(int32 size)
|
||||||
{
|
{
|
||||||
@@ -895,51 +1045,6 @@ IconView::SetEnabled(bool enabled)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
IconView::Update()
|
|
||||||
{
|
|
||||||
delete fIcon;
|
|
||||||
fIcon = NULL;
|
|
||||||
|
|
||||||
Invalidate();
|
|
||||||
// this will actually trigger a redraw *after* we updated the icon below
|
|
||||||
|
|
||||||
BBitmap* icon = NULL;
|
|
||||||
|
|
||||||
if (fHasRef) {
|
|
||||||
if (fIconData != NULL)
|
|
||||||
fIconData->Unset();
|
|
||||||
|
|
||||||
BFile file(&fRef, B_READ_ONLY);
|
|
||||||
if (file.InitCheck() != B_OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const char* fileType = fFileType.Length() > 0 ? fFileType.String() : NULL;
|
|
||||||
|
|
||||||
BAppFileInfo info;
|
|
||||||
if (info.SetTo(&file) != B_OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
icon = Icon::AllocateBitmap(fIconSize);
|
|
||||||
if (icon != NULL && info.GetIconForType(fileType, icon, (icon_size)fIconSize) != B_OK) {
|
|
||||||
delete icon;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fIconData != NULL)
|
|
||||||
fIconData->SetTo(info, fileType);
|
|
||||||
} else if (fIconData) {
|
|
||||||
icon = Icon::AllocateBitmap(fIconSize);
|
|
||||||
if (fIconData->GetIcon(icon) != B_OK) {
|
|
||||||
delete icon;
|
|
||||||
icon = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fIcon = icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
IconView::Invoke()
|
IconView::Invoke()
|
||||||
{
|
{
|
||||||
@@ -958,13 +1063,15 @@ void
|
|||||||
IconView::_AddOrEditIcon()
|
IconView::_AddOrEditIcon()
|
||||||
{
|
{
|
||||||
BMessage message;
|
BMessage message;
|
||||||
if (fIsLive) {
|
if (fHasRef) {
|
||||||
// in live mode, Icon-O-Matic can change the icon directly, and
|
// in ref mode, Icon-O-Matic can change the icon directly, and
|
||||||
// we'll pick it up via node monitoring
|
// we'll pick it up via node monitoring
|
||||||
message.what = B_REFS_RECEIVED;
|
message.what = B_REFS_RECEIVED;
|
||||||
message.AddRef("refs", &fRef);
|
message.AddRef("refs", &fRef);
|
||||||
if (fFileType.Length() > 0)
|
if (fType.Type() != NULL)
|
||||||
message.AddString("file type", fFileType.String());
|
message.AddString("file type", fType.Type());
|
||||||
|
// } else if (fHasType) {
|
||||||
|
// // TODO!
|
||||||
} else {
|
} else {
|
||||||
// in static mode, Icon-O-Matic need to return the buffer it
|
// in static mode, Icon-O-Matic need to return the buffer it
|
||||||
// changed once its done
|
// changed once its done
|
||||||
@@ -980,23 +1087,32 @@ IconView::_AddOrEditIcon()
|
|||||||
void
|
void
|
||||||
IconView::_SetIcon(BBitmap* large, BBitmap* mini, const uint8* data, size_t size, bool force)
|
IconView::_SetIcon(BBitmap* large, BBitmap* mini, const uint8* data, size_t size, bool force)
|
||||||
{
|
{
|
||||||
if (fIsLive) {
|
if (fHasRef) {
|
||||||
BFile file(&fRef, B_READ_WRITE);
|
BFile file(&fRef, B_READ_WRITE);
|
||||||
|
|
||||||
BAppFileInfo info(&file);
|
BAppFileInfo info(&file);
|
||||||
if (info.InitCheck() == B_OK) {
|
if (info.InitCheck() == B_OK) {
|
||||||
const char* type = fFileType.Length() > 0 ? fFileType.String() : NULL;
|
|
||||||
|
|
||||||
if (large != NULL || force)
|
if (large != NULL || force)
|
||||||
info.SetIconForType(type, large, B_LARGE_ICON);
|
info.SetIconForType(fType.Type(), large, B_LARGE_ICON);
|
||||||
if (mini != NULL || force)
|
if (mini != NULL || force)
|
||||||
info.SetIconForType(type, mini, B_MINI_ICON);
|
info.SetIconForType(fType.Type(), mini, B_MINI_ICON);
|
||||||
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
|
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
|
||||||
if (data != NULL || force)
|
if (data != NULL || force)
|
||||||
info.SetIconForType(type, data, size);
|
info.SetIconForType(fType.Type(), data, size);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
// the icon shown will be updated using node monitoring
|
// the icon shown will be updated using node monitoring
|
||||||
|
} else if (fHasType) {
|
||||||
|
if (large != NULL || force)
|
||||||
|
fType.SetIcon(large, B_LARGE_ICON);
|
||||||
|
if (mini != NULL || force)
|
||||||
|
fType.SetIcon(mini, B_MINI_ICON);
|
||||||
|
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
|
||||||
|
if (data != NULL || force)
|
||||||
|
fType.SetIcon(data, size);
|
||||||
|
#endif
|
||||||
|
// the icon shown will be updated automatically - we're watching
|
||||||
|
// any changes to the MIME database
|
||||||
} else if (fIconData != NULL) {
|
} else if (fIconData != NULL) {
|
||||||
if (large != NULL || force)
|
if (large != NULL || force)
|
||||||
fIconData->SetLarge(large);
|
fIconData->SetLarge(large);
|
||||||
@@ -1109,17 +1225,23 @@ IconView::_StartWatching()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BNode node(&fRef);
|
if (fHasRef) {
|
||||||
node_ref nodeRef;
|
BNode node(&fRef);
|
||||||
if (node.InitCheck() == B_OK
|
node_ref nodeRef;
|
||||||
&& node.GetNodeRef(&nodeRef) == B_OK)
|
if (node.InitCheck() == B_OK
|
||||||
watch_node(&nodeRef, B_WATCH_ATTR, this);
|
&& node.GetNodeRef(&nodeRef) == B_OK)
|
||||||
|
watch_node(&nodeRef, B_WATCH_ATTR, this);
|
||||||
|
} else if (fHasType)
|
||||||
|
BMimeType::StartWatching(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
IconView::_StopWatching()
|
IconView::_StopWatching()
|
||||||
{
|
{
|
||||||
stop_watching(this);
|
if (fHasRef)
|
||||||
|
stop_watching(this);
|
||||||
|
else if (fHasType)
|
||||||
|
BMimeType::StopWatching(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,14 @@
|
|||||||
#include <View.h>
|
#include <View.h>
|
||||||
|
|
||||||
|
|
||||||
|
enum icon_source {
|
||||||
|
kNoIcon = 0,
|
||||||
|
kOwnIcon,
|
||||||
|
kApplicationIcon,
|
||||||
|
kSupertypeIcon
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class Icon {
|
class Icon {
|
||||||
public:
|
public:
|
||||||
Icon();
|
Icon();
|
||||||
@@ -21,9 +29,11 @@ class Icon {
|
|||||||
|
|
||||||
void SetTo(BAppFileInfo& info, const char* type = NULL);
|
void SetTo(BAppFileInfo& info, const char* type = NULL);
|
||||||
void SetTo(entry_ref& ref, const char* type = NULL);
|
void SetTo(entry_ref& ref, const char* type = NULL);
|
||||||
status_t CopyTo(BAppFileInfo& info, const char* type = NULL, bool force = false);
|
void SetTo(BMimeType& type, icon_source* _source = NULL);
|
||||||
status_t CopyTo(entry_ref& ref, const char* type = NULL, bool force = false);
|
status_t CopyTo(BAppFileInfo& info, const char* type = NULL, bool force = false) const;
|
||||||
status_t CopyTo(BMessage& message);
|
status_t CopyTo(entry_ref& ref, const char* type = NULL, bool force = false) const;
|
||||||
|
status_t CopyTo(BMimeType& type, bool force = false) const;
|
||||||
|
status_t CopyTo(BMessage& message) const;
|
||||||
|
|
||||||
void SetData(const uint8* data, size_t size);
|
void SetData(const uint8* data, size_t size);
|
||||||
void SetLarge(const BBitmap* large);
|
void SetLarge(const BBitmap* large);
|
||||||
@@ -72,12 +82,15 @@ class IconView : public BView {
|
|||||||
virtual void MakeFocus(bool focus = true);
|
virtual void MakeFocus(bool focus = true);
|
||||||
|
|
||||||
void SetTo(entry_ref& file, const char* fileType = NULL);
|
void SetTo(entry_ref& file, const char* fileType = NULL);
|
||||||
|
void SetTo(BMimeType& type);
|
||||||
void SetTo(::Icon* icon);
|
void SetTo(::Icon* icon);
|
||||||
|
void Unset();
|
||||||
|
void Update();
|
||||||
|
|
||||||
void SetIconSize(int32 size);
|
void SetIconSize(int32 size);
|
||||||
void ShowIconHeap(bool show);
|
void ShowIconHeap(bool show);
|
||||||
void SetEnabled(bool enabled);
|
void SetEnabled(bool enabled);
|
||||||
void SetTarget(const BMessenger& target);
|
void SetTarget(const BMessenger& target);
|
||||||
void Update();
|
|
||||||
void Invoke();
|
void Invoke();
|
||||||
|
|
||||||
::Icon* Icon();
|
::Icon* Icon();
|
||||||
@@ -99,12 +112,13 @@ class IconView : public BView {
|
|||||||
BBitmap* fIcon;
|
BBitmap* fIcon;
|
||||||
BBitmap* fHeapIcon;
|
BBitmap* fHeapIcon;
|
||||||
|
|
||||||
|
bool fHasRef;
|
||||||
|
bool fHasType;
|
||||||
|
entry_ref fRef;
|
||||||
|
BMimeType fType;
|
||||||
|
icon_source fSource;
|
||||||
::Icon* fIconData;
|
::Icon* fIconData;
|
||||||
|
|
||||||
bool fHasRef;
|
|
||||||
bool fIsLive;
|
|
||||||
entry_ref fRef;
|
|
||||||
BString fFileType;
|
|
||||||
BPoint fDragPoint;
|
BPoint fDragPoint;
|
||||||
bool fTracking;
|
bool fTracking;
|
||||||
bool fDragging;
|
bool fDragging;
|
||||||
@@ -117,13 +131,6 @@ static const uint32 kMsgRemoveIcon = 'icrm';
|
|||||||
static const uint32 kMsgAddIcon = 'icad';
|
static const uint32 kMsgAddIcon = 'icad';
|
||||||
static const uint32 kMsgEditIcon = 'iced';
|
static const uint32 kMsgEditIcon = 'iced';
|
||||||
|
|
||||||
enum icon_source {
|
|
||||||
kNoIcon = 0,
|
|
||||||
kOwnIcon,
|
|
||||||
kApplicationIcon,
|
|
||||||
kSupertypeIcon
|
|
||||||
};
|
|
||||||
|
|
||||||
extern status_t icon_for_type(BMimeType& type, uint8** _data, size_t* _size,
|
extern status_t icon_for_type(BMimeType& type, uint8** _data, size_t* _size,
|
||||||
icon_source* _source = NULL);
|
icon_source* _source = NULL);
|
||||||
extern status_t icon_for_type(BMimeType& type, BBitmap& bitmap,
|
extern status_t icon_for_type(BMimeType& type, BBitmap& bitmap,
|
||||||
|
|||||||
Reference in New Issue
Block a user