]> git.saurik.com Git - wxWidgets.git/blame - samples/widgets/listbox.cpp
Implemented and tested EXPANDED etc events for wxGTK and in the sample
[wxWidgets.git] / samples / widgets / listbox.cpp
CommitLineData
32b8ec41 1/////////////////////////////////////////////////////////////////////////////
be5a51fb 2// Program: wxWidgets Widgets Sample
32b8ec41
VZ
3// Name: listbox.cpp
4// Purpose: Part of the widgets sample showing wxListbox
5// Author: Vadim Zeitlin
6// Created: 27.03.01
7// Id: $Id$
8// Copyright: (c) 2001 Vadim Zeitlin
9// License: wxWindows license
10/////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20// for compilers that support precompilation, includes "wx/wx.h".
21#include "wx/wxprec.h"
22
23#ifdef __BORLANDC__
24 #pragma hdrstop
25#endif
26
eecdb000
WS
27#if wxUSE_LISTBOX
28
32b8ec41
VZ
29// for all others, include the necessary headers
30#ifndef WX_PRECOMP
31 #include "wx/log.h"
32
3bb70c40 33 #include "wx/bitmap.h"
32b8ec41
VZ
34 #include "wx/button.h"
35 #include "wx/checkbox.h"
36 #include "wx/combobox.h"
37 #include "wx/listbox.h"
38 #include "wx/radiobox.h"
39 #include "wx/statbox.h"
40 #include "wx/textctrl.h"
41#endif
42
43#include "wx/sizer.h"
44
45#include "wx/checklst.h"
46
a236aa20 47#include "itemcontainer.h"
32b8ec41 48#include "widgets.h"
eecdb000 49
32b8ec41
VZ
50#include "icons/listbox.xpm"
51
52// ----------------------------------------------------------------------------
53// constants
54// ----------------------------------------------------------------------------
55
56// control ids
57enum
58{
f0fa4312 59 ListboxPage_Reset = wxID_HIGHEST,
32b8ec41
VZ
60 ListboxPage_Add,
61 ListboxPage_AddText,
62 ListboxPage_AddSeveral,
63 ListboxPage_AddMany,
64 ListboxPage_Clear,
65 ListboxPage_Change,
66 ListboxPage_ChangeText,
67 ListboxPage_Delete,
68 ListboxPage_DeleteText,
69 ListboxPage_DeleteSel,
a236aa20
VZ
70 ListboxPage_Listbox,
71 ListboxPage_ContainerTests
32b8ec41
VZ
72};
73
74// ----------------------------------------------------------------------------
75// ListboxWidgetsPage
76// ----------------------------------------------------------------------------
77
a236aa20 78class ListboxWidgetsPage : public ItemContainerWidgetsPage
32b8ec41
VZ
79{
80public:
f2fdc4d5 81 ListboxWidgetsPage(WidgetsBookCtrl *book, wxImageList *imaglist);
32b8ec41 82
195df7a7 83 virtual wxControl *GetWidget() const { return m_lbox; }
a236aa20 84 virtual wxItemContainer* GetContainer() const { return m_lbox; }
1301e228 85 virtual void RecreateWidget() { CreateLbox(); }
195df7a7 86
453535a7
WS
87 // lazy creation of the content
88 virtual void CreateContent();
89
32b8ec41
VZ
90protected:
91 // event handlers
92 void OnButtonReset(wxCommandEvent& event);
93 void OnButtonChange(wxCommandEvent& event);
94 void OnButtonDelete(wxCommandEvent& event);
95 void OnButtonDeleteSel(wxCommandEvent& event);
96 void OnButtonClear(wxCommandEvent& event);
97 void OnButtonAdd(wxCommandEvent& event);
98 void OnButtonAddSeveral(wxCommandEvent& event);
99 void OnButtonAddMany(wxCommandEvent& event);
100
101 void OnListbox(wxCommandEvent& event);
102 void OnListboxDClick(wxCommandEvent& event);
103 void OnCheckListbox(wxCommandEvent& event);
104
105 void OnCheckOrRadioBox(wxCommandEvent& event);
106
107 void OnUpdateUIAddSeveral(wxUpdateUIEvent& event);
108 void OnUpdateUIClearButton(wxUpdateUIEvent& event);
109 void OnUpdateUIDeleteButton(wxUpdateUIEvent& event);
110 void OnUpdateUIDeleteSelButton(wxUpdateUIEvent& event);
111 void OnUpdateUIResetButton(wxUpdateUIEvent& event);
112
113 // reset the listbox parameters
114 void Reset();
115
116 // (re)create the listbox
117 void CreateLbox();
118
119 // listbox parameters
120 // ------------------
121
122 // the selection mode
123 enum LboxSelection
124 {
125 LboxSel_Single,
126 LboxSel_Extended,
127 LboxSel_Multiple
128 } m_lboxSelMode;
129
130 // should it be sorted?
131 bool m_sorted;
132
133 // should it have horz scroll/vert scrollbar permanently shown?
134 bool m_horzScroll,
135 m_vertScrollAlways;
136
137 // the controls
138 // ------------
139
140 // the sel mode radiobox
141 wxRadioBox *m_radioSelMode;
142
143 // the checkboxes
822b9009 144 wxCheckBox *m_chkVScroll,
32b8ec41 145 *m_chkHScroll,
822b9009
VZ
146 *m_chkCheck,
147 *m_chkSort,
148 *m_chkOwnerDraw;
32b8ec41
VZ
149
150 // the listbox itself and the sizer it is in
b3c33d35
WS
151#ifdef __WXWINCE__
152 wxListBoxBase
153#else
453535a7 154 wxListBox
b3c33d35
WS
155#endif
156 *m_lbox;
157
32b8ec41
VZ
158 wxSizer *m_sizerLbox;
159
160 // the text entries for "Add/change string" and "Delete" buttons
161 wxTextCtrl *m_textAdd,
162 *m_textChange,
163 *m_textDelete;
164
165private:
5e173f35
GD
166 DECLARE_EVENT_TABLE()
167 DECLARE_WIDGETS_PAGE(ListboxWidgetsPage)
32b8ec41
VZ
168};
169
170// ----------------------------------------------------------------------------
171// event tables
172// ----------------------------------------------------------------------------
173
174BEGIN_EVENT_TABLE(ListboxWidgetsPage, WidgetsPage)
175 EVT_BUTTON(ListboxPage_Reset, ListboxWidgetsPage::OnButtonReset)
176 EVT_BUTTON(ListboxPage_Change, ListboxWidgetsPage::OnButtonChange)
177 EVT_BUTTON(ListboxPage_Delete, ListboxWidgetsPage::OnButtonDelete)
178 EVT_BUTTON(ListboxPage_DeleteSel, ListboxWidgetsPage::OnButtonDeleteSel)
179 EVT_BUTTON(ListboxPage_Clear, ListboxWidgetsPage::OnButtonClear)
180 EVT_BUTTON(ListboxPage_Add, ListboxWidgetsPage::OnButtonAdd)
181 EVT_BUTTON(ListboxPage_AddSeveral, ListboxWidgetsPage::OnButtonAddSeveral)
182 EVT_BUTTON(ListboxPage_AddMany, ListboxWidgetsPage::OnButtonAddMany)
a236aa20 183 EVT_BUTTON(ListboxPage_ContainerTests, ItemContainerWidgetsPage::OnButtonTestItemContainer)
32b8ec41
VZ
184
185 EVT_TEXT_ENTER(ListboxPage_AddText, ListboxWidgetsPage::OnButtonAdd)
186 EVT_TEXT_ENTER(ListboxPage_DeleteText, ListboxWidgetsPage::OnButtonDelete)
187
188 EVT_UPDATE_UI(ListboxPage_Reset, ListboxWidgetsPage::OnUpdateUIResetButton)
189 EVT_UPDATE_UI(ListboxPage_AddSeveral, ListboxWidgetsPage::OnUpdateUIAddSeveral)
190 EVT_UPDATE_UI(ListboxPage_Clear, ListboxWidgetsPage::OnUpdateUIClearButton)
191 EVT_UPDATE_UI(ListboxPage_DeleteText, ListboxWidgetsPage::OnUpdateUIClearButton)
192 EVT_UPDATE_UI(ListboxPage_Delete, ListboxWidgetsPage::OnUpdateUIDeleteButton)
193 EVT_UPDATE_UI(ListboxPage_Change, ListboxWidgetsPage::OnUpdateUIDeleteSelButton)
194 EVT_UPDATE_UI(ListboxPage_ChangeText, ListboxWidgetsPage::OnUpdateUIDeleteSelButton)
195 EVT_UPDATE_UI(ListboxPage_DeleteSel, ListboxWidgetsPage::OnUpdateUIDeleteSelButton)
196
197 EVT_LISTBOX(ListboxPage_Listbox, ListboxWidgetsPage::OnListbox)
198 EVT_LISTBOX_DCLICK(ListboxPage_Listbox, ListboxWidgetsPage::OnListboxDClick)
199 EVT_CHECKLISTBOX(ListboxPage_Listbox, ListboxWidgetsPage::OnCheckListbox)
200
206d3a16
JS
201 EVT_CHECKBOX(wxID_ANY, ListboxWidgetsPage::OnCheckOrRadioBox)
202 EVT_RADIOBOX(wxID_ANY, ListboxWidgetsPage::OnCheckOrRadioBox)
32b8ec41
VZ
203END_EVENT_TABLE()
204
205// ============================================================================
206// implementation
207// ============================================================================
208
f0fa4312
WS
209#if defined(__WXUNIVERSAL__)
210 #define FAMILY_CTRLS UNIVERSAL_CTRLS
211#else
212 #define FAMILY_CTRLS NATIVE_CTRLS
213#endif
214
f2fdc4d5 215IMPLEMENT_WIDGETS_PAGE(ListboxWidgetsPage, _T("Listbox"),
f0fa4312 216 FAMILY_CTRLS | WITH_ITEMS_CTRLS
f2fdc4d5 217 );
32b8ec41 218
f2fdc4d5 219ListboxWidgetsPage::ListboxWidgetsPage(WidgetsBookCtrl *book,
32b8ec41 220 wxImageList *imaglist)
a236aa20 221 : ItemContainerWidgetsPage(book, imaglist, listbox_xpm)
32b8ec41 222{
32b8ec41
VZ
223 // init everything
224 m_radioSelMode = (wxRadioBox *)NULL;
225
226 m_chkVScroll =
227 m_chkHScroll =
228 m_chkCheck =
822b9009
VZ
229 m_chkSort =
230 m_chkOwnerDraw = (wxCheckBox *)NULL;
32b8ec41 231
b3c33d35 232 m_lbox = NULL;
32b8ec41
VZ
233 m_sizerLbox = (wxSizer *)NULL;
234
453535a7
WS
235}
236
237void ListboxWidgetsPage::CreateContent()
238{
32b8ec41
VZ
239 /*
240 What we create here is a frame having 3 panes: style pane is the
241 leftmost one, in the middle the pane with buttons allowing to perform
242 miscellaneous listbox operations and the pane containing the listbox
243 itself to the right
244 */
245 wxSizer *sizerTop = new wxBoxSizer(wxHORIZONTAL);
246
247 // left pane
206d3a16
JS
248 wxStaticBox *box = new wxStaticBox(this, wxID_ANY,
249 _T("&Set listbox parameters"));
32b8ec41
VZ
250 wxSizer *sizerLeft = new wxStaticBoxSizer(box, wxVERTICAL);
251
252 static const wxString modes[] =
253 {
254 _T("single"),
255 _T("extended"),
256 _T("multiple"),
257 };
258
206d3a16 259 m_radioSelMode = new wxRadioBox(this, wxID_ANY, _T("Selection &mode:"),
32b8ec41
VZ
260 wxDefaultPosition, wxDefaultSize,
261 WXSIZEOF(modes), modes,
262 1, wxRA_SPECIFY_COLS);
263
264 m_chkVScroll = CreateCheckBoxAndAddToSizer
265 (
266 sizerLeft,
267 _T("Always show &vertical scrollbar")
268 );
269 m_chkHScroll = CreateCheckBoxAndAddToSizer
270 (
271 sizerLeft,
272 _T("Show &horizontal scrollbar")
273 );
274 m_chkCheck = CreateCheckBoxAndAddToSizer(sizerLeft, _T("&Check list box"));
275 m_chkSort = CreateCheckBoxAndAddToSizer(sizerLeft, _T("&Sort items"));
822b9009 276 m_chkOwnerDraw = CreateCheckBoxAndAddToSizer(sizerLeft, _T("&Owner drawn"));
32b8ec41
VZ
277
278 sizerLeft->Add(5, 5, 0, wxGROW | wxALL, 5); // spacer
279 sizerLeft->Add(m_radioSelMode, 0, wxGROW | wxALL, 5);
280
281 wxButton *btn = new wxButton(this, ListboxPage_Reset, _T("&Reset"));
282 sizerLeft->Add(btn, 0, wxALIGN_CENTRE_HORIZONTAL | wxALL, 15);
283
284 // middle pane
206d3a16
JS
285 wxStaticBox *box2 = new wxStaticBox(this, wxID_ANY,
286 _T("&Change listbox contents"));
32b8ec41
VZ
287 wxSizer *sizerMiddle = new wxStaticBoxSizer(box2, wxVERTICAL);
288
289 wxSizer *sizerRow = new wxBoxSizer(wxHORIZONTAL);
290 btn = new wxButton(this, ListboxPage_Add, _T("&Add this string"));
291 m_textAdd = new wxTextCtrl(this, ListboxPage_AddText, _T("test item 0"));
292 sizerRow->Add(btn, 0, wxRIGHT, 5);
293 sizerRow->Add(m_textAdd, 1, wxLEFT, 5);
294 sizerMiddle->Add(sizerRow, 0, wxALL | wxGROW, 5);
295
296 btn = new wxButton(this, ListboxPage_AddSeveral, _T("&Insert a few strings"));
297 sizerMiddle->Add(btn, 0, wxALL | wxGROW, 5);
298
299 btn = new wxButton(this, ListboxPage_AddMany, _T("Add &many strings"));
300 sizerMiddle->Add(btn, 0, wxALL | wxGROW, 5);
301
302 sizerRow = new wxBoxSizer(wxHORIZONTAL);
303 btn = new wxButton(this, ListboxPage_Change, _T("C&hange current"));
206d3a16 304 m_textChange = new wxTextCtrl(this, ListboxPage_ChangeText, wxEmptyString);
32b8ec41
VZ
305 sizerRow->Add(btn, 0, wxRIGHT, 5);
306 sizerRow->Add(m_textChange, 1, wxLEFT, 5);
307 sizerMiddle->Add(sizerRow, 0, wxALL | wxGROW, 5);
308
309 sizerRow = new wxBoxSizer(wxHORIZONTAL);
310 btn = new wxButton(this, ListboxPage_Delete, _T("&Delete this item"));
206d3a16 311 m_textDelete = new wxTextCtrl(this, ListboxPage_DeleteText, wxEmptyString);
32b8ec41
VZ
312 sizerRow->Add(btn, 0, wxRIGHT, 5);
313 sizerRow->Add(m_textDelete, 1, wxLEFT, 5);
314 sizerMiddle->Add(sizerRow, 0, wxALL | wxGROW, 5);
315
316 btn = new wxButton(this, ListboxPage_DeleteSel, _T("Delete &selection"));
317 sizerMiddle->Add(btn, 0, wxALL | wxGROW, 5);
318
319 btn = new wxButton(this, ListboxPage_Clear, _T("&Clear"));
320 sizerMiddle->Add(btn, 0, wxALL | wxGROW, 5);
321
a236aa20
VZ
322 btn = new wxButton(this, ListboxPage_ContainerTests, _T("Run &tests"));
323 sizerMiddle->Add(btn, 0, wxALL | wxGROW, 5);
324
32b8ec41
VZ
325 // right pane
326 wxSizer *sizerRight = new wxBoxSizer(wxVERTICAL);
327 m_lbox = new wxListBox(this, ListboxPage_Listbox,
328 wxDefaultPosition, wxDefaultSize,
329 0, NULL,
330 wxLB_HSCROLL);
331 sizerRight->Add(m_lbox, 1, wxGROW | wxALL, 5);
7b127900 332 sizerRight->SetMinSize(150, 0);
32b8ec41
VZ
333 m_sizerLbox = sizerRight; // save it to modify it later
334
335 // the 3 panes panes compose the window
336 sizerTop->Add(sizerLeft, 0, wxGROW | (wxALL & ~wxLEFT), 10);
337 sizerTop->Add(sizerMiddle, 1, wxGROW | wxALL, 10);
338 sizerTop->Add(sizerRight, 1, wxGROW | (wxALL & ~wxRIGHT), 10);
339
340 // final initializations
341 Reset();
342
32b8ec41 343 SetSizer(sizerTop);
32b8ec41
VZ
344}
345
346// ----------------------------------------------------------------------------
347// operations
348// ----------------------------------------------------------------------------
349
350void ListboxWidgetsPage::Reset()
351{
352 m_radioSelMode->SetSelection(LboxSel_Single);
206d3a16 353 m_chkVScroll->SetValue(false);
822b9009
VZ
354 m_chkHScroll->SetValue(true);
355 m_chkCheck->SetValue(false);
356 m_chkSort->SetValue(false);
357 m_chkOwnerDraw->SetValue(false);
32b8ec41
VZ
358}
359
360void ListboxWidgetsPage::CreateLbox()
361{
1301e228 362 int flags = ms_defaultFlags;
32b8ec41
VZ
363 switch ( m_radioSelMode->GetSelection() )
364 {
365 default:
366 wxFAIL_MSG( _T("unexpected radio box selection") );
367
368 case LboxSel_Single: flags |= wxLB_SINGLE; break;
369 case LboxSel_Extended: flags |= wxLB_EXTENDED; break;
370 case LboxSel_Multiple: flags |= wxLB_MULTIPLE; break;
371 }
372
373 if ( m_chkVScroll->GetValue() )
374 flags |= wxLB_ALWAYS_SB;
375 if ( m_chkHScroll->GetValue() )
376 flags |= wxLB_HSCROLL;
377 if ( m_chkSort->GetValue() )
378 flags |= wxLB_SORT;
822b9009
VZ
379 if ( m_chkOwnerDraw->GetValue() )
380 flags |= wxLB_OWNERDRAW;
32b8ec41
VZ
381
382 wxArrayString items;
383 if ( m_lbox )
384 {
385 int count = m_lbox->GetCount();
386 for ( int n = 0; n < count; n++ )
387 {
388 items.Add(m_lbox->GetString(n));
389 }
390
12a3f227 391 m_sizerLbox->Detach( m_lbox );
32b8ec41
VZ
392 delete m_lbox;
393 }
394
e640f823 395#if wxUSE_CHECKLISTBOX
32b8ec41
VZ
396 if ( m_chkCheck->GetValue() )
397 {
398 m_lbox = new wxCheckListBox(this, ListboxPage_Listbox,
399 wxDefaultPosition, wxDefaultSize,
400 0, NULL,
401 flags);
402 }
403 else // just a listbox
e640f823 404#endif
32b8ec41
VZ
405 {
406 m_lbox = new wxListBox(this, ListboxPage_Listbox,
407 wxDefaultPosition, wxDefaultSize,
408 0, NULL,
409 flags);
410 }
411
412 m_lbox->Set(items);
413 m_sizerLbox->Add(m_lbox, 1, wxGROW | wxALL, 5);
414 m_sizerLbox->Layout();
415}
416
417// ----------------------------------------------------------------------------
418// event handlers
419// ----------------------------------------------------------------------------
420
421void ListboxWidgetsPage::OnButtonReset(wxCommandEvent& WXUNUSED(event))
422{
423 Reset();
424
425 CreateLbox();
426}
427
428void ListboxWidgetsPage::OnButtonChange(wxCommandEvent& WXUNUSED(event))
429{
430 wxArrayInt selections;
431 int count = m_lbox->GetSelections(selections);
432 wxString s = m_textChange->GetValue();
433 for ( int n = 0; n < count; n++ )
434 {
435 m_lbox->SetString(selections[n], s);
436 }
437}
438
439void ListboxWidgetsPage::OnButtonDelete(wxCommandEvent& WXUNUSED(event))
440{
441 unsigned long n;
442 if ( !m_textDelete->GetValue().ToULong(&n) ||
443 (n >= (unsigned)m_lbox->GetCount()) )
444 {
445 return;
446 }
447
448 m_lbox->Delete(n);
449}
450
451void ListboxWidgetsPage::OnButtonDeleteSel(wxCommandEvent& WXUNUSED(event))
452{
453 wxArrayInt selections;
454 int n = m_lbox->GetSelections(selections);
455 while ( n > 0 )
456 {
457 m_lbox->Delete(selections[--n]);
458 }
459}
460
c02e5a31 461void ListboxWidgetsPage::OnButtonClear(wxCommandEvent& WXUNUSED(event))
32b8ec41
VZ
462{
463 m_lbox->Clear();
464}
465
c02e5a31 466void ListboxWidgetsPage::OnButtonAdd(wxCommandEvent& WXUNUSED(event))
32b8ec41 467{
0c61716c 468 static unsigned int s_item = 0;
32b8ec41
VZ
469
470 wxString s = m_textAdd->GetValue();
471 if ( !m_textAdd->IsModified() )
472 {
473 // update the default string
474 m_textAdd->SetValue(wxString::Format(_T("test item %u"), ++s_item));
475 }
476
477 m_lbox->Append(s);
478}
479
480void ListboxWidgetsPage::OnButtonAddMany(wxCommandEvent& WXUNUSED(event))
481{
482 // "many" means 1000 here
0c61716c 483 for ( unsigned int n = 0; n < 1000; n++ )
32b8ec41
VZ
484 {
485 m_lbox->Append(wxString::Format(_T("item #%u"), n));
486 }
487}
488
c02e5a31 489void ListboxWidgetsPage::OnButtonAddSeveral(wxCommandEvent& WXUNUSED(event))
32b8ec41
VZ
490{
491 wxArrayString items;
492 items.Add(_T("First"));
493 items.Add(_T("another one"));
494 items.Add(_T("and the last (very very very very very very very very very very long) one"));
495 m_lbox->InsertItems(items, 0);
496}
497
498void ListboxWidgetsPage::OnUpdateUIResetButton(wxUpdateUIEvent& event)
499{
500 event.Enable( (m_radioSelMode->GetSelection() != LboxSel_Single) ||
501 m_chkSort->GetValue() ||
822b9009 502 m_chkOwnerDraw->GetValue() ||
32b8ec41
VZ
503 !m_chkHScroll->GetValue() ||
504 m_chkVScroll->GetValue() );
505}
506
507void ListboxWidgetsPage::OnUpdateUIDeleteButton(wxUpdateUIEvent& event)
508{
509 unsigned long n;
510 event.Enable(m_textDelete->GetValue().ToULong(&n) &&
511 (n < (unsigned)m_lbox->GetCount()));
512}
513
514void ListboxWidgetsPage::OnUpdateUIDeleteSelButton(wxUpdateUIEvent& event)
515{
516 wxArrayInt selections;
517 event.Enable(m_lbox->GetSelections(selections) != 0);
518}
519
520void ListboxWidgetsPage::OnUpdateUIClearButton(wxUpdateUIEvent& event)
521{
522 event.Enable(m_lbox->GetCount() != 0);
523}
524
525void ListboxWidgetsPage::OnUpdateUIAddSeveral(wxUpdateUIEvent& event)
526{
527 event.Enable(!(m_lbox->GetWindowStyle() & wxLB_SORT));
528}
529
530void ListboxWidgetsPage::OnListbox(wxCommandEvent& event)
531{
a0086878 532 long sel = event.GetSelection();
32b8ec41
VZ
533 m_textDelete->SetValue(wxString::Format(_T("%ld"), sel));
534
a0086878
JS
535 if (event.IsSelection())
536 wxLogMessage(_T("Listbox item %ld selected"), sel);
537 else
538 wxLogMessage(_T("Listbox item %ld deselected"), sel);
32b8ec41
VZ
539}
540
541void ListboxWidgetsPage::OnListboxDClick(wxCommandEvent& event)
542{
33cf9a19 543 wxLogMessage( _T("Listbox item %d double clicked"), event.GetInt() );
32b8ec41
VZ
544}
545
546void ListboxWidgetsPage::OnCheckListbox(wxCommandEvent& event)
547{
33cf9a19 548 wxLogMessage( _T("Listbox item %d toggled"), event.GetInt() );
32b8ec41
VZ
549}
550
c02e5a31 551void ListboxWidgetsPage::OnCheckOrRadioBox(wxCommandEvent& WXUNUSED(event))
32b8ec41
VZ
552{
553 CreateLbox();
554}
555
eecdb000 556#endif // wxUSE_LISTBOX