]> git.xonotic.org Git - xonotic/netradiant.git/blob - libs/gtkutil/filechooser.cpp
Wrap gtk
[xonotic/netradiant.git] / libs / gtkutil / filechooser.cpp
1 /*
2    Copyright (C) 2001-2006, William Joseph.
3    All Rights Reserved.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #include "filechooser.h"
23
24 #include "ifiletypes.h"
25
26 #include <list>
27 #include <vector>
28 #include <gtk/gtkwidget.h>
29 #include <gtk/gtkwindow.h>
30 #include <gtk/gtkfilechooser.h>
31 #include <gtk/gtkfilechooserdialog.h>
32 #include <gtk/gtkstock.h>
33 #include <uilib/uilib.h>
34
35 #include "string/string.h"
36 #include "stream/stringstream.h"
37 #include "container/array.h"
38 #include "os/path.h"
39 #include "os/file.h"
40
41 #include "messagebox.h"
42
43
44 struct filetype_pair_t
45 {
46         filetype_pair_t()
47                 : m_moduleName( "" ){
48         }
49         filetype_pair_t( const char* moduleName, filetype_t type )
50                 : m_moduleName( moduleName ), m_type( type ){
51         }
52         const char* m_moduleName;
53         filetype_t m_type;
54 };
55
56 class FileTypeList : public IFileTypeList
57 {
58 struct filetype_copy_t
59 {
60         filetype_copy_t( const filetype_pair_t& other )
61                 : m_moduleName( other.m_moduleName ), m_name( other.m_type.name ), m_pattern( other.m_type.pattern ){
62         }
63         std::string m_moduleName;
64         std::string m_name;
65         std::string m_pattern;
66 };
67
68 typedef std::list<filetype_copy_t> Types;
69 Types m_types;
70 public:
71
72 typedef Types::const_iterator const_iterator;
73 const_iterator begin() const {
74         return m_types.begin();
75 }
76 const_iterator end() const {
77         return m_types.end();
78 }
79
80 std::size_t size() const {
81         return m_types.size();
82 }
83
84 void addType( const char* moduleName, filetype_t type ){
85         m_types.push_back( filetype_pair_t( moduleName, type ) );
86 }
87 };
88
89
90 class GTKMasks
91 {
92 const FileTypeList& m_types;
93 public:
94 std::vector<std::string> m_filters;
95 std::vector<std::string> m_masks;
96
97 GTKMasks( const FileTypeList& types ) : m_types( types ){
98         m_masks.reserve( m_types.size() );
99         for ( FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i )
100         {
101                 std::size_t len = strlen( ( *i ).m_name.c_str() ) + strlen( ( *i ).m_pattern.c_str() ) + 3;
102                 StringOutputStream buffer( len + 1 ); // length + null char
103
104                 buffer << ( *i ).m_name.c_str() << " <" << ( *i ).m_pattern.c_str() << ">";
105
106                 m_masks.push_back( buffer.c_str() );
107         }
108
109         m_filters.reserve( m_types.size() );
110         for ( FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i )
111         {
112                 m_filters.push_back( ( *i ).m_pattern );
113         }
114 }
115
116 filetype_pair_t GetTypeForGTKMask( const char *mask ) const {
117         std::vector<std::string>::const_iterator j = m_masks.begin();
118         for ( FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i, ++j )
119         {
120                 if ( string_equal( ( *j ).c_str(), mask ) ) {
121                         return filetype_pair_t( ( *i ).m_moduleName.c_str(), filetype_t( ( *i ).m_name.c_str(), ( *i ).m_pattern.c_str() ) );
122                 }
123         }
124         return filetype_pair_t();
125 }
126
127 };
128
129 static char g_file_dialog_file[1024];
130
131 const char* file_dialog_show( GtkWidget* parent, bool open, const char* title, const char* path, const char* pattern, bool want_load, bool want_import, bool want_save ){
132         filetype_t type;
133
134         if ( pattern == 0 ) {
135                 pattern = "*";
136         }
137
138         FileTypeList typelist;
139         GlobalFiletypes().getTypeList( pattern, &typelist, want_load, want_import, want_save );
140
141         GTKMasks masks( typelist );
142
143         if ( title == 0 ) {
144                 title = open ? "Open File" : "Save File";
145         }
146
147         GtkWidget* dialog;
148         if ( open ) {
149                 dialog = gtk_file_chooser_dialog_new( title,
150                                                                                           GTK_WINDOW( parent ),
151                                                                                           GTK_FILE_CHOOSER_ACTION_OPEN,
152                                                                                           GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
153                                                                                           GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
154                                                                                           NULL );
155         }
156         else
157         {
158                 dialog = gtk_file_chooser_dialog_new( title,
159                                                                                           GTK_WINDOW( parent ),
160                                                                                           GTK_FILE_CHOOSER_ACTION_SAVE,
161                                                                                           GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
162                                                                                           GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
163                                                                                           NULL );
164                 gtk_file_chooser_set_current_name( GTK_FILE_CHOOSER( dialog ), "unnamed" );
165         }
166
167         gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
168         gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_CENTER_ON_PARENT );
169
170         // we expect an actual path below, if the path is 0 we might crash
171         if ( path != 0 && !string_empty( path ) ) {
172                 ASSERT_MESSAGE( path_is_absolute( path ), "file_dialog_show: path not absolute: " << makeQuoted( path ) );
173
174                 std::string new_path(path);
175                 std::replace(new_path.begin(), new_path.end(), '/', G_DIR_SEPARATOR);
176                 if ( !new_path.empty() && new_path.back() == G_DIR_SEPARATOR )
177                         new_path.pop_back();
178
179                 gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( dialog ), new_path.c_str() );
180         }
181
182         // we should add all important paths as shortcut folder...
183         // gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), "/tmp/", NULL);
184
185
186         for ( std::size_t i = 0; i < masks.m_filters.size(); ++i )
187         {
188                 GtkFileFilter* filter = gtk_file_filter_new();
189                 gtk_file_filter_add_pattern( filter, masks.m_filters[i].c_str() );
190                 gtk_file_filter_set_name( filter, masks.m_masks[i].c_str() );
191                 gtk_file_chooser_add_filter( GTK_FILE_CHOOSER( dialog ), filter );
192         }
193
194         if ( gtk_dialog_run( GTK_DIALOG( dialog ) ) == GTK_RESPONSE_ACCEPT ) {
195                 strcpy( g_file_dialog_file, gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( dialog ) ) );
196
197                 if ( !string_equal( pattern, "*" ) ) {
198                         GtkFileFilter* filter = gtk_file_chooser_get_filter( GTK_FILE_CHOOSER( dialog ) );
199                         if ( filter != 0 ) { // no filter set? some file-chooser implementations may allow the user to set no filter, which we treat as 'all files'
200                                 type = masks.GetTypeForGTKMask( gtk_file_filter_get_name( filter ) ).m_type;
201                                 // last ext separator
202                                 const char* extension = path_get_extension( g_file_dialog_file );
203                                 // no extension
204                                 if ( string_empty( extension ) ) {
205                                         strcat( g_file_dialog_file, type.pattern + 1 );
206                                 }
207                                 else
208                                 {
209                                         strcpy( g_file_dialog_file + ( extension - g_file_dialog_file ), type.pattern + 2 );
210                                 }
211                         }
212                 }
213
214                 // convert back to unix format
215                 for ( char* w = g_file_dialog_file; *w != '\0'; w++ )
216                 {
217                         if ( *w == '\\' ) {
218                                 *w = '/';
219                         }
220                 }
221         }
222         else
223         {
224                 g_file_dialog_file[0] = '\0';
225         }
226
227         gtk_widget_destroy( dialog );
228
229         // don't return an empty filename
230         if ( g_file_dialog_file[0] == '\0' ) {
231                 return NULL;
232         }
233
234         return g_file_dialog_file;
235 }
236
237 char* dir_dialog( ui::Widget parent, const char* title, const char* path ){
238         GtkWidget* dialog = gtk_file_chooser_dialog_new( title,
239                                                                                                          GTK_WINDOW( parent ),
240                                                                                                          GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
241                                                                                                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
242                                                                                                          GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
243                                                                                                          NULL );
244
245         gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
246         gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_CENTER_ON_PARENT );
247
248         if ( !string_empty( path ) ) {
249                 gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( dialog ), path );
250         }
251
252         char* filename = 0;
253         if ( gtk_dialog_run( GTK_DIALOG( dialog ) ) == GTK_RESPONSE_ACCEPT ) {
254                 filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( dialog ) );
255         }
256
257         gtk_widget_destroy( dialog );
258
259         return filename;
260 }
261
262 const char* file_dialog( ui::Widget parent, bool open, const char* title, const char* path, const char* pattern, bool want_load, bool want_import, bool want_save ){
263         for (;; )
264         {
265                 const char* file = file_dialog_show( parent, open, title, path, pattern, want_load, want_import, want_save );
266
267                 if ( open
268                          || file == nullptr
269                          || !file_exists( file )
270                          || parent.alert("The file specified already exists.\nDo you want to replace it?", title, ui::alert_type::NOYES, ui::alert_icon::QUESTION ) == ui::alert_response::YES ) {
271                         return file;
272                 }
273         }
274 }