X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/373d466f85e880baef6a13c130dc019a6b143331..69659fd770f615210efac4b4fa741b3ad6223616:/src/common/combocmn.cpp diff --git a/src/common/combocmn.cpp b/src/common/combocmn.cpp index a633163d30..ef60292f51 100644 --- a/src/common/combocmn.cpp +++ b/src/common/combocmn.cpp @@ -28,6 +28,7 @@ #include "wx/combobox.h" #ifndef WX_PRECOMP + #include "wx/app.h" #include "wx/log.h" #include "wx/dcclient.h" #include "wx/settings.h" @@ -62,6 +63,7 @@ // native controls work on it like normal. #define POPUPWIN_IS_PERFECT 0 // Same, but for non-transient popup window. #define TEXTCTRL_TEXT_CENTERED 0 // 1 if text in textctrl is vertically centered +#define FOCUS_RING 0 // No focus ring on wxMSW //#undef wxUSE_POPUPWIN //#define wxUSE_POPUPWIN 0 @@ -79,6 +81,7 @@ // native controls work on it like normal. #define POPUPWIN_IS_PERFECT 1 // Same, but for non-transient popup window. #define TEXTCTRL_TEXT_CENTERED 1 // 1 if text in textctrl is vertically centered +#define FOCUS_RING 0 // No focus ring on wxGTK #elif defined(__WXMAC__) @@ -87,6 +90,12 @@ // native controls work on it like normal. #define POPUPWIN_IS_PERFECT 0 // Same, but for non-transient popup window. #define TEXTCTRL_TEXT_CENTERED 1 // 1 if text in textctrl is vertically centered +#define FOCUS_RING 3 // Reserve room for the textctrl's focus ring to display + +#undef DEFAULT_DROPBUTTON_WIDTH +#define DEFAULT_DROPBUTTON_WIDTH 22 +#undef COMBO_MARGIN +#define COMBO_MARGIN FOCUS_RING #else @@ -95,6 +104,7 @@ // native controls work on it like normal. #define POPUPWIN_IS_PERFECT 0 // Same, but for non-transient popup window. #define TEXTCTRL_TEXT_CENTERED 1 // 1 if text in textctrl is vertically centered +#define FOCUS_RING 0 #endif @@ -346,6 +356,7 @@ public: #if USES_WXPOPUPTRANSIENTWINDOW virtual bool Show( bool show ); virtual bool ProcessLeftDown(wxMouseEvent& event); +protected: virtual void OnDismiss(); #endif @@ -366,12 +377,14 @@ bool wxComboPopupWindow::Show( bool show ) wxASSERT( IsKindOf(CLASSINFO(wxPopupTransientWindow)) ); wxPopupTransientWindow* ptw = (wxPopupTransientWindow*) this; - wxComboCtrlBase* combo = (wxComboCtrlBase*) GetParent(); if ( show != ptw->IsShown() ) { if ( show ) - ptw->Popup(combo->GetPopupControl()->GetControl()); + // We used to do wxPopupTransientWindow::Popup here, + // but this would hide normal Show, which we are + // also going to need. + ptw->Show(); else ptw->Dismiss(); } @@ -560,6 +573,7 @@ private: BEGIN_EVENT_TABLE(wxComboBoxExtraInputHandler, wxEvtHandler) EVT_KEY_DOWN(wxComboBoxExtraInputHandler::OnKey) EVT_SET_FOCUS(wxComboBoxExtraInputHandler::OnFocus) + EVT_KILL_FOCUS(wxComboBoxExtraInputHandler::OnFocus) END_EVENT_TABLE() @@ -581,7 +595,8 @@ void wxComboBoxExtraInputHandler::OnFocus(wxFocusEvent& event) { // FIXME: This code does run when control is clicked, // yet on Windows it doesn't select all the text. - if ( !(m_combo->GetInternalFlags() & wxCC_NO_TEXT_AUTO_SELECT) ) + if ( event.GetEventType() == wxEVT_SET_FOCUS && + !(m_combo->GetInternalFlags() & wxCC_NO_TEXT_AUTO_SELECT) ) { if ( m_combo->GetTextCtrl() ) m_combo->GetTextCtrl()->SelectAll(); @@ -595,7 +610,7 @@ void wxComboBoxExtraInputHandler::OnFocus(wxFocusEvent& event) // wxEVT_SET_FOCUSes (since m_text->SetFocus is called // from combo's focus event handler), they should be quite // harmless. - wxFocusEvent evt2(wxEVT_SET_FOCUS,m_combo->GetId()); + wxFocusEvent evt2(event.GetEventType(),m_combo->GetId()); evt2.SetEventObject(m_combo); m_combo->GetEventHandler()->ProcessEvent(evt2); @@ -648,6 +663,9 @@ void wxComboPopupExtraEventHandler::OnMouseEvent( wxMouseEvent& event ) wxSize sz = m_combo->GetPopupControl()->GetControl()->GetClientSize(); int evtType = event.GetEventType(); bool isInside = pt.x >= 0 && pt.y >= 0 && pt.x < sz.x && pt.y < sz.y; + bool relayToButton = false; + + event.Skip(); if ( evtType == wxEVT_MOTION || evtType == wxEVT_LEFT_DOWN || @@ -657,7 +675,6 @@ void wxComboPopupExtraEventHandler::OnMouseEvent( wxMouseEvent& event ) if ( !isInside || !m_combo->IsPopupShown() ) { event.Skip(false); - return; } } else if ( evtType == wxEVT_LEFT_UP ) @@ -665,10 +682,9 @@ void wxComboPopupExtraEventHandler::OnMouseEvent( wxMouseEvent& event ) if ( !m_combo->IsPopupShown() ) { event.Skip(false); - return; + relayToButton = true; } - - if ( !m_beenInside ) + else if ( !m_beenInside ) { if ( isInside ) { @@ -676,27 +692,48 @@ void wxComboPopupExtraEventHandler::OnMouseEvent( wxMouseEvent& event ) } else { - // - // Some mouse events to popup that happen outside it, before cursor - // has been inside the popu, need to be ignored by it but relayed to - // the dropbutton. - // - wxWindow* btn = m_combo->GetButton(); - if ( btn ) - btn->GetEventHandler()->AddPendingEvent(event); - else - m_combo->GetEventHandler()->AddPendingEvent(event); - - return; + relayToButton = true; } - - event.Skip(); } } - event.Skip(); + if ( relayToButton ) + { + // + // Some mouse events to popup that happen outside it, before cursor + // has been inside the popup, need to be ignored by it but relayed to + // the dropbutton. + // + wxWindow* eventSink = m_combo; + wxWindow* btn = m_combo->GetButton(); + if ( btn ) + eventSink = btn; + + eventSink->GetEventHandler()->ProcessEvent(event); + } } +// ---------------------------------------------------------------------------- +// wxComboCtrlTextCtrl +// ---------------------------------------------------------------------------- + +class wxComboCtrlTextCtrl : public wxTextCtrl +{ +public: + wxComboCtrlTextCtrl() : wxTextCtrl() { } + virtual ~wxComboCtrlTextCtrl() { } + + virtual wxWindow *GetMainWindowOfCompositeControl() + { + wxComboCtrl* combo = (wxComboCtrl*) GetParent(); + + // Returning this instead of just 'parent' lets FindFocus work + // correctly even when parent control is a child of a composite + // generic control (as is case with wxGenericDatePickerCtrl). + return combo->GetMainWindowOfCompositeControl(); + } +}; + // ---------------------------------------------------------------------------- // wxComboCtrlBase // ---------------------------------------------------------------------------- @@ -707,6 +744,7 @@ BEGIN_EVENT_TABLE(wxComboCtrlBase, wxControl) EVT_SIZE(wxComboCtrlBase::OnSizeEvent) EVT_SET_FOCUS(wxComboCtrlBase::OnFocusEvent) EVT_KILL_FOCUS(wxComboCtrlBase::OnFocusEvent) + EVT_IDLE(wxComboCtrlBase::OnIdleEvent) //EVT_BUTTON(wxID_ANY,wxComboCtrlBase::OnButtonClickEvent) EVT_KEY_DOWN(wxComboCtrlBase::OnKeyEvent) EVT_TEXT_ENTER(wxID_ANY,wxComboCtrlBase::OnTextCtrlEvent) @@ -754,6 +792,8 @@ void wxComboCtrlBase::Init() m_absIndent = -1; m_iFlags = 0; m_timeCanAcceptClick = 0; + + m_resetFocus = false; } bool wxComboCtrlBase::Create(wxWindow *parent, @@ -827,15 +867,21 @@ wxComboCtrlBase::CreateTextCtrl(int style, const wxValidator& validator) else m_ignoreEvtText = 0; - m_text = new wxTextCtrl(this, wxID_ANY, m_valueString, - wxDefaultPosition, wxSize(10,-1), - style, validator); + m_text = new wxComboCtrlTextCtrl(); + m_text->Create(this, wxID_ANY, m_valueString, + wxDefaultPosition, wxSize(10,-1), + style, validator); } } void wxComboCtrlBase::OnThemeChange() { + // Leave the default bg on the Mac so the area used by the focus ring will + // be the correct colour and themed brush. Instead we'll use + // wxSYS_COLOUR_WINDOW in the EVT_PAINT handler as needed. +#ifndef __WXMAC__ SetOwnBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif } wxComboCtrlBase::~wxComboCtrlBase() @@ -879,6 +925,12 @@ void wxComboCtrlBase::CalculateAreas( int btnWidth ) m_iFlags |= wxCC_IFLAG_BUTTON_OUTSIDE; btnBorder = 0; } + else if ( (m_iFlags & wxCC_BUTTON_COVERS_BORDER) && + m_btnSpacingX == 0 && !m_bmpNormal.Ok() ) + { + m_iFlags &= ~(wxCC_IFLAG_BUTTON_OUTSIDE); + btnBorder = 0; + } else { m_iFlags &= ~(wxCC_IFLAG_BUTTON_OUTSIDE); @@ -953,6 +1005,11 @@ void wxComboCtrlBase::CalculateAreas( int btnWidth ) { int newY = butHeight+(customBorder*2); SetClientSize(wxDefaultCoord,newY); + if ( m_bmpNormal.Ok() || m_btnArea.width != butWidth || m_btnArea.height != butHeight ) + m_iFlags |= wxCC_IFLAG_HAS_NONSTANDARD_BUTTON; + else + m_iFlags &= ~wxCC_IFLAG_HAS_NONSTANDARD_BUTTON; + sz.y = newY; } } @@ -963,14 +1020,14 @@ void wxComboCtrlBase::CalculateAreas( int btnWidth ) m_btnSize.y = butHeight; m_btnArea.x = ( m_btnSide==wxRIGHT ? sz.x - butAreaWid - btnBorder : btnBorder ); - m_btnArea.y = btnBorder; + m_btnArea.y = btnBorder + FOCUS_RING; m_btnArea.width = butAreaWid; - m_btnArea.height = sz.y - (btnBorder*2); + m_btnArea.height = sz.y - ((btnBorder+FOCUS_RING)*2); - m_tcArea.x = ( m_btnSide==wxRIGHT ? 0 : butAreaWid ) + customBorder; - m_tcArea.y = customBorder; - m_tcArea.width = sz.x - butAreaWid - (customBorder*2); - m_tcArea.height = sz.y - (customBorder*2); + m_tcArea.x = ( m_btnSide==wxRIGHT ? 0 : butAreaWid ) + customBorder + FOCUS_RING; + m_tcArea.y = customBorder + FOCUS_RING; + m_tcArea.width = sz.x - butAreaWid - (customBorder*2) - (FOCUS_RING*2); + m_tcArea.height = sz.y - ((customBorder+FOCUS_RING)*2); /* if ( m_text ) @@ -986,10 +1043,11 @@ void wxComboCtrlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust if ( !m_text ) return; +#if !TEXTCTRL_TEXT_CENTERED + wxSize sz = GetClientSize(); - int customBorder = m_widthCustomBorder; -#if !TEXTCTRL_TEXT_CENTERED + int customBorder = m_widthCustomBorder; if ( (m_text->GetWindowStyleFlag() & wxBORDER_MASK) == wxNO_BORDER ) { // Centre textctrl @@ -1016,16 +1074,16 @@ void wxComboCtrlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust } } else -#else +#else // TEXTCTRL_TEXT_CENTERED wxUnusedVar(textCtrlXAdjust); wxUnusedVar(textCtrlYAdjust); -#endif +#endif // !TEXTCTRL_TEXT_CENTERED/TEXTCTRL_TEXT_CENTERED { // If it has border, have textctrl will the entire text field. m_text->SetSize( m_tcArea.x + m_widthCustomPaint, - customBorder, - sz.x - m_btnArea.width - m_widthCustomPaint - customBorder, - sz.y-(customBorder*2) ); + m_tcArea.y, + m_tcArea.width - m_widthCustomPaint, + m_tcArea.height ); } } @@ -1069,9 +1127,27 @@ wxSize wxComboCtrlBase::DoGetBestSize() const fhei += 1; #endif - wxSize ret(sizeText.x + COMBO_MARGIN + DEFAULT_DROPBUTTON_WIDTH, - fhei); +#ifdef __WXMAC__ + // these are the numbers from the HIG: + switch ( m_windowVariant ) + { + case wxWINDOW_VARIANT_NORMAL: + default : + fhei = 22; + break; + case wxWINDOW_VARIANT_SMALL: + fhei = 19; + break; + case wxWINDOW_VARIANT_MINI: + fhei = 15; + break; + } +#endif + + fhei += 2 * FOCUS_RING; + int width = sizeText.x + FOCUS_RING + COMBO_MARGIN + DEFAULT_DROPBUTTON_WIDTH; + wxSize ret(width, fhei); CacheBestSize(ret); return ret; } @@ -1149,6 +1225,26 @@ void wxComboCtrlBase::DoSetToolTip(wxToolTip *tooltip) } #endif // wxUSE_TOOLTIPS +#if wxUSE_VALIDATORS +void wxComboCtrlBase::SetValidator(const wxValidator& validator) +{ + wxTextCtrl* textCtrl = GetTextCtrl(); + + if ( textCtrl ) + textCtrl->SetValidator( validator ); +} + +wxValidator* wxComboCtrlBase::GetValidator() +{ + wxTextCtrl* textCtrl = GetTextCtrl(); + + if ( textCtrl ) + return textCtrl->GetValidator(); + + return wxControl::GetValidator(); +} +#endif // wxUSE_VALIDATORS + // ---------------------------------------------------------------------------- // painting // ---------------------------------------------------------------------------- @@ -1159,7 +1255,7 @@ void wxComboCtrlBase::PrepareBackground( wxDC& dc, const wxRect& rect, int flags { wxSize sz = GetClientSize(); bool isEnabled; - bool isFocused; // also selected + bool doDrawFocusRect; // also selected // For smaller size control (and for disabled background) use less spacing int focusSpacingX; @@ -1169,7 +1265,7 @@ void wxComboCtrlBase::PrepareBackground( wxDC& dc, const wxRect& rect, int flags { // Drawing control isEnabled = IsEnabled(); - isFocused = ShouldDrawFocus(); + doDrawFocusRect = ShouldDrawFocus() & !(m_iFlags & wxCC_FULL_BUTTON); // Windows-style: for smaller size control (and for disabled background) use less spacing focusSpacingX = isEnabled ? 2 : 1; @@ -1179,7 +1275,7 @@ void wxComboCtrlBase::PrepareBackground( wxDC& dc, const wxRect& rect, int flags { // Drawing a list item isEnabled = true; // they are never disabled - isFocused = flags & wxCONTROL_SELECTED ? true : false; + doDrawFocusRect = flags & wxCONTROL_SELECTED ? true : false; focusSpacingX = 0; focusSpacingY = 0; @@ -1199,12 +1295,13 @@ void wxComboCtrlBase::PrepareBackground( wxDC& dc, const wxRect& rect, int flags selRect.width -= wcp + (focusSpacingX*2); wxColour bgCol; + bool doDrawSelRect = true; if ( isEnabled ) { // If popup is hidden and this control is focused, // then draw the focus-indicator (selbgcolor background etc.). - if ( isFocused ) + if ( doDrawFocusRect ) { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT) ); bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); @@ -1212,18 +1309,30 @@ void wxComboCtrlBase::PrepareBackground( wxDC& dc, const wxRect& rect, int flags else { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) ); +#ifndef __WXMAC__ // see note in OnThemeChange + doDrawSelRect = false; bgCol = GetBackgroundColour(); +#else + bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); +#endif } } else { dc.SetTextForeground( wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT) ); +#ifndef __WXMAC__ // see note in OnThemeChange bgCol = GetBackgroundColour(); +#else + bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); +#endif } dc.SetBrush( bgCol ); - dc.SetPen( bgCol ); - dc.DrawRectangle( selRect ); + if ( doDrawSelRect ) + { + dc.SetPen( bgCol ); + dc.DrawRectangle( selRect ); + } // Don't clip exactly to the selection rectangle so we can draw // to the non-selected area in front of it. @@ -1238,14 +1347,13 @@ void wxComboCtrlBase::PrepareBackground( wxDC&, const wxRect&, int ) const } #endif -void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int paintBg ) +void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int flags ) { int drawState = m_btnState; -#ifdef __WXGTK__ - if ( GetPopupWindowState() >= Animating ) + if ( (m_iFlags & wxCC_BUTTON_STAYS_DOWN) && + GetPopupWindowState() >= Animating ) drawState |= wxCONTROL_PRESSED; -#endif wxRect drawRect(rect.x+m_btnSpacingX, rect.y+((rect.height-m_btnSize.y)/2), @@ -1265,8 +1373,11 @@ void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int paintBg ) if ( !m_bmpNormal.Ok() ) { + if ( flags & Button_BitmapOnly ) + return; + // Need to clear button background even if m_btn is present - if ( paintBg ) + if ( flags & Button_PaintBackground ) { wxColour bgCol; @@ -1305,7 +1416,7 @@ void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int paintBg ) { // If using blank button background, we need to clear its background // with button face colour instead of colour for rest of the control. - if ( paintBg ) + if ( flags & Button_PaintBackground ) { wxColour bgCol = GetParent()->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); //wxColour bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -1314,18 +1425,20 @@ void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int paintBg ) dc.DrawRectangle(rect); } - wxRendererNative::Get().DrawPushButton(this, - dc, - drawRect, - drawState); - + if ( !(flags & Button_BitmapOnly) ) + { + wxRendererNative::Get().DrawPushButton(this, + dc, + drawRect, + drawState); + } } else { // Need to clear button background even if m_btn is present // (assume non-button background was cleared just before this call so brushes are good) - if ( paintBg ) + if ( flags & Button_PaintBackground ) dc.DrawRectangle(rect); } @@ -1372,13 +1485,14 @@ void wxComboCtrlBase::OnTextCtrlEvent(wxCommandEvent& event) // call if cursor is on button area or mouse is captured for the button bool wxComboCtrlBase::HandleButtonMouseEvent( wxMouseEvent& event, - int flags ) + int flags ) { int type = event.GetEventType(); if ( type == wxEVT_MOTION ) { - if ( flags & wxCC_MF_ON_BUTTON ) + if ( (flags & wxCC_MF_ON_BUTTON) && + IsPopupWindowState(Hidden) ) { if ( !(m_btnState & wxCONTROL_CURRENT) ) { @@ -1447,6 +1561,11 @@ bool wxComboCtrlBase::HandleButtonMouseEvent( wxMouseEvent& event, else return false; + // Never have 'hot' state when popup is being shown + // (this is mostly needed because of the animation). + if ( !IsPopupWindowState(Hidden) ) + m_btnState &= ~wxCONTROL_CURRENT; + return true; } @@ -1469,7 +1588,7 @@ bool wxComboCtrlBase::PreprocessMouseEvent( wxMouseEvent& event, } #endif - // Filter out clicks on button immediately after popup dismiss (Windows like behaviour) + // Filter out clicks on button immediately after popup dismiss if ( evtType == wxEVT_LEFT_DOWN && t < m_timeCanAcceptClick ) { event.SetEventType(0); @@ -1577,16 +1696,31 @@ void wxComboCtrlBase::OnFocusEvent( wxFocusEvent& event ) { if ( event.GetEventType() == wxEVT_SET_FOCUS ) { -#ifndef __WXMAC__ wxWindow* tc = GetTextCtrl(); if ( tc && tc != DoFindFocus() ) +#ifdef __WXMAC__ + m_resetFocus = true; +#else + { tc->SetFocus(); + } #endif } Refresh(); } +void wxComboCtrlBase::OnIdleEvent( wxIdleEvent& WXUNUSED(event) ) +{ + if ( m_resetFocus ) + { + m_resetFocus = false; + wxWindow* tc = GetTextCtrl(); + if ( tc ) + tc->SetFocus(); + } +} + void wxComboCtrlBase::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event)) { OnThemeChange(); @@ -1621,7 +1755,7 @@ void wxComboCtrlBase::CreatePopup() m_popupWinType = SECONDARY_POPUP_TYPE; } else -#endif +#endif // wxComboPopupWindowBase2 { m_winPopup = new wxComboPopupWindow( this, wxNO_BORDER ); m_popupWinType = PRIMARY_POPUP_TYPE; @@ -1776,7 +1910,7 @@ void wxComboCtrlBase::ShowPopup() } winPopup->Enable(); - + wxASSERT( !m_popup || m_popup == popup ); // Consistency check. wxSize adjustedSize = m_popupInterface->GetAdjustedSize(widthPopup, @@ -1803,6 +1937,10 @@ void wxComboCtrlBase::ShowPopup() int rightX = scrPos.x + ctrlSz.x + m_extRight - szp.x; int leftX = scrPos.x - m_extLeft; + + if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ) + leftX -= ctrlSz.x; + int screenWidth = wxSystemSettings::GetMetric( wxSYS_SCREEN_X ); // If there is not enough horizontal space, anchor on the other side. @@ -1903,7 +2041,12 @@ void wxComboCtrlBase::DoShowPopup( const wxRect& rect, int WXUNUSED(flags) ) // (though the bug was probably fixed). winPopup->SetSize( rect ); - winPopup->Show(); +#if USES_WXPOPUPTRANSIENTWINDOW + if ( m_popupWinType == POPUPWIN_WXPOPUPTRANSIENTWINDOW ) + ((wxPopupTransientWindow*)winPopup)->Popup(m_popup); + else +#endif + winPopup->Show(); m_popupWinState = Visible; } @@ -1915,6 +2058,8 @@ void wxComboCtrlBase::DoShowPopup( const wxRect& rect, int WXUNUSED(flags) ) m_popupWinState = Hidden; } + + Refresh(); } void wxComboCtrlBase::OnPopupDismiss() @@ -2108,16 +2253,19 @@ void wxComboCtrlBase::SetValueWithEvent(const wxString& value, bool withEvent) m_text->SelectAll(); } - m_valueString = value; - - Refresh(); - // Since wxComboPopup may want to paint the combo as well, we need // to set the string value here (as well as sometimes in ShowPopup). - if ( m_valueString != value && m_popupInterface ) + if ( m_valueString != value ) { - m_popupInterface->SetStringValue(value); + m_valueString = value; + + EnsurePopupControl(); + + if (m_popupInterface) + m_popupInterface->SetStringValue(value); } + + Refresh(); } void wxComboCtrlBase::SetValue(const wxString& value)