]> git.saurik.com Git - wxWidgets.git/blame - src/motif/choice.cpp
fixed crash in wxDb::Open() in Unicode build due to wrong interpretation of BufferLen...
[wxWidgets.git] / src / motif / choice.cpp
CommitLineData
4bb6408c 1/////////////////////////////////////////////////////////////////////////////
11e62fe6 2// Name: src/motif/choice.cpp
4bb6408c
JS
3// Purpose: wxChoice
4// Author: Julian Smart
5// Modified by:
6// Created: 17/09/98
7// RCS-ID: $Id$
8// Copyright: (c) Julian Smart
312ebad4 9// Licence: wxWindows licence
4bb6408c
JS
10/////////////////////////////////////////////////////////////////////////////
11
1248b41f
MB
12// For compilers that support precompilation, includes "wx.h".
13#include "wx/wxprec.h"
14
8228b893
WS
15#if wxUSE_CHOICE
16
bcd055ae 17#ifdef __VMS
4dff3400
JJ
18#define XtDisplay XTDISPLAY
19#define XtParent XTPARENT
20#endif
21
4bb6408c 22#include "wx/choice.h"
de6185e2
WS
23
24#ifndef WX_PRECOMP
25 #include "wx/utils.h"
aaa6d89a 26 #include "wx/arrstr.h"
de6185e2
WS
27#endif
28
338dd992
JJ
29#ifdef __VMS__
30#pragma message disable nosimpint
31#endif
f97c9854
JS
32#include <Xm/Xm.h>
33#include <Xm/PushBG.h>
34#include <Xm/PushB.h>
35#include <Xm/RowColumn.h>
338dd992
JJ
36#ifdef __VMS__
37#pragma message enable nosimpint
38#endif
f97c9854
JS
39
40#include "wx/motif/private.h"
4bb6408c 41
3a73cc52
MB
42#define WIDTH_OVERHEAD 48
43#define WIDTH_OVERHEAD_SUBTRACT 40
44#define HEIGHT_OVERHEAD 15
45
4bb6408c 46IMPLEMENT_DYNAMIC_CLASS(wxChoice, wxControl)
4bb6408c 47
f97c9854 48void wxChoiceCallback (Widget w, XtPointer clientData,
2d120f83 49 XtPointer ptr);
f97c9854
JS
50
51wxChoice::wxChoice()
52{
ec75d791
MB
53 Init();
54}
55
56void wxChoice::Init()
57{
f97c9854
JS
58 m_noStrings = 0;
59 m_buttonWidget = (WXWidget) 0;
60 m_menuWidget = (WXWidget) 0;
f97c9854 61 m_formWidget = (WXWidget) 0;
f97c9854
JS
62}
63
4bb6408c 64bool wxChoice::Create(wxWindow *parent, wxWindowID id,
2d120f83
JS
65 const wxPoint& pos,
66 const wxSize& size,
67 int n, const wxString choices[],
68 long style,
69 const wxValidator& validator,
70 const wxString& name)
4bb6408c 71{
ec75d791 72 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
312ebad4 73 return false;
31528cd3 74
f97c9854 75 Widget parentWidget = (Widget) parent->GetClientWidget();
31528cd3
VZ
76
77 m_formWidget = (WXWidget) XtVaCreateManagedWidget(name.c_str(),
2d120f83
JS
78 xmRowColumnWidgetClass, parentWidget,
79 XmNmarginHeight, 0,
80 XmNmarginWidth, 0,
81 XmNpacking, XmPACK_TIGHT,
82 XmNorientation, XmHORIZONTAL,
3a73cc52
MB
83 XmNresizeWidth, False,
84 XmNresizeHeight, False,
2d120f83 85 NULL);
31528cd3 86
f97c9854 87 XtVaSetValues ((Widget) m_formWidget, XmNspacing, 0, NULL);
31528cd3 88
2d120f83 89 /*
f97c9854
JS
90 * Create the popup menu
91 */
ec75d791 92 m_menuWidget = (WXWidget) XmCreatePulldownMenu ((Widget) m_formWidget,
f1db433a
VZ
93 wxMOTIF_STR("choiceMenu"),
94 NULL, 0);
31528cd3 95
f97c9854
JS
96 if (n > 0)
97 {
98 int i;
99 for (i = 0; i < n; i++)
100 Append (choices[i]);
101 }
31528cd3 102
2d120f83 103 /*
f97c9854
JS
104 * Create button
105 */
106 Arg args[10];
107 Cardinal argcnt = 0;
31528cd3 108
ec75d791
MB
109 XtSetArg (args[argcnt], XmNsubMenuId, (Widget) m_menuWidget); ++argcnt;
110 XtSetArg (args[argcnt], XmNmarginWidth, 0); ++argcnt;
111 XtSetArg (args[argcnt], XmNmarginHeight, 0); ++argcnt;
112 XtSetArg (args[argcnt], XmNpacking, XmPACK_TIGHT); ++argcnt;
113 m_buttonWidget = (WXWidget) XmCreateOptionMenu ((Widget) m_formWidget,
f1db433a 114 wxMOTIF_STR("choiceButton"),
ec75d791 115 args, argcnt);
31528cd3 116
f97c9854 117 m_mainWidget = m_buttonWidget;
31528cd3 118
f97c9854 119 XtManageChild ((Widget) m_buttonWidget);
9838df2c 120
f97c9854
JS
121 // New code from Roland Haenel (roland_haenel@ac.cybercity.de)
122 // Some time ago, I reported a problem with wxChoice-items under
123 // Linux and Motif 2.0 (they caused sporadic GPFs). Now it seems
124 // that I have found the code responsible for this behaviour.
125#if XmVersion >= 1002
126#if XmVersion < 2000
9838df2c
JS
127 // JACS, 24/1/99: this seems to cause a malloc crash later on, e.g.
128 // in controls sample.
dfe1eee3
VZ
129 //
130 // Widget optionLabel = XmOptionLabelGadget ((Widget) m_buttonWidget);
131 // XtUnmanageChild (optionLabel);
f97c9854
JS
132#endif
133#endif
9838df2c 134
a29ee706
MB
135 wxSize bestSize = GetBestSize();
136 if( size.x > 0 ) bestSize.x = size.x;
137 if( size.y > 0 ) bestSize.y = size.y;
138
f97c9854 139 XtVaSetValues((Widget) m_formWidget, XmNresizePolicy, XmRESIZE_NONE, NULL);
31528cd3 140
312ebad4 141 ChangeFont(false);
3a73cc52 142 ChangeBackgroundColour();
9838df2c 143
ec75d791 144 AttachWidget (parent, m_buttonWidget, m_formWidget,
a29ee706 145 pos.x, pos.y, bestSize.x, bestSize.y);
31528cd3 146
312ebad4 147 return true;
f97c9854
JS
148}
149
584ad2a3
MB
150bool wxChoice::Create(wxWindow *parent, wxWindowID id,
151 const wxPoint& pos,
152 const wxSize& size,
153 const wxArrayString& choices,
154 long style,
155 const wxValidator& validator,
156 const wxString& name)
157{
158 wxCArrayString chs(choices);
159 return Create(parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
160 style, validator, name);
161}
162
f97c9854
JS
163wxChoice::~wxChoice()
164{
2d120f83
JS
165 // For some reason destroying the menuWidget
166 // can cause crashes on some machines. It will
167 // be deleted implicitly by deleting the parent form
168 // anyway.
169 // XtDestroyWidget (menuWidget);
31528cd3 170
8aa04e8b
JS
171 if (GetMainWidget())
172 {
173 DetachWidget(GetMainWidget()); // Removes event handlers
b412f9be 174 DetachWidget(m_formWidget);
31528cd3 175
8aa04e8b
JS
176 XtDestroyWidget((Widget) m_formWidget);
177 m_formWidget = (WXWidget) 0;
31528cd3 178
8aa04e8b
JS
179 // Presumably the other widgets have been deleted now, via the form
180 m_mainWidget = (WXWidget) 0;
181 m_buttonWidget = (WXWidget) 0;
182 }
ec75d791
MB
183 if ( HasClientObjectData() )
184 m_clientDataDict.DestroyData();
4bb6408c
JS
185}
186
f94a81c0
MB
187static inline wxChar* MYcopystring(const wxChar* s)
188{
189 wxChar* copy = new wxChar[wxStrlen(s) + 1];
190 return wxStrcpy(copy, s);
191}
192
aa61d352 193int wxChoice::DoInsert(const wxString& item, unsigned int pos)
4bb6408c 194{
f94a81c0 195#ifndef XmNpositionIndex
aa61d352 196 wxCHECK_MSG( pos == GetCount(), -1, wxT("insert not implemented"));
f94a81c0 197#endif
31528cd3 198 Widget w = XtVaCreateManagedWidget (wxStripMenuCodes(item),
8c624a14 199#if wxUSE_GADGETS
2d120f83 200 xmPushButtonGadgetClass, (Widget) m_menuWidget,
f97c9854 201#else
2d120f83 202 xmPushButtonWidgetClass, (Widget) m_menuWidget,
f94a81c0
MB
203#endif
204#ifdef XmNpositionIndex
205 XmNpositionIndex, pos,
f97c9854 206#endif
2d120f83 207 NULL);
31528cd3 208
a8680e3e 209 wxDoChangeBackgroundColour((WXWidget) w, m_backgroundColour);
31528cd3 210
e1aae528
MB
211 if( m_font.Ok() )
212 wxDoChangeFont( w, m_font );
31528cd3 213
f94a81c0 214 m_widgetArray.Insert(w, pos);
31528cd3 215
3a73cc52 216 char mnem = wxFindMnemonic (item);
f6bcfd97
BP
217 if (mnem != 0)
218 XtVaSetValues (w, XmNmnemonic, mnem, NULL);
31528cd3 219
ec75d791
MB
220 XtAddCallback (w, XmNactivateCallback,
221 (XtCallbackProc) wxChoiceCallback,
222 (XtPointer) this);
31528cd3 223
f6bcfd97
BP
224 if (m_noStrings == 0 && m_buttonWidget)
225 {
226 XtVaSetValues ((Widget) m_buttonWidget, XmNmenuHistory, w, NULL);
227 Widget label = XmOptionButtonGadget ((Widget) m_buttonWidget);
c13c9657 228 wxXmString text( item );
f6bcfd97 229 XtVaSetValues (label,
c13c9657 230 XmNlabelString, text(),
f6bcfd97 231 NULL);
f6bcfd97 232 }
f94a81c0
MB
233 // need to ditch wxStringList for wxArrayString
234 m_stringList.Insert(pos, MYcopystring(item));
f6bcfd97 235 m_noStrings ++;
c33c81c3 236
f94a81c0 237 return pos;
4bb6408c
JS
238}
239
f94a81c0 240int wxChoice::DoAppend(const wxString& item)
243dbf1a 241{
aa61d352 242 return DoInsert(item, GetCount());
243dbf1a
VZ
243}
244
aa61d352 245void wxChoice::Delete(unsigned int n)
4bb6408c 246{
ec75d791
MB
247 Widget w = (Widget)m_widgetArray[n];
248 XtRemoveCallback(w, XmNactivateCallback, (XtCallbackProc)wxChoiceCallback,
249 (XtPointer)this);
ac32ba44 250 m_stringList.Erase(m_stringList.Item(n));
ec75d791
MB
251 m_widgetArray.RemoveAt(size_t(n));
252 m_clientDataDict.Delete(n, HasClientObjectData());
31528cd3 253
ec75d791 254 XtDestroyWidget(w);
aa61d352 255 m_noStrings--;
4bb6408c
JS
256}
257
258void wxChoice::Clear()
259{
f97c9854 260 m_stringList.Clear ();
aa61d352 261 unsigned int i;
f97c9854
JS
262 for (i = 0; i < m_noStrings; i++)
263 {
ec75d791
MB
264 XtRemoveCallback((Widget) m_widgetArray[i],
265 XmNactivateCallback, (XtCallbackProc)wxChoiceCallback,
266 (XtPointer)this);
267 XtUnmanageChild ((Widget) m_widgetArray[i]);
268 XtDestroyWidget ((Widget) m_widgetArray[i]);
f97c9854 269 }
ec75d791 270 m_widgetArray.Clear();
f97c9854 271 if (m_buttonWidget)
ec75d791
MB
272 XtVaSetValues ((Widget) m_buttonWidget,
273 XmNmenuHistory, (Widget) NULL,
274 NULL);
f6bcfd97
BP
275
276 if ( HasClientObjectData() )
ec75d791 277 m_clientDataDict.DestroyData();
f6bcfd97 278
4bb6408c
JS
279 m_noStrings = 0;
280}
281
282int wxChoice::GetSelection() const
283{
2d120f83 284 XmString text;
2d120f83
JS
285 Widget label = XmOptionButtonGadget ((Widget) m_buttonWidget);
286 XtVaGetValues (label,
287 XmNlabelString, &text,
288 NULL);
da494b40
MB
289 wxXmString freeMe(text);
290 wxString s = wxXmStringToString( text );
31528cd3 291
11e62fe6 292 if (!s.empty())
2d120f83
JS
293 {
294 int i = 0;
ac32ba44 295 for (wxStringList::compatibility_iterator node = m_stringList.GetFirst ();
ec75d791 296 node; node = node->GetNext ())
f97c9854 297 {
da494b40 298 if (wxStrcmp(node->GetData(), s.c_str()) == 0)
2d120f83 299 {
2d120f83
JS
300 return i;
301 }
302 else
303 i++;
304 } // for()
31528cd3 305
2d120f83
JS
306 return -1;
307 }
2d120f83 308 return -1;
4bb6408c
JS
309}
310
311void wxChoice::SetSelection(int n)
312{
312ebad4 313 m_inSetValue = true;
31528cd3 314
ac32ba44 315 wxStringList::compatibility_iterator node = m_stringList.Item(n);
2d120f83 316 if (node)
f97c9854 317 {
ec75d791 318#if 0
2d120f83 319 Dimension selectionWidth, selectionHeight;
ec75d791 320#endif
fd304d98 321 wxXmString text( node->GetData() );
ec75d791
MB
322// MBN: this seems silly, at best, and causes wxChoices to be clipped:
323// will remove "soon"
324#if 0
325 XtVaGetValues ((Widget) m_widgetArray[n],
326 XmNwidth, &selectionWidth,
327 XmNheight, &selectionHeight,
328 NULL);
329#endif
2d120f83
JS
330 Widget label = XmOptionButtonGadget ((Widget) m_buttonWidget);
331 XtVaSetValues (label,
ec75d791 332 XmNlabelString, text(),
2d120f83 333 NULL);
ec75d791 334#if 0
2d120f83
JS
335 XtVaSetValues ((Widget) m_buttonWidget,
336 XmNwidth, selectionWidth, XmNheight, selectionHeight,
ec75d791
MB
337 XmNmenuHistory, (Widget) m_widgetArray[n], NULL);
338#endif
f97c9854 339 }
312ebad4 340 m_inSetValue = false;
4bb6408c
JS
341}
342
aa61d352 343wxString wxChoice::GetString(unsigned int n) const
4bb6408c 344{
ac32ba44 345 wxStringList::compatibility_iterator node = m_stringList.Item(n);
2d120f83 346 if (node)
ec75d791 347 return node->GetData();
2d120f83
JS
348 else
349 return wxEmptyString;
f97c9854
JS
350}
351
352void wxChoice::SetColumns(int n)
353{
2d120f83 354 if (n<1) n = 1 ;
31528cd3 355
55034339 356 short numColumns = (short)n ;
2d120f83 357 Arg args[3];
31528cd3 358
2d120f83
JS
359 XtSetArg(args[0], XmNnumColumns, numColumns);
360 XtSetArg(args[1], XmNpacking, XmPACK_COLUMN);
361 XtSetValues((Widget) m_menuWidget,args,2) ;
f97c9854
JS
362}
363
364int wxChoice::GetColumns(void) const
365{
2d120f83 366 short numColumns ;
31528cd3 367
2d120f83
JS
368 XtVaGetValues((Widget) m_menuWidget,XmNnumColumns,&numColumns,NULL) ;
369 return numColumns ;
f97c9854
JS
370}
371
372void wxChoice::SetFocus()
373{
2d120f83 374 XmProcessTraversal(XtParent((Widget)m_mainWidget), XmTRAVERSE_CURRENT);
4bb6408c
JS
375}
376
bfc6fde4 377void wxChoice::DoSetSize(int x, int y, int width, int height, int sizeFlags)
4bb6408c 378{
f97c9854
JS
379 XtVaSetValues((Widget) m_formWidget, XmNresizePolicy, XmRESIZE_ANY, NULL);
380 bool managed = XtIsManaged((Widget) m_formWidget);
31528cd3 381
f97c9854
JS
382 if (managed)
383 XtUnmanageChild ((Widget) m_formWidget);
31528cd3 384
3a73cc52
MB
385 int actualWidth = width - WIDTH_OVERHEAD_SUBTRACT,
386 actualHeight = height - HEIGHT_OVERHEAD;
31528cd3 387
f97c9854
JS
388 if (width > -1)
389 {
aa61d352 390 unsigned int i;
f97c9854 391 for (i = 0; i < m_noStrings; i++)
ec75d791
MB
392 XtVaSetValues ((Widget) m_widgetArray[i],
393 XmNwidth, actualWidth,
394 NULL);
f97c9854 395 XtVaSetValues ((Widget) m_buttonWidget, XmNwidth, actualWidth,
2d120f83 396 NULL);
f97c9854
JS
397 }
398 if (height > -1)
399 {
3a73cc52 400#if 0
aa61d352 401 unsigned int i;
f97c9854 402 for (i = 0; i < m_noStrings; i++)
ec75d791
MB
403 XtVaSetValues ((Widget) m_widgetArray[i],
404 XmNheight, actualHeight,
405 NULL);
3a73cc52 406#endif
f97c9854 407 XtVaSetValues ((Widget) m_buttonWidget, XmNheight, actualHeight,
2d120f83 408 NULL);
f97c9854 409 }
31528cd3 410
f97c9854
JS
411 if (managed)
412 XtManageChild ((Widget) m_formWidget);
413 XtVaSetValues((Widget) m_formWidget, XmNresizePolicy, XmRESIZE_NONE, NULL);
31528cd3 414
bfc6fde4 415 wxControl::DoSetSize (x, y, width, height, sizeFlags);
4bb6408c
JS
416}
417
4bb6408c
JS
418void wxChoice::Command(wxCommandEvent & event)
419{
420 SetSelection (event.GetInt());
421 ProcessCommand (event);
422}
423
f9e02ac7 424void wxChoiceCallback (Widget w, XtPointer clientData, XtPointer WXUNUSED(ptr))
f97c9854
JS
425{
426 wxChoice *item = (wxChoice *) clientData;
427 if (item)
428 {
a4294b78 429 if (item->InSetValue())
f97c9854 430 return;
31528cd3 431
ec75d791
MB
432 int n = item->GetWidgets().Index(w);
433 if (n != wxNOT_FOUND)
f97c9854 434 {
ec75d791 435 wxCommandEvent event(wxEVT_COMMAND_CHOICE_SELECTED, item->GetId());
55acd85e 436 event.SetEventObject(item);
687706f5
KH
437 event.SetInt(n);
438 event.SetString( item->GetStrings().Item(n)->GetData() );
ec75d791
MB
439 if ( item->HasClientObjectData() )
440 event.SetClientObject( item->GetClientObject(n) );
441 else if ( item->HasClientUntypedData() )
442 event.SetClientData( item->GetClientData(n) );
f97c9854
JS
443 item->ProcessCommand (event);
444 }
445 }
446}
447
4b5f3fe6 448void wxChoice::ChangeFont(bool keepOriginalSize)
0d57be45 449{
321db4b6
JS
450 // Note that this causes the widget to be resized back
451 // to its original size! We therefore have to set the size
452 // back again. TODO: a better way in Motif?
da175b2c 453 if (m_font.Ok())
321db4b6 454 {
73608949 455 Display* dpy = XtDisplay((Widget) m_mainWidget);
321db4b6
JS
456 int width, height, width1, height1;
457 GetSize(& width, & height);
31528cd3 458
da494b40
MB
459 WXString fontTag = wxFont::GetFontTag();
460
73608949
MB
461 XtVaSetValues ((Widget) m_formWidget,
462 fontTag, m_font.GetFontTypeC(dpy),
463 NULL);
464 XtVaSetValues ((Widget) m_buttonWidget,
465 fontTag, m_font.GetFontTypeC(dpy),
466 NULL);
31528cd3 467
aa61d352 468 for( unsigned int i = 0; i < m_noStrings; ++i )
ec75d791 469 XtVaSetValues( (Widget)m_widgetArray[i],
73608949 470 fontTag, m_font.GetFontTypeC(dpy),
ec75d791 471 NULL );
312ebad4 472
321db4b6 473 GetSize(& width1, & height1);
4b5f3fe6 474 if (keepOriginalSize && (width != width1 || height != height1))
321db4b6 475 {
312ebad4 476 SetSize(wxDefaultCoord, wxDefaultCoord, width, height);
321db4b6
JS
477 }
478 }
0d57be45
JS
479}
480
481void wxChoice::ChangeBackgroundColour()
482{
a8680e3e
MB
483 wxDoChangeBackgroundColour(m_formWidget, m_backgroundColour);
484 wxDoChangeBackgroundColour(m_buttonWidget, m_backgroundColour);
485 wxDoChangeBackgroundColour(m_menuWidget, m_backgroundColour);
aa61d352 486 unsigned int i;
321db4b6 487 for (i = 0; i < m_noStrings; i++)
a8680e3e 488 wxDoChangeBackgroundColour(m_widgetArray[i], m_backgroundColour);
0d57be45
JS
489}
490
491void wxChoice::ChangeForegroundColour()
492{
a8680e3e
MB
493 wxDoChangeForegroundColour(m_formWidget, m_foregroundColour);
494 wxDoChangeForegroundColour(m_buttonWidget, m_foregroundColour);
495 wxDoChangeForegroundColour(m_menuWidget, m_foregroundColour);
aa61d352 496 unsigned int i;
321db4b6 497 for (i = 0; i < m_noStrings; i++)
a8680e3e 498 wxDoChangeForegroundColour(m_widgetArray[i], m_foregroundColour);
0d57be45 499}
6adaedf0 500
aa61d352 501unsigned int wxChoice::GetCount() const
6adaedf0 502{
ec75d791 503 return m_noStrings;
6adaedf0
JS
504}
505
aa61d352 506void wxChoice::DoSetItemClientData(unsigned int n, void* clientData)
6adaedf0 507{
312ebad4 508 m_clientDataDict.Set(n, (wxClientData*)clientData, false);
6adaedf0
JS
509}
510
aa61d352 511void* wxChoice::DoGetItemClientData(unsigned int n) const
6adaedf0 512{
ec75d791 513 return (void*)m_clientDataDict.Get(n);
6adaedf0
JS
514}
515
aa61d352 516void wxChoice::DoSetItemClientObject(unsigned int n, wxClientData* clientData)
6adaedf0 517{
ec75d791 518 // don't delete, wxItemContainer does that for us
312ebad4 519 m_clientDataDict.Set(n, clientData, false);
6adaedf0
JS
520}
521
aa61d352 522wxClientData* wxChoice::DoGetItemClientObject(unsigned int n) const
6adaedf0 523{
ec75d791 524 return m_clientDataDict.Get(n);
6adaedf0
JS
525}
526
aa61d352 527void wxChoice::SetString(unsigned int WXUNUSED(n), const wxString& WXUNUSED(s))
6adaedf0 528{
ec75d791 529 wxFAIL_MSG( wxT("wxChoice::SetString not implemented") );
6adaedf0
JS
530}
531
ec75d791 532wxSize wxChoice::GetItemsSize() const
6adaedf0 533{
ec75d791 534 int x, y, mx = 0, my = 0;
f6bcfd97 535
ec75d791
MB
536 // get my
537 GetTextExtent( "|", &x, &my );
f6bcfd97 538
ac32ba44 539 wxStringList::compatibility_iterator curr = m_stringList.GetFirst();
ec75d791
MB
540 while( curr )
541 {
542 GetTextExtent( curr->GetData(), &x, &y );
543 mx = wxMax( mx, x );
544 my = wxMax( my, y );
545 curr = curr->GetNext();
546 }
6adaedf0 547
ec75d791 548 return wxSize( mx, my );
6adaedf0
JS
549}
550
ec75d791 551wxSize wxChoice::DoGetBestSize() const
6adaedf0 552{
ec75d791
MB
553 wxSize items = GetItemsSize();
554 // FIXME arbitrary constants
3a73cc52
MB
555 return wxSize( ( items.x ? items.x + WIDTH_OVERHEAD : 120 ),
556 items.y + HEIGHT_OVERHEAD );
6adaedf0 557}
312ebad4
WS
558
559#endif // wxUSE_CHOICE