1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/filepicker.cpp
3 // Purpose: implementation of wxFileButton and wxDirButton
4 // Author: Francesco Montorsi
5 // Modified By:
6 // Created: 15/04/2006
7 // Copyright: (c) Francesco Montorsi
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11
12 // ----------------------------------------------------------------------------
13 // headers
14 // ----------------------------------------------------------------------------
15
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18
19 #if wxUSE_FILEPICKERCTRL
20
21 #ifndef WX_PRECOMP
22 #include "wx/log.h"
23 #endif
24
25 #include "wx/filepicker.h"
26 #include "wx/tooltip.h"
27
28 #include "wx/gtk/private.h"
29
30 // ============================================================================
31 // implementation
32 // ============================================================================
33
34 //-----------------------------------------------------------------------------
35 // wxFileButton
36 //-----------------------------------------------------------------------------
37
38 wxIMPLEMENT_DYNAMIC_CLASS(wxFileButton, wxButton);
39
Create(wxWindow * parent,wxWindowID id,const wxString & label,const wxString & path,const wxString & message,const wxString & wildcard,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)40 bool wxFileButton::Create( wxWindow *parent, wxWindowID id,
41 const wxString &label, const wxString &path,
42 const wxString &message, const wxString &wildcard,
43 const wxPoint &pos, const wxSize &size,
44 long style, const wxValidator& validator,
45 const wxString &name )
46 {
47 // we can't use the native button for wxFLP_SAVE pickers as it can only
48 // open existing files and there is no way to create a new file using it
49 if (!(style & wxFLP_SAVE) && !(style & wxFLP_USE_TEXTCTRL))
50 {
51 // VERY IMPORTANT: this code is identical to relative code in wxDirButton;
52 // if you find a problem here, fix it also in wxDirButton !
53
54 if (!PreCreation( parent, pos, size ) ||
55 !wxControl::CreateBase(parent, id, pos, size, style & wxWINDOW_STYLE_MASK,
56 validator, name))
57 {
58 wxFAIL_MSG( wxT("wxFileButton creation failed") );
59 return false;
60 }
61
62 // create the dialog associated with this button
63 // NB: unlike generic implementation, native GTK implementation needs to create
64 // the filedialog here as it needs to use gtk_file_chooser_button_new_with_dialog()
65 SetWindowStyle(style);
66 m_path = path;
67 m_message = message;
68 m_wildcard = wildcard;
69 if ((m_dialog = CreateDialog()) == NULL)
70 return false;
71
72 // little trick used to avoid problems when there are other GTK windows 'grabbed':
73 // GtkFileChooserDialog won't be responsive to user events if there is another
74 // window which called gtk_grab_add (and this happens if e.g. a wxDialog is running
75 // in modal mode in the application - see wxDialogGTK::ShowModal).
76 // An idea could be to put the grab on the m_dialog->m_widget when the GtkFileChooserButton
77 // is clicked and then remove it as soon as the user closes the dialog itself.
78 // Unfortunately there's no way to hook in the 'clicked' event of the GtkFileChooserButton,
79 // thus we add grab on m_dialog->m_widget when it's shown and remove it when it's
80 // hidden simply using its "show" and "hide" events - clean & simple :)
81 g_signal_connect(m_dialog->m_widget, "show", G_CALLBACK(gtk_grab_add), NULL);
82 g_signal_connect(m_dialog->m_widget, "hide", G_CALLBACK(gtk_grab_remove), NULL);
83
84 // use as label the currently selected file
85 m_widget = gtk_file_chooser_button_new_with_dialog( m_dialog->m_widget );
86 g_object_ref(m_widget);
87
88 // we need to know when the dialog has been dismissed clicking OK...
89 // NOTE: the "clicked" signal is not available for a GtkFileChooserButton
90 // thus we are forced to use wxFileDialog's event
91 m_dialog->Bind(wxEVT_BUTTON, &wxFileButton::OnDialogOK, this);
92
93 m_parent->DoAddChild( this );
94
95 PostCreation(size);
96 SetInitialSize(size);
97 }
98 else
99 return wxGenericFileButton::Create(parent, id, label, path, message, wildcard,
100 pos, size, style, validator, name);
101 return true;
102 }
103
~wxFileButton()104 wxFileButton::~wxFileButton()
105 {
106 if ( m_dialog )
107 {
108 // when m_dialog is deleted, it will destroy the widget it is sharing
109 // with GtkFileChooserButton, which results in a bunch of Gtk-CRITICAL
110 // errors from GtkFileChooserButton. To avoid this, call gtk_widget_destroy()
111 // on GtkFileChooserButton first (our base dtor will do it again, but
112 // that does no harm). m_dialog holds a reference to the shared widget,
113 // so it won't go away until m_dialog base dtor unrefs it.
114 gtk_widget_destroy(m_widget);
115 delete m_dialog;
116 }
117 }
118
OnDialogOK(wxCommandEvent & ev)119 void wxFileButton::OnDialogOK(wxCommandEvent& ev)
120 {
121 // the wxFileDialog associated with the GtkFileChooserButton has been closed
122 // using the OK button, thus the selected file has changed...
123 if (ev.GetId() == wxID_OK)
124 {
125 // ...update our path
126 UpdatePathFromDialog(m_dialog);
127
128 // ...and fire an event
129 wxFileDirPickerEvent event(wxEVT_FILEPICKER_CHANGED, this, GetId(), m_path);
130 HandleWindowEvent(event);
131 }
132 }
133
SetPath(const wxString & str)134 void wxFileButton::SetPath(const wxString &str)
135 {
136 m_path = str;
137
138 if (m_dialog)
139 UpdateDialogPath(m_dialog);
140 }
141
SetInitialDirectory(const wxString & dir)142 void wxFileButton::SetInitialDirectory(const wxString& dir)
143 {
144 if (m_dialog)
145 {
146 // Only change the directory if the default file name doesn't have any
147 // directory in it, otherwise it takes precedence.
148 if ( m_path.find_first_of(wxFileName::GetPathSeparators()) ==
149 wxString::npos )
150 {
151 static_cast<wxFileDialog*>(m_dialog)->SetDirectory(dir);
152 }
153 }
154 else
155 wxGenericFileButton::SetInitialDirectory(dir);
156 }
157
DoApplyWidgetStyle(GtkRcStyle *)158 void wxFileButton::DoApplyWidgetStyle(GtkRcStyle*)
159 {
160 }
161 #endif // wxUSE_FILEPICKERCTRL
162
163 #if wxUSE_DIRPICKERCTRL
164
165 #ifdef __UNIX__
166 #include <unistd.h> // chdir
167 #endif
168
169 //-----------------------------------------------------------------------------
170 // "file-set"
171 //-----------------------------------------------------------------------------
172
173 extern "C" {
file_set(GtkFileChooser * widget,wxDirButton * p)174 static void file_set(GtkFileChooser* widget, wxDirButton* p)
175 {
176 // NB: it's important to use gtk_file_chooser_get_filename instead of
177 // gtk_file_chooser_get_current_folder (see GTK docs) !
178 wxGtkString filename(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)));
179 p->GTKUpdatePath(filename);
180
181 // since GtkFileChooserButton when used to pick directories also uses a combobox,
182 // maybe that the current folder has been changed but not through the GtkFileChooserDialog
183 // and thus the 'gtk_filedialog_ok_callback' could have not been called...
184 // thus we need to make sure the current working directory is updated if wxDIRP_CHANGE_DIR
185 // style was given.
186 if (p->HasFlag(wxDIRP_CHANGE_DIR))
187 {
188 if ( chdir(filename) != 0 )
189 {
190 wxLogSysError(_("Changing current directory to \"%s\" failed"),
191 wxString::FromUTF8(filename));
192 }
193 }
194
195 // ...and fire an event
196 wxFileDirPickerEvent event(wxEVT_DIRPICKER_CHANGED, p, p->GetId(), p->GetPath());
197 p->HandleWindowEvent(event);
198 }
199 }
200
201 //-----------------------------------------------------------------------------
202 // "selection-changed"
203 //-----------------------------------------------------------------------------
204
205 extern "C" {
selection_changed(GtkFileChooser * chooser,wxDirButton * win)206 static void selection_changed(GtkFileChooser* chooser, wxDirButton* win)
207 {
208 char* filename = gtk_file_chooser_get_filename(chooser);
209
210 if (wxString::FromUTF8(filename) == win->GetPath())
211 win->m_bIgnoreNextChange = false;
212 else if (!win->m_bIgnoreNextChange)
213 file_set(chooser, win);
214
215 g_free(filename);
216 }
217 }
218
219 //-----------------------------------------------------------------------------
220 // wxDirButtonGTK
221 //-----------------------------------------------------------------------------
222
223 wxIMPLEMENT_DYNAMIC_CLASS(wxDirButton, wxButton);
224
Create(wxWindow * parent,wxWindowID id,const wxString & label,const wxString & path,const wxString & message,const wxString & wildcard,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)225 bool wxDirButton::Create( wxWindow *parent, wxWindowID id,
226 const wxString &label, const wxString &path,
227 const wxString &message, const wxString &wildcard,
228 const wxPoint &pos, const wxSize &size,
229 long style, const wxValidator& validator,
230 const wxString &name )
231 {
232 if (!(style & wxDIRP_USE_TEXTCTRL))
233 {
234 // VERY IMPORTANT: this code is identic to relative code in wxFileButton;
235 // if you find a problem here, fix it also in wxFileButton !
236
237 if (!PreCreation( parent, pos, size ) ||
238 !wxControl::CreateBase(parent, id, pos, size, style & wxWINDOW_STYLE_MASK,
239 validator, name))
240 {
241 wxFAIL_MSG( wxT("wxDirButtonGTK creation failed") );
242 return false;
243 }
244
245 // create the dialog associated with this button
246 SetWindowStyle(style);
247 m_message = message;
248 m_wildcard = wildcard;
249 if ((m_dialog = CreateDialog()) == NULL)
250 return false;
251
252 // little trick used to avoid problems when there are other GTK windows 'grabbed':
253 // GtkFileChooserDialog won't be responsive to user events if there is another
254 // window which called gtk_grab_add (and this happens if e.g. a wxDialog is running
255 // in modal mode in the application - see wxDialogGTK::ShowModal).
256 // An idea could be to put the grab on the m_dialog->m_widget when the GtkFileChooserButton
257 // is clicked and then remove it as soon as the user closes the dialog itself.
258 // Unfortunately there's no way to hook in the 'clicked' event of the GtkFileChooserButton,
259 // thus we add grab on m_dialog->m_widget when it's shown and remove it when it's
260 // hidden simply using its "show" and "hide" events - clean & simple :)
261 g_signal_connect(m_dialog->m_widget, "show", G_CALLBACK(gtk_grab_add), NULL);
262 g_signal_connect(m_dialog->m_widget, "hide", G_CALLBACK(gtk_grab_remove), NULL);
263
264
265 // NOTE: we deliberately ignore the given label as GtkFileChooserButton
266 // use as label the currently selected file
267 m_widget = gtk_file_chooser_button_new_with_dialog( m_dialog->m_widget );
268 g_object_ref(m_widget);
269 SetPath(path);
270
271 #ifdef __WXGTK3__
272 if (gtk_check_version(3,8,0) == NULL)
273 g_signal_connect(m_widget, "file_set", G_CALLBACK(file_set), this);
274 else
275 #endif
276 {
277 // prior to GTK+ 3.8 neither "file-set" nor "current-folder-changed" will be
278 // emitted when the user selects one of the special folders from the combobox
279 g_signal_connect(m_widget, "selection_changed",
280 G_CALLBACK(selection_changed), this);
281 }
282
283 m_parent->DoAddChild( this );
284
285 PostCreation(size);
286 SetInitialSize(size);
287 }
288 else
289 return wxGenericDirButton::Create(parent, id, label, path, message, wildcard,
290 pos, size, style, validator, name);
291 return true;
292 }
293
~wxDirButton()294 wxDirButton::~wxDirButton()
295 {
296 if (m_dialog)
297 {
298 // see ~wxFileButton() comment
299 gtk_widget_destroy(m_widget);
300 delete m_dialog;
301 }
302 }
303
GTKUpdatePath(const char * gtkpath)304 void wxDirButton::GTKUpdatePath(const char *gtkpath)
305 {
306 m_path = wxString::FromUTF8(gtkpath);
307 }
SetPath(const wxString & str)308 void wxDirButton::SetPath(const wxString& str)
309 {
310 if ( m_path == str )
311 return;
312
313 m_path = str;
314
315 m_bIgnoreNextChange = true;
316
317 if (GTK_IS_FILE_CHOOSER(m_widget))
318 gtk_file_chooser_set_filename((GtkFileChooser*)m_widget, str.utf8_str());
319 else if (m_dialog)
320 UpdateDialogPath(m_dialog);
321 }
322
SetInitialDirectory(const wxString & dir)323 void wxDirButton::SetInitialDirectory(const wxString& dir)
324 {
325 if (m_dialog)
326 {
327 if (m_path.empty())
328 static_cast<wxDirDialog*>(m_dialog)->SetPath(dir);
329 }
330 else
331 wxGenericDirButton::SetInitialDirectory(dir);
332 }
333
DoApplyWidgetStyle(GtkRcStyle *)334 void wxDirButton::DoApplyWidgetStyle(GtkRcStyle*)
335 {
336 }
337 #endif // wxUSE_DIRPICKERCTRL
338