More common code moved from generic dialogs to wxDialogBase::CreateButtonSizer(). New system option 'wince.dialog.real-ok-cancel'.
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@36880 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: dialogs.cpp
|
||||
// Name: life/dialogs.cpp
|
||||
// Purpose: Life! dialogs
|
||||
// Author: Guillermo Rodriguez Garcia, <guille@iies.es>
|
||||
// Modified by:
|
||||
@@ -86,7 +86,7 @@ LifeSamplesDialog::LifeSamplesDialog(wxWindow *parent)
|
||||
if (isPDA &&
|
||||
wxSystemSettings::GetMetric(wxSYS_SCREEN_X) < wxSystemSettings::GetMetric(wxSYS_SCREEN_Y))
|
||||
{
|
||||
listSize = wxSize(-1, 50);
|
||||
listSize = wxSize(wxDefaultCoord, 50);
|
||||
screenIsHorizontal = false;
|
||||
}
|
||||
|
||||
@@ -128,21 +128,21 @@ LifeSamplesDialog::LifeSamplesDialog(wxWindow *parent)
|
||||
sizer3->Add( new wxStaticLine(this, wxID_ANY), 0, wxGROW | wxLEFT | wxRIGHT, 10 );
|
||||
#endif // wxUSE_STATLINE
|
||||
sizer3->Add( sizer2, 1, wxGROW | wxALL, 5 );
|
||||
#if wxUSE_STATLINE
|
||||
if (!isPDA)
|
||||
sizer3->Add( new wxStaticLine(this, wxID_ANY), 0, wxGROW | wxLEFT | wxRIGHT, 10 );
|
||||
#endif // wxUSE_STATLINE
|
||||
|
||||
#if defined(__SMARTPHONE__)
|
||||
SetLeftMenu(wxID_CANCEL);
|
||||
SetRightMenu(wxID_OK);
|
||||
#endif
|
||||
wxSizer *buttonSizer = CreateButtonSizer( wxOK|wxCANCEL , true, 10 );
|
||||
if(buttonSizer->GetChildren().GetCount() > 0 )
|
||||
{
|
||||
sizer3->Add( buttonSizer, 0, wxEXPAND | wxALL, 10 );
|
||||
}
|
||||
else
|
||||
{
|
||||
sizer3->AddSpacer( 10 );
|
||||
delete buttonSizer;
|
||||
}
|
||||
|
||||
// activate
|
||||
SetSizer(sizer3);
|
||||
|
||||
#if !defined(__POCKETPC__) && !defined(__SMARTPHONE__)
|
||||
sizer3->Add( CreateButtonSizer(wxOK | wxCANCEL), 0, wxCENTRE | wxALL, isPDA ? 2 : 10 );
|
||||
#if !defined(__SMARTPHONE__) && !defined(__POCKETPC__)
|
||||
sizer3->SetSizeHints(this);
|
||||
sizer3->Fit(this);
|
||||
Centre(wxBOTH | wxCENTRE_ON_SCREEN);
|
||||
@@ -199,24 +199,25 @@ LifeAboutDialog::LifeAboutDialog(wxWindow *parent)
|
||||
<guille@iies.es>\n\n\
|
||||
Portions of the code are based in XLife;\n\
|
||||
XLife is (c) 1989 by Jon Bennett et al.")),
|
||||
0, wxCENTRE | wxALL, 20 );
|
||||
#if wxUSE_STATLINE
|
||||
sizer->Add( new wxStaticLine(this, wxID_ANY), 0, wxGROW | wxLEFT | wxRIGHT, 5 );
|
||||
#endif // wxUSE_STATLINE
|
||||
0, wxCENTRE | wxRIGHT|wxLEFT|wxTOP, 20 );
|
||||
|
||||
#if ! (defined(__SMARTPHONE__) || defined(__POCKETPC__))
|
||||
sizer->Add( CreateButtonSizer(wxOK), 0, wxCENTRE | wxALL, 10 );
|
||||
#endif
|
||||
// buttons if any
|
||||
wxSizer *buttonSizer = CreateButtonSizer( wxOK , true, 10 );
|
||||
if(buttonSizer->GetChildren().GetCount() > 0 )
|
||||
{
|
||||
sizer->Add( buttonSizer, 0, wxEXPAND | wxALL, 10 );
|
||||
}
|
||||
else
|
||||
{
|
||||
sizer->AddSpacer( 20 );
|
||||
delete buttonSizer;
|
||||
}
|
||||
|
||||
// activate
|
||||
SetSizer(sizer);
|
||||
|
||||
#if ! (defined(__SMARTPHONE__) || defined(__POCKETPC__))
|
||||
#if !defined(__SMARTPHONE__) && !defined(__POCKETPC__)
|
||||
sizer->SetSizeHints(this);
|
||||
sizer->Fit(this);
|
||||
Centre(wxBOTH | wxCENTRE_ON_SCREEN);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@@ -123,6 +123,8 @@ wxWinCE:
|
||||
- ::wxGetUserName() implemented.
|
||||
- wxDisplay enumeration support.
|
||||
- Fixed wxFileDialog breakage on WinCE due to incorrect structure size.
|
||||
- new wxSystemOption "wince.dialog.real-ok-cancel" to switch between WinCE
|
||||
guidelines with Ok-only dialogs and dialogs using wxButtons.
|
||||
|
||||
Unix:
|
||||
|
||||
|
@@ -32,6 +32,13 @@ extern WXDLLEXPORT_DATA(const wxChar*) wxDialogNameStr;
|
||||
class WXDLLEXPORT wxDialogBase : public wxTopLevelWindow
|
||||
{
|
||||
public:
|
||||
|
||||
enum
|
||||
{
|
||||
// all flags allowed in wxDialogBase::CreateButtonSizer()
|
||||
ButtonSizerFlags = wxOK|wxCANCEL|wxYES|wxNO|wxHELP|wxNO_DEFAULT
|
||||
};
|
||||
|
||||
wxDialogBase() { Init(); }
|
||||
virtual ~wxDialogBase() { }
|
||||
|
||||
@@ -56,9 +63,11 @@ public:
|
||||
wxSizer *CreateTextSizer( const wxString &message );
|
||||
#endif // wxUSE_STATTEXT // && wxUSE_TEXTCTRL
|
||||
|
||||
#if wxUSE_BUTTON
|
||||
// places buttons into a horizontal wxBoxSizer
|
||||
wxSizer *CreateButtonSizer( long flags );
|
||||
wxSizer *CreateButtonSizer( long flags,
|
||||
bool separated = false,
|
||||
wxCoord distance = 0 );
|
||||
#if wxUSE_BUTTON
|
||||
wxStdDialogButtonSizer *CreateStdDialogButtonSizer( long flags );
|
||||
#endif // wxUSE_BUTTON
|
||||
|
||||
|
@@ -1381,7 +1381,11 @@ SettingsDialog::SettingsDialog(wxWindow* win)
|
||||
|wxRESIZE_BORDER
|
||||
#endif
|
||||
);
|
||||
CreateButtons(wxOK|wxCANCEL|wxHELP);
|
||||
CreateButtons(wxOK|wxCANCEL
|
||||
#ifndef __POCKETPC__
|
||||
|wxHELP
|
||||
#endif
|
||||
);
|
||||
|
||||
wxBookCtrlBase* notebook = GetBookCtrl();
|
||||
|
||||
|
@@ -36,6 +36,9 @@
|
||||
#include "wx/containr.h"
|
||||
#endif
|
||||
|
||||
#include "wx/statline.h"
|
||||
#include "wx/sysopt.h"
|
||||
|
||||
#if wxUSE_STATTEXT
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -273,11 +276,12 @@ wxStaticTextBase
|
||||
|
||||
#endif // wxUSE_STATTEXT
|
||||
|
||||
#if wxUSE_BUTTON
|
||||
|
||||
wxSizer *wxDialogBase::CreateButtonSizer( long flags )
|
||||
wxSizer *wxDialogBase::CreateButtonSizer( long flags, bool separated, wxCoord distance )
|
||||
{
|
||||
#ifdef __SMARTPHONE__
|
||||
wxUnusedVar(separated);
|
||||
wxUnusedVar(distance);
|
||||
|
||||
wxDialog* dialog = (wxDialog*) this;
|
||||
if (flags & wxOK){
|
||||
dialog->SetLeftMenu(wxID_OK);
|
||||
@@ -296,14 +300,64 @@ wxSizer *wxDialogBase::CreateButtonSizer( long flags )
|
||||
}
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
return sizer;
|
||||
#else
|
||||
return CreateStdDialogButtonSizer( flags );
|
||||
#endif
|
||||
|
||||
#else // !__SMARTPHONE__
|
||||
|
||||
#ifdef __POCKETPC__
|
||||
// PocketPC guidelines recommend for Ok/Cancel dialogs to use
|
||||
// OK button located inside caption bar and implement Cancel functionality
|
||||
// through Undo outside dialog. As native behaviour this will be default
|
||||
// here but can be easily replaced with real wxButtons
|
||||
// with "wince.dialog.real-ok-cancel" option set to 1
|
||||
if ( ((flags & ~(wxCANCEL|wxNO_DEFAULT))== wxOK) &&
|
||||
(wxSystemOptions::GetOptionInt(wxT("wince.dialog.real-ok-cancel"))==0)
|
||||
)
|
||||
{
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
return sizer;
|
||||
}
|
||||
#endif // __POCKETPC__
|
||||
|
||||
#if wxUSE_BUTTON
|
||||
|
||||
wxSizer* buttonSizer = CreateStdDialogButtonSizer( flags );
|
||||
|
||||
// Mac Human Interface Guidelines recommend not to use static lines as grouping elements
|
||||
#if wxUSE_STATLINE && !defined(__WXMAC__)
|
||||
if(!separated)
|
||||
return buttonSizer;
|
||||
|
||||
wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
|
||||
topsizer->Add( new wxStaticLine( this, wxID_ANY ), 0, wxEXPAND | wxBOTTOM, distance );
|
||||
topsizer->Add( buttonSizer, 0, wxEXPAND );
|
||||
return topsizer;
|
||||
|
||||
#else // !wxUSE_STATLINE
|
||||
|
||||
wxUnusedVar(separated);
|
||||
wxUnusedVar(distance);
|
||||
return buttonSizer;
|
||||
|
||||
#endif // wxUSE_STATLINE/!wxUSE_STATLINE
|
||||
|
||||
#else // !wxUSE_BUTTON
|
||||
|
||||
wxUnusedVar(separated);
|
||||
wxUnusedVar(distance);
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
return sizer;
|
||||
|
||||
#endif // wxUSE_BUTTON/!wxUSE_BUTTON
|
||||
|
||||
#endif // __SMARTPHONE__/!__SMARTPHONE__
|
||||
}
|
||||
|
||||
#if wxUSE_BUTTON
|
||||
|
||||
wxStdDialogButtonSizer *wxDialogBase::CreateStdDialogButtonSizer( long flags )
|
||||
{
|
||||
wxStdDialogButtonSizer *sizer = new wxStdDialogButtonSizer();
|
||||
|
||||
wxButton *ok = NULL;
|
||||
wxButton *yes = NULL;
|
||||
wxButton *no = NULL;
|
||||
@@ -365,5 +419,4 @@ wxStdDialogButtonSizer *wxDialogBase::CreateStdDialogButtonSizer( long flags )
|
||||
return sizer;
|
||||
}
|
||||
|
||||
|
||||
#endif // wxUSE_BUTTON
|
||||
|
@@ -39,10 +39,7 @@
|
||||
#include "wx/arrstr.h"
|
||||
#endif
|
||||
|
||||
#if wxUSE_STATLINE
|
||||
#include "wx/statline.h"
|
||||
#endif
|
||||
|
||||
#include "wx/generic/choicdgg.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -288,25 +285,17 @@ bool wxAnyChoiceDialog::Create(wxWindow *parent,
|
||||
|
||||
topsizer->Add( m_listbox, 1, wxEXPAND|wxLEFT|wxRIGHT, wxLARGESMALL(15,0) );
|
||||
|
||||
// smart phones does not support or do not waste space for wxButtons
|
||||
#ifdef __SMARTPHONE__
|
||||
|
||||
SetRightMenu(wxID_CANCEL, _("Cancel"));
|
||||
|
||||
#else // __SMARTPHONE__/!__SMARTPHONE__
|
||||
|
||||
// Mac Human Interface Guidelines recommend not to use static lines as grouping elements
|
||||
#ifndef __WXMAC__
|
||||
#if wxUSE_STATLINE
|
||||
// 3) static line
|
||||
topsizer->Add( new wxStaticLine( this, wxID_ANY ), 0, wxEXPAND | wxLEFT|wxRIGHT|wxTOP, 10 );
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// 4) buttons
|
||||
topsizer->Add( CreateButtonSizer( styleDlg & (wxOK|wxCANCEL) ), 0, wxEXPAND | wxALL, 10 );
|
||||
|
||||
#endif // !__SMARTPHONE__
|
||||
// 3) buttons if any
|
||||
wxSizer *buttonSizer = CreateButtonSizer( styleDlg & ButtonSizerFlags , true, wxLARGESMALL(10,0) );
|
||||
if(buttonSizer->GetChildren().GetCount() > 0 )
|
||||
{
|
||||
topsizer->Add( buttonSizer, 0, wxEXPAND | wxALL, wxLARGESMALL(10,0) );
|
||||
}
|
||||
else
|
||||
{
|
||||
topsizer->AddSpacer( wxLARGESMALL(15,0) );
|
||||
delete buttonSizer;
|
||||
}
|
||||
|
||||
SetSizer( topsizer );
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: dirdlg.cpp
|
||||
// Name: src/generic/dirdlg.cpp
|
||||
// Purpose: wxDirDialog
|
||||
// Author: Harm van der Heijden, Robert Roebling & Julian Smart
|
||||
// Modified by:
|
||||
@@ -108,8 +108,6 @@ wxGenericDirDialog::wxGenericDirDialog(wxWindow* parent, const wxString& title,
|
||||
dirMenu->AppendSeparator();
|
||||
dirMenu->Append(wxID_CANCEL, _("Cancel"));
|
||||
|
||||
SetRightMenu(wxID_ANY, _("Options"), dirMenu);
|
||||
|
||||
#else
|
||||
|
||||
// 0) 'New' and 'Home' Buttons
|
||||
@@ -175,18 +173,23 @@ wxGenericDirDialog::wxGenericDirDialog(wxWindow* parent, const wxString& title,
|
||||
m_input = new wxTextCtrl( this, ID_TEXTCTRL, m_path, wxDefaultPosition );
|
||||
topsizer->Add( m_input, 0, wxTOP|wxLEFT|wxRIGHT | wxEXPAND, wxLARGESMALL(10,0) );
|
||||
|
||||
#ifndef __SMARTPHONE__
|
||||
// 3) buttons if any
|
||||
wxSizer *buttonSizer = CreateButtonSizer( wxOK|wxCANCEL , true, wxLARGESMALL(10,0) );
|
||||
if(buttonSizer->GetChildren().GetCount() > 0 )
|
||||
{
|
||||
topsizer->Add( buttonSizer, 0, wxEXPAND | wxALL, wxLARGESMALL(10,0) );
|
||||
}
|
||||
else
|
||||
{
|
||||
topsizer->AddSpacer( wxLARGESMALL(10,0) );
|
||||
delete buttonSizer;
|
||||
}
|
||||
|
||||
#if wxUSE_STATLINE
|
||||
// 3) Static line
|
||||
topsizer->Add( new wxStaticLine( this, wxID_ANY ), 0, wxEXPAND | wxLEFT|wxRIGHT|wxTOP, 10 );
|
||||
#ifdef __SMARTPHONE__
|
||||
// overwrite menu achieved with earlier CreateButtonSizer() call
|
||||
SetRightMenu(wxID_ANY, _("Options"), dirMenu);
|
||||
#endif
|
||||
|
||||
// 4) Buttons
|
||||
topsizer->Add( CreateButtonSizer( wxOK|wxCANCEL ), 0, wxEXPAND | wxALL, 10 );
|
||||
|
||||
#endif // !__SMARTPHONE__
|
||||
|
||||
m_input->SetFocus();
|
||||
|
||||
SetAutoLayout( true );
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: numdlgg.cpp
|
||||
// Name: src/generic/numdlgg.cpp
|
||||
// Purpose: wxGetNumberFromUser implementation
|
||||
// Author: Vadim Zeitlin
|
||||
// Modified by:
|
||||
@@ -111,7 +111,7 @@ wxNumberEntryDialog::wxNumberEntryDialog(wxWindow *parent,
|
||||
|
||||
#if wxUSE_STATTEXT
|
||||
// prompt if any
|
||||
if (!prompt.IsEmpty())
|
||||
if (!prompt.empty())
|
||||
inputsizer->Add( new wxStaticText( this, wxID_ANY, prompt ), 0, wxCENTER | wxLEFT, 10 );
|
||||
#endif
|
||||
|
||||
@@ -124,24 +124,19 @@ wxNumberEntryDialog::wxNumberEntryDialog(wxWindow *parent,
|
||||
#endif
|
||||
inputsizer->Add( m_spinctrl, 1, wxCENTER | wxLEFT | wxRIGHT, 10 );
|
||||
// add both
|
||||
topsizer->Add( inputsizer, 1, wxEXPAND | wxLEFT|wxRIGHT, 5 );
|
||||
topsizer->Add( inputsizer, 0, wxEXPAND | wxLEFT|wxRIGHT, 5 );
|
||||
|
||||
// smart phones does not support or do not waste space for wxButtons
|
||||
#ifdef __SMARTPHONE__
|
||||
|
||||
SetRightMenu(wxID_CANCEL, _("Cancel"));
|
||||
|
||||
#else // __SMARTPHONE__/!__SMARTPHONE__
|
||||
|
||||
#if wxUSE_STATLINE
|
||||
// 3) static line
|
||||
topsizer->Add( new wxStaticLine( this, wxID_ANY ), 0, wxEXPAND | wxLEFT|wxRIGHT|wxTOP, 10 );
|
||||
#endif
|
||||
|
||||
// 4) buttons
|
||||
topsizer->Add( CreateButtonSizer( wxOK|wxCANCEL ), 0, wxEXPAND | wxALL, 10 );
|
||||
|
||||
#endif // !__SMARTPHONE__
|
||||
// 3) buttons if any
|
||||
wxSizer *buttonSizer = CreateButtonSizer( wxOK|wxCANCEL , true, wxLARGESMALL(10,0) );
|
||||
if(buttonSizer->GetChildren().GetCount() > 0 )
|
||||
{
|
||||
topsizer->Add( buttonSizer, 0, wxEXPAND | wxALL, wxLARGESMALL(10,0) );
|
||||
}
|
||||
else
|
||||
{
|
||||
topsizer->AddSpacer( wxLARGESMALL(15,0) );
|
||||
delete buttonSizer;
|
||||
}
|
||||
|
||||
SetSizer( topsizer );
|
||||
SetAutoLayout( true );
|
||||
|
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "wx/bookctrl.h"
|
||||
#include "wx/generic/propdlg.h"
|
||||
#include "wx/sysopt.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// wxPropertySheetDialog
|
||||
@@ -89,19 +90,27 @@ void wxPropertySheetDialog::LayoutDialog()
|
||||
// Creates the buttons, if any
|
||||
void wxPropertySheetDialog::CreateButtons(int flags)
|
||||
{
|
||||
#if defined(__SMARTPHONE__)
|
||||
// TODO: create a right-click menu with all the other IDs available.
|
||||
// Perhaps that could be embedded in CreateButtonSizer() directly.
|
||||
SetRightMenu(wxID_CANCEL);
|
||||
SetLeftMenu(wxID_OK);
|
||||
wxUnusedVar(flags);
|
||||
#elif defined(__POCKETPC__)
|
||||
// Do nothing
|
||||
wxUnusedVar(flags);
|
||||
#else
|
||||
wxSizer* sizer = CreateButtonSizer(flags);
|
||||
m_innerSizer->Add( sizer, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT|wxRIGHT, 2);
|
||||
#ifdef __POCKETPC__
|
||||
// keep system option status
|
||||
const wxChar *optionName = wxT("wince.dialog.real-ok-cancel");
|
||||
const int status = wxSystemOptions::GetOptionInt(optionName);
|
||||
wxSystemOptions::SetOption(optionName,0);
|
||||
#endif
|
||||
|
||||
wxSizer *buttonSizer = CreateButtonSizer( flags & ButtonSizerFlags );
|
||||
if(buttonSizer->GetChildren().GetCount() > 0 )
|
||||
{
|
||||
m_innerSizer->Add( buttonSizer, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT|wxRIGHT, 2);
|
||||
m_innerSizer->AddSpacer(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete buttonSizer;
|
||||
}
|
||||
|
||||
#ifdef __POCKETPC__
|
||||
// restore system option
|
||||
wxSystemOptions::SetOption(optionName,status);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Name: textdlgg.cpp
|
||||
// Name: src/generic/textdlgg.cpp
|
||||
// Purpose: wxTextEntryDialog
|
||||
// Author: Julian Smart
|
||||
// Modified by:
|
||||
@@ -107,25 +107,19 @@ wxTextEntryDialog::wxTextEntryDialog(wxWindow *parent,
|
||||
#if wxUSE_VALIDATORS
|
||||
wxTextValidator validator( wxFILTER_NONE, &m_value );
|
||||
m_textctrl->SetValidator( validator );
|
||||
#endif
|
||||
// wxUSE_VALIDATORS
|
||||
#endif // wxUSE_VALIDATORS
|
||||
|
||||
// smart phones does not support or do not waste space for wxButtons
|
||||
#ifdef __SMARTPHONE__
|
||||
|
||||
SetRightMenu(wxID_CANCEL, _("Cancel"));
|
||||
|
||||
#else // __SMARTPHONE__/!__SMARTPHONE__
|
||||
|
||||
#if wxUSE_STATLINE
|
||||
// 3) static line
|
||||
topsizer->Add( new wxStaticLine( this, wxID_ANY ), 0, wxEXPAND | wxLEFT|wxRIGHT|wxTOP, 10 );
|
||||
#endif
|
||||
|
||||
// 4) buttons
|
||||
topsizer->Add( CreateButtonSizer( style ), 0, wxEXPAND | wxALL, 10 );
|
||||
|
||||
#endif // !__SMARTPHONE__
|
||||
// 3) buttons if any
|
||||
wxSizer *buttonSizer = CreateButtonSizer( style & ButtonSizerFlags , true, wxLARGESMALL(10,0) );
|
||||
if(buttonSizer->GetChildren().GetCount() > 0 )
|
||||
{
|
||||
topsizer->Add( buttonSizer, 0, wxEXPAND | wxALL, wxLARGESMALL(10,0) );
|
||||
}
|
||||
else
|
||||
{
|
||||
topsizer->AddSpacer( wxLARGESMALL(15,0) );
|
||||
delete buttonSizer;
|
||||
}
|
||||
|
||||
SetAutoLayout( true );
|
||||
SetSizer( topsizer );
|
||||
|
Reference in New Issue
Block a user