wxMessageBox off the main thread lost result code.
[wxWidgets.git] / src / osx / cocoa / preferences.mm
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/osx/cocoa/preferences.cpp
3 // Purpose:     Native OS X implementation of wxPreferencesEditor.
4 // Author:      Vaclav Slavik
5 // Created:     2013-02-19
6 // Copyright:   (c) 2013 Vaclav Slavik <vslavik@fastmail.fm>
7 // Licence:     wxWindows licence
8 ///////////////////////////////////////////////////////////////////////////////
9
10 // ============================================================================
11 // declarations
12 // ============================================================================
13
14 // ----------------------------------------------------------------------------
15 // headers
16 // ----------------------------------------------------------------------------
17
18 // for compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20
21 #ifdef __BORLANDC__
22     #pragma hdrstop
23 #endif
24
25 #if wxUSE_PREFERENCES_EDITOR
26
27 #include "wx/private/preferences.h"
28
29 #ifdef wxHAS_PREF_EDITOR_NATIVE
30
31 #include "wx/frame.h"
32 #include "wx/sharedptr.h"
33 #include "wx/toolbar.h"
34 #include "wx/vector.h"
35 #include "wx/weakref.h"
36 #include "wx/windowid.h"
37 #include "wx/osx/private.h"
38
39 #import <AppKit/NSWindow.h>
40
41
42 wxBitmap wxStockPreferencesPage::GetLargeIcon() const
43 {
44     switch ( m_kind )
45     {
46         case Kind_General:
47             return wxBitmap([NSImage imageNamed:NSImageNamePreferencesGeneral]);
48         case Kind_Advanced:
49             return wxBitmap([NSImage imageNamed:NSImageNameAdvanced]);
50     }
51     return wxBitmap(); // silence compiler warning
52 }
53
54
55 class wxCocoaPrefsWindow : public wxFrame
56 {
57 public:
58     wxCocoaPrefsWindow(const wxString& title)
59         : wxFrame(NULL, wxID_ANY, title,
60                   wxDefaultPosition, wxDefaultSize,
61                   wxDEFAULT_FRAME_STYLE & ~(wxRESIZE_BORDER | wxMAXIMIZE_BOX | wxMINIMIZE_BOX)),
62           m_toolbarRealized(false),
63           m_visiblePage(NULL)
64     {
65         m_toolbar = new wxToolBar(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
66                                   wxTB_FLAT | wxTB_TEXT);
67         m_toolbar->SetToolBitmapSize(wxSize(32,32));
68         m_toolbar->OSXSetSelectableTools(true);
69         SetToolBar(m_toolbar);
70
71         m_toolbar->Bind(wxEVT_TOOL,
72                         &wxCocoaPrefsWindow::OnPageChanged, this);
73         Bind(wxEVT_CLOSE_WINDOW, &wxCocoaPrefsWindow::OnClose, this);
74     }
75
76     void AddPage(wxPreferencesPage *page)
77     {
78         wxASSERT_MSG( !m_toolbarRealized,
79                       "can't add more preferences pages after showing the window" );
80
81         const wxString title = page->GetName();
82         wxBitmap bmp(page->GetLargeIcon());
83         wxASSERT_MSG( bmp.IsOk(), "OS X requires valid bitmap for preference page" );
84
85         int toolId = wxIdManager::ReserveId();
86         wxToolBarToolBase *tool = m_toolbar->AddTool(toolId, title, bmp);
87
88         wxSharedPtr<PageInfo> info(new PageInfo(page));
89         m_pages.push_back(info);
90
91         tool->SetClientData(info.get());
92     }
93
94     virtual bool Show(bool show)
95     {
96         if ( show && !m_toolbarRealized )
97         {
98             m_toolbar->Realize();
99             m_toolbarRealized = true;
100
101             const wxToolBarToolBase *first = m_toolbar->GetToolByPos(0);
102             wxCHECK_MSG( first, false, "no preferences panels" );
103             OnSelectPageForTool(first);
104             m_toolbar->OSXSelectTool(first->GetId());
105         }
106
107         return wxFrame::Show(show);
108     }
109
110     virtual bool ShouldPreventAppExit() const { return false; }
111
112 protected:
113     // Native preferences windows resize when the selected panel changes and
114     // the resizing is animated, so we need to override DoMoveWindow.
115     virtual void DoMoveWindow(int x, int y, int width, int height)
116     {
117         NSRect r = wxToNSRect(NULL, wxRect(x, y, width, height));
118         NSWindow *win = (NSWindow*)GetWXWindow();
119         [win setFrame:r display:YES animate:YES];
120     }
121
122
123 private:
124     void OnSelectPageForTool(const wxToolBarToolBase *tool)
125     {
126         PageInfo *info = static_cast<PageInfo*>(tool->GetClientData());
127         wxCHECK_RET( info, "toolbar item lacks client data" );
128
129         if ( !info->win )
130         {
131             info->win = info->page->CreateWindow(this);
132             info->win->Hide();
133             info->win->Fit();
134             // fill the page with data using wxEVT_INIT_DIALOG/TransferDataToWindow:
135             info->win->InitDialog();
136         }
137
138         // When the page changes in a native preferences dialog, the sequence
139         // of events is thus:
140
141         // 1. the old page is hidden, only gray background remains
142         if ( m_visiblePage )
143             m_visiblePage->Hide();
144         m_visiblePage = info->win;
145
146         //   2. window is resized to fix the new page, with animation
147         //      (in our case, using overriden DoMoveWindow())
148         SetClientSize(info->win->GetSize());
149
150         //   3. new page is shown and the title updated.
151         info->win->Show();
152         SetTitle(info->page->GetName());
153
154         // TODO: Preferences window may have some pages resizeable and some
155         //       non-resizable on OS X; the whole window is or is not resizable
156         //       depending on which page is selected.
157         //
158         //       We'll need to add wxPreferencesPage::IsResizable() virtual
159         //       method to implement this.
160     }
161
162     void OnPageChanged(wxCommandEvent& event)
163     {
164         wxToolBarToolBase *tool = m_toolbar->FindById(event.GetId());
165         wxCHECK_RET( tool, "invalid tool ID" );
166         OnSelectPageForTool(tool);
167     }
168
169     void OnClose(wxCloseEvent& e)
170     {
171         // Instead of destroying the window, just hide it, it could be
172         // reused again by another invocation of the editor.
173         Hide();
174     }
175
176 private:
177     struct PageInfo : public wxObject
178     {
179         PageInfo(wxPreferencesPage *p) : page(p), win(NULL) {}
180
181         wxSharedPtr<wxPreferencesPage> page;
182         wxWindow *win;
183     };
184     // All pages. Use shared pointer to be able to get pointers to PageInfo structs
185     wxVector< wxSharedPtr<PageInfo> > m_pages;
186
187     wxToolBar *m_toolbar;
188     bool       m_toolbarRealized;
189     wxWindow  *m_visiblePage;
190 };
191
192
193 class wxCocoaPreferencesEditorImpl : public wxPreferencesEditorImpl
194 {
195 public:
196     wxCocoaPreferencesEditorImpl(const wxString& title)
197         : m_win(NULL), m_title(title)
198     {
199     }
200
201     virtual ~wxCocoaPreferencesEditorImpl()
202     {
203         // m_win may already be destroyed if this destructor is called from
204         // wxApp's destructor. In that case, all windows -- including this
205         // one -- would already be destroyed by now.
206         if ( m_win )
207             m_win->Destroy();
208     }
209
210     virtual void AddPage(wxPreferencesPage* page)
211     {
212         GetWin()->AddPage(page);
213     }
214
215     virtual void Show(wxWindow* WXUNUSED(parent))
216     {
217         // OS X preferences windows don't have parents, they are independent
218         // windows, so we just ignore the 'parent' argument.
219         wxWindow *win = GetWin();
220         win->Show();
221         win->Raise();
222     }
223
224     virtual void Dismiss()
225     {
226         // Don't destroy the window, only hide it, because OS X preferences
227         // window typically remember their state even when closed. Reopening
228         // the window should show it in the exact same state the user left it.
229         GetWin()->Hide();
230     }
231
232 private:
233     // Use this function to access m_win, so that the window is only created on
234     // demand when actually needed.
235     wxCocoaPrefsWindow* GetWin()
236     {
237         if ( !m_win )
238         {
239             if ( m_title.empty() )
240                 m_title = _("Preferences");
241
242             m_win = new wxCocoaPrefsWindow(m_title);
243         }
244
245         return m_win;
246     }
247
248     wxWeakRef<wxCocoaPrefsWindow> m_win;
249
250     wxString m_title;
251 };
252
253 /*static*/
254 wxPreferencesEditorImpl* wxPreferencesEditorImpl::Create(const wxString& title)
255 {
256     return new wxCocoaPreferencesEditorImpl(title);
257 }
258
259 #endif // wxHAS_PREF_EDITOR_NATIVE
260
261 #endif // wxUSE_PREFERENCES_EDITOR