1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/mac/carbon/spinbutt.cpp
3// Purpose:     wxSpinCtrl
4// Author:      Robert
5// Modified by: Mark Newsam (Based on GTK file)
6// RCS-ID:      $Id: spinctrl.cpp 62128 2009-09-25 15:24:54Z JS $
7// Copyright:   (c) Robert Roebling
8// Licence:     wxWindows licence
9/////////////////////////////////////////////////////////////////////////////
10
11#include "wx/wxprec.h"
12
13#if wxUSE_SPINCTRL
14
15#include "wx/spinctrl.h"
16
17#ifndef WX_PRECOMP
18    #include "wx/textctrl.h"
19    #include "wx/containr.h"
20#endif
21
22#include "wx/spinbutt.h"
23
24// ----------------------------------------------------------------------------
25// constants
26// ----------------------------------------------------------------------------
27
28// the focus rect around a text may have 4 pixels in each direction
29// we handle these problems right now in an extended vis region of a window
30static const wxCoord TEXTBORDER = 4 ;
31// the margin between the text control and the spin
32static const wxCoord MARGIN = 8 - TEXTBORDER;
33
34// ----------------------------------------------------------------------------
35// wxSpinCtrlText: text control used by spin control
36// ----------------------------------------------------------------------------
37
38class wxSpinCtrlText : public wxTextCtrl
39{
40public:
41    wxSpinCtrlText(wxSpinCtrl *spin, const wxString& value)
42        : wxTextCtrl(spin , wxID_ANY, value, wxDefaultPosition, wxSize(40, wxDefaultCoord))
43    {
44        m_spin = spin;
45
46        // remove the default minsize, the spinctrl will have one instead
47        SetMinSize(wxDefaultSize);
48    }
49
50    bool ProcessEvent(wxEvent &event)
51    {
52        // Hand button down events to wxSpinCtrl. Doesn't work.
53        if (event.GetEventType() == wxEVT_LEFT_DOWN && m_spin->ProcessEvent( event ))
54            return true;
55
56        return wxTextCtrl::ProcessEvent( event );
57    }
58
59protected:
60     void OnKillFocus(wxFocusEvent &event)
61     {
62         long l;
63         if ( !GetValue().ToLong(&l) )
64         {
65             // not a number at all
66             return;
67         }
68
69         // is within range
70         if (l < m_spin->GetMin())
71             l = m_spin->GetMin();
72         if (l > m_spin->GetMax())
73             l = m_spin->GetMax();
74
75         // Update text control
76         wxString str;
77         str.Printf( wxT("%d"), (int)l );
78         if (str != GetValue())
79             SetValue( str );
80
81         if (l != m_spin->m_oldValue)
82         {
83             // set value in spin button
84             // does that trigger an event?
85             m_spin->m_btn->SetValue( l );
86
87             // if not
88             wxCommandEvent event(wxEVT_COMMAND_SPINCTRL_UPDATED, m_spin->GetId());
89             event.SetEventObject(m_spin);
90             event.SetInt(l);
91             m_spin->GetEventHandler()->ProcessEvent(event);
92
93             m_spin->m_oldValue = l;
94         }
95    }
96
97    void OnTextChange(wxCommandEvent& event)
98    {
99        int val;
100        if ( m_spin->GetTextValue(&val) )
101        {
102            m_spin->GetSpinButton()->SetValue(val);
103
104            // If we're already processing a text update from m_spin,
105            // don't send it again, since we could end up recursing
106            // infinitely.
107            if (event.GetId() == m_spin->GetId())
108            {
109                event.Skip();
110                return;
111            }
112
113            // Send event that the text was manually changed
114            wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, m_spin->GetId());
115            event.SetEventObject(m_spin);
116            event.SetString(m_spin->GetText()->GetValue());
117            event.SetInt(val);
118
119            m_spin->GetEventHandler()->ProcessEvent(event);
120        }
121
122        event.Skip();
123    }
124
125private:
126    wxSpinCtrl *m_spin;
127
128    DECLARE_EVENT_TABLE()
129};
130
131BEGIN_EVENT_TABLE(wxSpinCtrlText, wxTextCtrl)
132    EVT_TEXT(wxID_ANY, wxSpinCtrlText::OnTextChange)
133    EVT_KILL_FOCUS( wxSpinCtrlText::OnKillFocus)
134END_EVENT_TABLE()
135
136// ----------------------------------------------------------------------------
137// wxSpinCtrlButton: spin button used by spin control
138// ----------------------------------------------------------------------------
139
140class wxSpinCtrlButton : public wxSpinButton
141{
142public:
143    wxSpinCtrlButton(wxSpinCtrl *spin, int style)
144        : wxSpinButton(spin )
145    {
146        m_spin = spin;
147        SetWindowStyle(style | wxSP_VERTICAL);
148
149        // TODO: The spin button gets truncated a little bit due to size
150        // differences so change it's default size a bit.  SMALL still gets a
151        // bit truncated, but MINI seems to be too small...  Readdress this
152        // when the textctrl issues are all sorted out.
153        //SetWindowVariant(wxWINDOW_VARIANT_SMALL);
154
155        // remove the default minsize, the spinctrl will have one instead
156        SetMinSize(wxDefaultSize);
157    }
158
159protected:
160    void OnSpinButton(wxSpinEvent& eventSpin)
161    {
162        int pos = eventSpin.GetPosition();
163        m_spin->SetTextValue(pos);
164
165        wxCommandEvent event(wxEVT_COMMAND_SPINCTRL_UPDATED, m_spin->GetId());
166        event.SetEventObject(m_spin);
167        event.SetInt(pos);
168
169        m_spin->GetEventHandler()->ProcessEvent(event);
170
171        m_spin->m_oldValue = pos;
172    }
173
174private:
175    wxSpinCtrl *m_spin;
176
177    DECLARE_EVENT_TABLE()
178};
179
180BEGIN_EVENT_TABLE(wxSpinCtrlButton, wxSpinButton)
181    EVT_SPIN(wxID_ANY, wxSpinCtrlButton::OnSpinButton)
182END_EVENT_TABLE()
183
184IMPLEMENT_DYNAMIC_CLASS(wxSpinCtrl, wxControl)
185
186BEGIN_EVENT_TABLE(wxSpinCtrl, wxControl)
187    WX_EVENT_TABLE_CONTROL_CONTAINER(wxSpinCtrl)
188END_EVENT_TABLE()
189
190WX_DELEGATE_TO_CONTROL_CONTAINER(wxSpinCtrl, wxControl)
191
192
193// ============================================================================
194// implementation
195// ============================================================================
196
197// ----------------------------------------------------------------------------
198// wxSpinCtrl creation
199// ----------------------------------------------------------------------------
200
201void wxSpinCtrl::Init()
202{
203    m_text = NULL;
204    m_btn = NULL;
205    m_container.SetContainerWindow(this);
206}
207
208bool wxSpinCtrl::Create(wxWindow *parent,
209                        wxWindowID id,
210                        const wxString& value,
211                        const wxPoint& pos,
212                        const wxSize& size,
213                        long style,
214                        int min,
215                        int max,
216                        int initial,
217                        const wxString& name)
218{
219    m_macIsUserPane = true;
220    if ( !wxControl::Create(parent, id, pos, size, style,
221                            wxDefaultValidator, name) )
222    {
223        return false;
224    }
225
226    // the string value overrides the numeric one (for backwards compatibility
227    // reasons and also because it is simpler to satisfy the string value which
228    // comes much sooner in the list of arguments and leave the initial
229    // parameter unspecified)
230    if ( !value.empty() )
231    {
232        long l;
233        if ( value.ToLong(&l) )
234            initial = l;
235    }
236
237    wxSize csize = size ;
238    m_text = new wxSpinCtrlText(this, value);
239    m_btn = new wxSpinCtrlButton(this, style);
240
241    m_btn->SetRange(min, max);
242    m_btn->SetValue(initial);
243    // make it different
244    m_oldValue = GetMin()-1;
245
246    if ( size.x == wxDefaultCoord ){
247        csize.x = m_text->GetSize().x + MARGIN + m_btn->GetSize().x ;
248    }
249
250    if ( size.y == wxDefaultCoord ) {
251        csize.y = m_text->GetSize().y + 2 * TEXTBORDER ; //allow for text border highlights
252        if ( m_btn->GetSize().y > csize.y )
253            csize.y = m_btn->GetSize().y ;
254    }
255
256    //SetSize(csize);
257
258    //MacPostControlCreate(pos, csize);
259    SetInitialSize(csize);
260
261    return true;
262}
263
264wxSpinCtrl::~wxSpinCtrl()
265{
266    // delete the controls now, don't leave them alive even though they would
267    // still be eventually deleted by our parent - but it will be too late, the
268    // user code expects them to be gone now
269    delete m_text;
270    m_text = NULL ;
271    delete m_btn;
272    m_btn = NULL ;
273}
274
275// ----------------------------------------------------------------------------
276// geometry
277// ----------------------------------------------------------------------------
278
279wxSize wxSpinCtrl::DoGetBestSize() const
280{
281    if (!m_btn || !m_text)
282        return GetSize();
283
284    wxSize sizeBtn = m_btn->GetBestSize(),
285           sizeText = m_text->GetBestSize();
286
287    sizeText.y += 2 * TEXTBORDER ;
288    sizeText.x += 2 * TEXTBORDER ;
289
290    int height;
291    if (sizeText.y > sizeBtn.y)
292        height = sizeText.y;
293    else
294        height = sizeBtn.y;
295
296    return wxSize(sizeBtn.x + sizeText.x + MARGIN, height );
297}
298
299void wxSpinCtrl::DoMoveWindow(int x, int y, int width, int height)
300{
301    // position the subcontrols inside the client area
302    wxSize sizeBtn = m_btn->GetSize();
303    wxSize sizeText = m_text->GetSize();
304
305    wxControl::DoMoveWindow(x, y, width, height);
306
307    wxCoord wText = width - sizeBtn.x - MARGIN - 2 * TEXTBORDER;
308
309    m_text->SetSize(TEXTBORDER, (height - sizeText.y) / 2, wText, -1);
310    m_btn->SetSize(0 + wText + MARGIN + 2 * TEXTBORDER , (height - sizeBtn.y) / 2 , -1, -1 );
311}
312
313// ----------------------------------------------------------------------------
314// operations forwarded to the subcontrols
315// ----------------------------------------------------------------------------
316
317bool wxSpinCtrl::Enable(bool enable)
318{
319    if ( !wxControl::Enable(enable) )
320        return false;
321    return true;
322}
323
324bool wxSpinCtrl::Show(bool show)
325{
326    if ( !wxControl::Show(show) )
327        return false;
328    return true;
329}
330
331// ----------------------------------------------------------------------------
332// value and range access
333// ----------------------------------------------------------------------------
334
335bool wxSpinCtrl::GetTextValue(int *val) const
336{
337    long l;
338    if ( !m_text->GetValue().ToLong(&l) )
339    {
340        // not a number at all
341        return false;
342    }
343
344    if ( l < GetMin() || l > GetMax() )
345    {
346        // out of range
347        return false;
348    }
349
350    *val = l;
351
352    return true;
353}
354
355int wxSpinCtrl::GetValue() const
356{
357    return m_btn ? m_btn->GetValue() : 0;
358}
359
360int wxSpinCtrl::GetMin() const
361{
362    return m_btn ? m_btn->GetMin() : 0;
363}
364
365int wxSpinCtrl::GetMax() const
366{
367    return m_btn ? m_btn->GetMax() : 0;
368}
369
370// ----------------------------------------------------------------------------
371// changing value and range
372// ----------------------------------------------------------------------------
373
374void wxSpinCtrl::SetTextValue(int val)
375{
376    wxCHECK_RET( m_text, _T("invalid call to wxSpinCtrl::SetTextValue") );
377
378    m_text->SetValue(wxString::Format(_T("%d"), val));
379
380    // select all text
381    m_text->SetSelection(0, -1);
382
383    m_text->SetInsertionPointEnd();
384
385    // and give focus to the control!
386    // m_text->SetFocus();    Why???? TODO.
387}
388
389void wxSpinCtrl::SetValue(int val)
390{
391    wxCHECK_RET( m_btn, _T("invalid call to wxSpinCtrl::SetValue") );
392
393    SetTextValue(val);
394
395    m_btn->SetValue(val);
396    m_oldValue = val;
397}
398
399void wxSpinCtrl::SetValue(const wxString& text)
400{
401    wxCHECK_RET( m_text, _T("invalid call to wxSpinCtrl::SetValue") );
402
403    long val;
404    if ( text.ToLong(&val) && ((val > INT_MIN) && (val < INT_MAX)) )
405    {
406        SetValue((int)val);
407    }
408    else // not a number at all or out of range
409    {
410        m_text->SetValue(text);
411        m_text->SetSelection(0, -1);
412    }
413}
414
415void wxSpinCtrl::SetRange(int min, int max)
416{
417    wxCHECK_RET( m_btn, _T("invalid call to wxSpinCtrl::SetRange") );
418
419    m_btn->SetRange(min, max);
420}
421
422void wxSpinCtrl::SetSelection(long from, long to)
423{
424    // if from and to are both -1, it means (in wxWidgets) that all text should
425    // be selected
426    if ( (from == -1) && (to == -1) )
427    {
428        from = 0;
429    }
430    m_text->SetSelection(from, to);
431}
432
433#endif // wxUSE_SPINCTRL
434