]> git.saurik.com Git - wxWidgets.git/blob - src/mac/carbon/combobxc.cpp
Finally fixed control scrolling
[wxWidgets.git] / src / mac / carbon / combobxc.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/mac/carbon/combobxc.cpp
3 // Purpose: wxComboBox class using HIView ComboBox
4 // Author: Stefan Csomor
5 // Modified by:
6 // Created: 1998-01-01
7 // RCS-ID: $Id$
8 // Copyright: (c) Stefan Csomor
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #include "wx/combobox.h"
15
16 #ifndef WX_PRECOMP
17 #include "wx/button.h"
18 #include "wx/menu.h"
19 #endif
20
21 #include "wx/mac/uma.h"
22 #if TARGET_API_MAC_OSX
23 #ifndef __HIVIEW__
24 #include <HIToolbox/HIView.h>
25 #endif
26 #endif
27
28 IMPLEMENT_DYNAMIC_CLASS(wxComboBox, wxControl)
29
30 #if TARGET_API_MAC_OSX
31 #define USE_HICOMBOBOX 1 //use hi combobox define
32 #else
33 #define USE_HICOMBOBOX 0
34 #endif
35
36 static int nextPopUpMenuId = 1000;
37 MenuHandle NewUniqueMenu()
38 {
39 MenuHandle handle = NewMenu( nextPopUpMenuId , "\pMenu" );
40 nextPopUpMenuId++;
41 return handle;
42 }
43
44 #if USE_HICOMBOBOX
45 static const EventTypeSpec eventList[] =
46 {
47 { kEventClassTextField , kEventTextAccepted } ,
48 };
49
50 static pascal OSStatus wxMacComboBoxEventHandler( EventHandlerCallRef handler , EventRef event , void *data )
51 {
52 OSStatus result = eventNotHandledErr;
53 wxComboBox* cb = (wxComboBox*) data;
54
55 wxMacCarbonEvent cEvent( event );
56
57 switch( cEvent.GetClass() )
58 {
59 case kEventClassTextField :
60 switch( cEvent.GetKind() )
61 {
62 case kEventTextAccepted :
63 {
64 wxCommandEvent event( wxEVT_COMMAND_COMBOBOX_SELECTED, cb->GetId() );
65 event.SetInt( cb->GetSelection() );
66 event.SetString( cb->GetStringSelection() );
67 event.SetEventObject( cb );
68 cb->GetEventHandler()->ProcessEvent( event );
69 }
70 break;
71 default :
72 break;
73 }
74 break;
75 default :
76 break;
77 }
78
79
80 return result;
81 }
82
83 DEFINE_ONE_SHOT_HANDLER_GETTER( wxMacComboBoxEventHandler )
84
85 #endif
86
87 // ----------------------------------------------------------------------------
88 // constants
89 // ----------------------------------------------------------------------------
90
91 // the margin between the text control and the choice
92 static const wxCoord MARGIN = 2;
93 #if TARGET_API_MAC_OSX
94 static const int POPUPWIDTH = 24;
95 #else
96 static const int POPUPWIDTH = 18;
97 #endif
98 static const int POPUPHEIGHT = 23;
99
100 // ----------------------------------------------------------------------------
101 // wxComboBoxText: text control forwards events to combobox
102 // ----------------------------------------------------------------------------
103
104 class wxComboBoxText : public wxTextCtrl
105 {
106 public:
107 wxComboBoxText( wxComboBox * cb )
108 : wxTextCtrl( cb , 1 )
109 {
110 m_cb = cb;
111 }
112
113 protected:
114 void OnChar( wxKeyEvent& event )
115 {
116 if ( event.GetKeyCode() == WXK_RETURN )
117 {
118 wxString value = GetValue();
119
120 if ( m_cb->GetCount() == 0 )
121 {
122 // make Enter generate "selected" event if there is only one item
123 // in the combobox - without it, it's impossible to select it at
124 // all!
125 wxCommandEvent event( wxEVT_COMMAND_COMBOBOX_SELECTED, m_cb->GetId() );
126 event.SetInt( 0 );
127 event.SetString( value );
128 event.SetEventObject( m_cb );
129 m_cb->GetEventHandler()->ProcessEvent( event );
130 }
131 else
132 {
133 // add the item to the list if it's not there yet
134 if ( m_cb->FindString(value) == wxNOT_FOUND )
135 {
136 m_cb->Append(value);
137 m_cb->SetStringSelection(value);
138
139 // and generate the selected event for it
140 wxCommandEvent event( wxEVT_COMMAND_COMBOBOX_SELECTED, m_cb->GetId() );
141 event.SetInt( m_cb->GetCount() - 1 );
142 event.SetString( value );
143 event.SetEventObject( m_cb );
144 m_cb->GetEventHandler()->ProcessEvent( event );
145 }
146
147 // This will invoke the dialog default action, such
148 // as the clicking the default button.
149
150 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
151 if ( tlw && tlw->GetDefaultItem() )
152 {
153 wxButton *def = wxDynamicCast(tlw->GetDefaultItem(), wxButton);
154 if ( def && def->IsEnabled() )
155 {
156 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, def->GetId() );
157 event.SetEventObject(def);
158 def->Command(event);
159 return;
160 }
161 }
162
163 return;
164 }
165 }
166
167 event.Skip();
168 }
169 private:
170 wxComboBox *m_cb;
171
172 DECLARE_EVENT_TABLE()
173 };
174
175 BEGIN_EVENT_TABLE(wxComboBoxText, wxTextCtrl)
176 EVT_CHAR( wxComboBoxText::OnChar)
177 END_EVENT_TABLE()
178
179 class wxComboBoxChoice : public wxChoice
180 {
181 public:
182 wxComboBoxChoice(wxComboBox *cb, int style)
183 : wxChoice( cb , 1 )
184 {
185 m_cb = cb;
186 }
187
188 protected:
189 void OnChoice( wxCommandEvent& e )
190 {
191 wxString s = e.GetString();
192
193 m_cb->DelegateChoice( s );
194 wxCommandEvent event2(wxEVT_COMMAND_COMBOBOX_SELECTED, m_cb->GetId() );
195 event2.SetInt(m_cb->GetSelection());
196 event2.SetEventObject(m_cb);
197 event2.SetString(m_cb->GetStringSelection());
198 m_cb->ProcessCommand(event2);
199 }
200 virtual wxSize DoGetBestSize() const
201 {
202 wxSize sz = wxChoice::DoGetBestSize();
203 sz.x = POPUPWIDTH;
204 return sz;
205 }
206
207 private:
208 wxComboBox *m_cb;
209
210 DECLARE_EVENT_TABLE()
211 };
212
213 BEGIN_EVENT_TABLE(wxComboBoxChoice, wxChoice)
214 EVT_CHOICE(wxID_ANY, wxComboBoxChoice::OnChoice)
215 END_EVENT_TABLE()
216
217 wxComboBox::~wxComboBox()
218 {
219 // delete client objects
220 FreeData();
221
222 // delete the controls now, don't leave them alive even though they would
223 // still be eventually deleted by our parent - but it will be too late, the
224 // user code expects them to be gone now
225 if (m_text != NULL) {
226 delete m_text;
227 m_text = NULL;
228 }
229 if (m_choice != NULL) {
230 delete m_choice;
231 m_choice = NULL;
232 }
233 }
234
235
236 // ----------------------------------------------------------------------------
237 // geometry
238 // ----------------------------------------------------------------------------
239
240 wxSize wxComboBox::DoGetBestSize() const
241 {
242 #if USE_HICOMBOBOX
243 return wxControl::DoGetBestSize();
244 #else
245 wxSize size = m_choice->GetBestSize();
246
247 if ( m_text != NULL )
248 {
249 wxSize sizeText = m_text->GetBestSize();
250
251 size.x = POPUPWIDTH + sizeText.x + MARGIN;
252 }
253
254 return size;
255 #endif
256 }
257
258 void wxComboBox::DoMoveWindow(int x, int y, int width, int height) {
259 #if USE_HICOMBOBOX
260 wxControl::DoMoveWindow(x, y, width, height);
261 #else
262 height = POPUPHEIGHT;
263
264 wxControl::DoMoveWindow(x, y, width, height);
265
266 if ( m_text == NULL )
267 {
268 // we might not be fully constructed yet, therefore watch out...
269 if ( m_choice )
270 m_choice->SetSize(0, 0 , width, wxDefaultCoord);
271 }
272 else
273 {
274 wxCoord wText = width - POPUPWIDTH - MARGIN;
275 m_text->SetSize(0, 0, wText, height);
276 m_choice->SetSize(0 + wText + MARGIN, 0, POPUPWIDTH, wxDefaultCoord);
277 }
278 #endif
279 }
280
281
282
283 // ----------------------------------------------------------------------------
284 // operations forwarded to the subcontrols
285 // ----------------------------------------------------------------------------
286
287 bool wxComboBox::Enable(bool enable)
288 {
289 if ( !wxControl::Enable(enable) )
290 return false;
291
292 return true;
293 }
294
295 bool wxComboBox::Show(bool show)
296 {
297 if ( !wxControl::Show(show) )
298 return false;
299
300 return true;
301 }
302
303 void wxComboBox::SetFocus()
304 {
305 #if USE_HICOMBOBOX
306 wxControl::SetFocus();
307 #else
308 if ( m_text != NULL) {
309 m_text->SetFocus();
310 }
311 #endif
312 }
313
314
315 void wxComboBox::DelegateTextChanged( const wxString& value )
316 {
317 SetStringSelection( value );
318 }
319
320
321 void wxComboBox::DelegateChoice( const wxString& value )
322 {
323 SetStringSelection( value );
324 }
325
326
327 bool wxComboBox::Create(wxWindow *parent, wxWindowID id,
328 const wxString& value,
329 const wxPoint& pos,
330 const wxSize& size,
331 const wxArrayString& choices,
332 long style,
333 const wxValidator& validator,
334 const wxString& name)
335 {
336 wxCArrayString chs( choices );
337
338 return Create( parent, id, value, pos, size, chs.GetCount(),
339 chs.GetStrings(), style, validator, name );
340 }
341
342
343 bool wxComboBox::Create(wxWindow *parent, wxWindowID id,
344 const wxString& value,
345 const wxPoint& pos,
346 const wxSize& size,
347 int n, const wxString choices[],
348 long style,
349 const wxValidator& validator,
350 const wxString& name)
351 {
352 m_text = NULL;
353 m_choice = NULL;
354 #if USE_HICOMBOBOX
355 m_macIsUserPane = false;
356 #endif
357 if ( !wxControl::Create(parent, id, wxDefaultPosition, wxDefaultSize, style ,
358 wxDefaultValidator, name) )
359 {
360 return false;
361 }
362 #if USE_HICOMBOBOX
363 Rect bounds = wxMacGetBoundsForControl( this , pos , size );
364 HIRect hiRect;
365
366 hiRect.origin.x = 20; //bounds.left;
367 hiRect.origin.y = 25; //bounds.top;
368 hiRect.size.width = 120;// bounds.right - bounds.left;
369 hiRect.size.height = 24;
370
371 //For some reason, this code causes the combo box not to be displayed at all.
372 //hiRect.origin.x = bounds.left;
373 //hiRect.origin.y = bounds.top;
374 //hiRect.size.width = bounds.right - bounds.left;
375 //hiRect.size.height = bounds.bottom - bounds.top;
376 //printf("left = %d, right = %d, top = %d, bottom = %d\n", bounds.left, bounds.right, bounds.top, bounds.bottom);
377 //printf("x = %d, y = %d, width = %d, height = %d\n", hibounds.origin.x, hibounds.origin.y, hibounds.size.width, hibounds.size.height);
378 m_peer = new wxMacControl(this);
379 verify_noerr( HIComboBoxCreate( &hiRect, CFSTR(""), NULL, NULL, kHIComboBoxStandardAttributes, m_peer->GetControlRefAddr() ) );
380
381
382 m_peer->SetMinimum( 0 );
383 m_peer->SetMaximum( 100);
384 if ( n > 0 )
385 m_peer->SetValue( 1 );
386
387 MacPostControlCreate(pos,size);
388
389 for ( int i = 0; i < n; i++ )
390 {
391 DoAppend( choices[ i ] );
392 }
393
394 HIViewSetVisible( m_peer->GetControlRef(), true );
395 SetSelection(0);
396 EventHandlerRef comboEventHandler;
397 InstallControlEventHandler( m_peer->GetControlRef(), GetwxMacComboBoxEventHandlerUPP(),
398 GetEventTypeCount(eventList), eventList, this,
399 (EventHandlerRef *)&comboEventHandler);
400 #else
401 m_choice = new wxComboBoxChoice(this, style );
402 m_choice->SetMinSize( wxSize( POPUPWIDTH , POPUPHEIGHT ) );
403
404 wxSize csize = size;
405 if ( style & wxCB_READONLY )
406 {
407 m_text = NULL;
408 }
409 else
410 {
411 m_text = new wxComboBoxText(this);
412 if ( size.y == wxDefaultCoord ) {
413 csize.y = m_text->GetSize().y;
414 }
415 }
416
417 DoSetSize(pos.x, pos.y, csize.x, csize.y);
418
419 for ( int i = 0; i < n; i++ )
420 {
421 m_choice->DoAppend( choices[ i ] );
422 }
423 SetInitialSize(csize); // Needed because it is a wxControlWithItems
424 #endif
425
426 return true;
427 }
428
429 wxString wxComboBox::GetValue() const
430 {
431 #if USE_HICOMBOBOX
432 CFStringRef myString;
433 HIComboBoxCopyTextItemAtIndex( m_peer->GetControlRef(), (CFIndex)GetSelection(), &myString );
434 return wxMacCFStringHolder( myString, m_font.GetEncoding() ).AsString();
435 #else
436 wxString result;
437
438 if ( m_text == NULL )
439 {
440 result = m_choice->GetString( m_choice->GetSelection() );
441 }
442 else
443 {
444 result = m_text->GetValue();
445 }
446
447 return result;
448 #endif
449 }
450
451 void wxComboBox::SetValue(const wxString& value)
452 {
453 #if USE_HICOMBOBOX
454
455 #else
456 int s = FindString (value);
457 if (s == wxNOT_FOUND && !HasFlag(wxCB_READONLY) )
458 {
459 m_choice->Append(value);
460 }
461 SetStringSelection( value );
462 #endif
463 }
464
465 // Clipboard operations
466 void wxComboBox::Copy()
467 {
468 if ( m_text != NULL )
469 {
470 m_text->Copy();
471 }
472 }
473
474 void wxComboBox::Cut()
475 {
476 if ( m_text != NULL )
477 {
478 m_text->Cut();
479 }
480 }
481
482 void wxComboBox::Paste()
483 {
484 if ( m_text != NULL )
485 {
486 m_text->Paste();
487 }
488 }
489
490 void wxComboBox::SetEditable(bool editable)
491 {
492 if ( ( m_text == NULL ) && editable )
493 {
494 m_text = new wxComboBoxText( this );
495 }
496 else if ( ( m_text != NULL ) && !editable )
497 {
498 delete m_text;
499 m_text = NULL;
500 }
501
502 int currentX, currentY;
503 GetPosition( &currentX, &currentY );
504
505 int currentW, currentH;
506 GetSize( &currentW, &currentH );
507
508 DoMoveWindow( currentX, currentY, currentW, currentH );
509 }
510
511 void wxComboBox::SetInsertionPoint(long pos)
512 {
513 // TODO
514 }
515
516 void wxComboBox::SetInsertionPointEnd()
517 {
518 // TODO
519 }
520
521 long wxComboBox::GetInsertionPoint() const
522 {
523 // TODO
524 return 0;
525 }
526
527 wxTextPos wxComboBox::GetLastPosition() const
528 {
529 // TODO
530 return 0;
531 }
532
533 void wxComboBox::Replace(long from, long to, const wxString& value)
534 {
535 // TODO
536 }
537
538 void wxComboBox::Remove(long from, long to)
539 {
540 // TODO
541 }
542
543 void wxComboBox::SetSelection(long from, long to)
544 {
545 // TODO
546 }
547
548 int wxComboBox::DoAppend(const wxString& item)
549 {
550 #if USE_HICOMBOBOX
551 CFIndex outIndex;
552 HIComboBoxAppendTextItem( m_peer->GetControlRef(), wxMacCFStringHolder( item, m_font.GetEncoding() ), &outIndex );
553 //SetControl32BitMaximum( m_peer->GetControlRef(), GetCount() );
554 return (int) outIndex;
555 #else
556 return m_choice->DoAppend( item );
557 #endif
558 }
559
560 int wxComboBox::DoInsert(const wxString& item, unsigned int pos)
561 {
562 #if USE_HICOMBOBOX
563 HIComboBoxInsertTextItemAtIndex( m_peer->GetControlRef(), (CFIndex)pos, wxMacCFStringHolder(item, m_font.GetEncoding()) );
564
565 //SetControl32BitMaximum( m_peer->GetControlRef(), GetCount() );
566
567 return pos;
568 #else
569 return m_choice->DoInsert( item , pos );
570 #endif
571 }
572
573 void wxComboBox::DoSetItemClientData(unsigned int n, void* clientData)
574 {
575 #if USE_HICOMBOBOX
576 return; //TODO
577 #else
578 return m_choice->DoSetItemClientData( n , clientData );
579 #endif
580 }
581
582 void* wxComboBox::DoGetItemClientData(unsigned int n) const
583 {
584 #if USE_HICOMBOBOX
585 return NULL; //TODO
586 #else
587 return m_choice->DoGetItemClientData( n );
588 #endif
589 }
590
591 void wxComboBox::DoSetItemClientObject(unsigned int n, wxClientData* clientData)
592 {
593 #if USE_HICOMBOBOX
594 return; //TODO
595 #else
596 return m_choice->DoSetItemClientObject( n , clientData );
597 #endif
598 }
599
600 wxClientData* wxComboBox::DoGetItemClientObject(unsigned int n) const
601 {
602 #if USE_HICOMBOBOX
603 return NULL;
604 #else
605 return m_choice->DoGetItemClientObject( n );
606 #endif
607 }
608
609 void wxComboBox::FreeData()
610 {
611 if (HasClientObjectData())
612 {
613 unsigned int count = GetCount();
614 for ( unsigned int n = 0; n < count; n++ )
615 {
616 SetClientObject( n, NULL );
617 }
618 }
619 }
620
621 unsigned int wxComboBox::GetCount() const {
622 #if USE_HICOMBOBOX
623 return (unsigned int) HIComboBoxGetItemCount( m_peer->GetControlRef() );
624 #else
625 return m_choice->GetCount();
626 #endif
627 }
628
629 void wxComboBox::Delete(unsigned int n)
630 {
631 #if USE_HICOMBOBOX
632 HIComboBoxRemoveItemAtIndex( m_peer->GetControlRef(), (CFIndex)n );
633 #else
634 // force client object deletion
635 if( HasClientObjectData() )
636 SetClientObject( n, NULL );
637 m_choice->Delete( n );
638 #endif
639 }
640
641 void wxComboBox::Clear()
642 {
643 FreeData();
644 #if USE_HICOMBOBOX
645 for ( CFIndex i = GetCount() - 1; i >= 0; ++ i )
646 verify_noerr( HIComboBoxRemoveItemAtIndex( m_peer->GetControlRef(), i ) );
647 m_peer->SetData<CFStringRef>(kHIComboBoxEditTextPart,kControlEditTextCFStringTag,CFSTR(""));
648 #else
649 m_choice->Clear();
650 #endif
651 }
652
653 int wxComboBox::GetSelection() const
654 {
655 #if USE_HICOMBOBOX
656 return FindString( GetStringSelection() );
657 #else
658 return m_choice->GetSelection();
659 #endif
660 }
661
662 void wxComboBox::SetSelection(int n)
663 {
664 #if USE_HICOMBOBOX
665 SetControl32BitValue( m_peer->GetControlRef() , n + 1 );
666 #else
667 m_choice->SetSelection( n );
668
669 if ( m_text != NULL )
670 {
671 m_text->SetValue(GetString(n));
672 }
673 #endif
674 }
675
676 int wxComboBox::FindString(const wxString& s, bool bCase) const
677 {
678 #if USE_HICOMBOBOX
679 for( unsigned int i = 0 ; i < GetCount() ; i++ )
680 {
681 if (GetString(i).IsSameAs(s, bCase) )
682 return i ;
683 }
684 return wxNOT_FOUND;
685 #else
686 return m_choice->FindString( s, bCase );
687 #endif
688 }
689
690 wxString wxComboBox::GetString(unsigned int n) const
691 {
692 #if USE_HICOMBOBOX
693 CFStringRef itemText;
694 HIComboBoxCopyTextItemAtIndex( m_peer->GetControlRef(), (CFIndex)n, &itemText );
695 return wxMacCFStringHolder(itemText).AsString();
696 #else
697 return m_choice->GetString( n );
698 #endif
699 }
700
701 wxString wxComboBox::GetStringSelection() const
702 {
703 #if USE_HICOMBOBOX
704 return wxMacCFStringHolder(m_peer->GetData<CFStringRef>(kHIComboBoxEditTextPart,kControlEditTextCFStringTag)).AsString();
705 #else
706 int sel = GetSelection ();
707 if (sel != wxNOT_FOUND)
708 return wxString(this->GetString((unsigned int)sel));
709 else
710 return wxEmptyString;
711 #endif
712 }
713
714 void wxComboBox::SetString(unsigned int n, const wxString& s)
715 {
716 #if USE_HICOMBOBOX
717 verify_noerr ( HIComboBoxInsertTextItemAtIndex( m_peer->GetControlRef(), (CFIndex) n,
718 wxMacCFStringHolder(s, m_font.GetEncoding()) ) );
719 verify_noerr ( HIComboBoxRemoveItemAtIndex( m_peer->GetControlRef(), (CFIndex) n + 1 ) );
720 #else
721 m_choice->SetString( n , s );
722 #endif
723 }
724
725 bool wxComboBox::IsEditable() const
726 {
727 #if USE_HICOMBOBOX
728 // TODO
729 return !HasFlag(wxCB_READONLY);
730 #else
731 return m_text != NULL && !HasFlag(wxCB_READONLY);
732 #endif
733 }
734
735 void wxComboBox::Undo()
736 {
737 #if USE_HICOMBOBOX
738 // TODO
739 #else
740 if (m_text != NULL)
741 m_text->Undo();
742 #endif
743 }
744
745 void wxComboBox::Redo()
746 {
747 #if USE_HICOMBOBOX
748 // TODO
749 #else
750 if (m_text != NULL)
751 m_text->Redo();
752 #endif
753 }
754
755 void wxComboBox::SelectAll()
756 {
757 #if USE_HICOMBOBOX
758 // TODO
759 #else
760 if (m_text != NULL)
761 m_text->SelectAll();
762 #endif
763 }
764
765 bool wxComboBox::CanCopy() const
766 {
767 #if USE_HICOMBOBOX
768 // TODO
769 return false;
770 #else
771 if (m_text != NULL)
772 return m_text->CanCopy();
773 else
774 return false;
775 #endif
776 }
777
778 bool wxComboBox::CanCut() const
779 {
780 #if USE_HICOMBOBOX
781 // TODO
782 return false;
783 #else
784 if (m_text != NULL)
785 return m_text->CanCut();
786 else
787 return false;
788 #endif
789 }
790
791 bool wxComboBox::CanPaste() const
792 {
793 #if USE_HICOMBOBOX
794 // TODO
795 return false;
796 #else
797 if (m_text != NULL)
798 return m_text->CanPaste();
799 else
800 return false;
801 #endif
802 }
803
804 bool wxComboBox::CanUndo() const
805 {
806 #if USE_HICOMBOBOX
807 // TODO
808 return false;
809 #else
810 if (m_text != NULL)
811 return m_text->CanUndo();
812 else
813 return false;
814 #endif
815 }
816
817 bool wxComboBox::CanRedo() const
818 {
819 #if USE_HICOMBOBOX
820 // TODO
821 return false;
822 #else
823 if (m_text != NULL)
824 return m_text->CanRedo();
825 else
826 return false;
827 #endif
828 }
829
830 wxInt32 wxComboBox::MacControlHit(WXEVENTHANDLERREF WXUNUSED(handler) , WXEVENTREF WXUNUSED(event) )
831 {
832 wxCommandEvent event(wxEVT_COMMAND_COMBOBOX_SELECTED, m_windowId );
833 event.SetInt(GetSelection());
834 event.SetEventObject(this);
835 event.SetString(GetStringSelection());
836 ProcessCommand(event);
837 return noErr;
838 }