]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - libs/gtkutil/filechooser.cpp
reformat code! now the code is only ugly on the *inside*
[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/gtk.h>
29 #include <uilib/uilib.h>
30
31 #include "string/string.h"
32 #include "stream/stringstream.h"
33 #include "container/array.h"
34 #include "os/path.h"
35 #include "os/file.h"
36
37 #include "messagebox.h"
38
39
40 struct filetype_pair_t {
41     filetype_pair_t()
42             : m_moduleName("")
43     {
44     }
45
46     filetype_pair_t(const char *moduleName, filetype_t type)
47             : m_moduleName(moduleName), m_type(type)
48     {
49     }
50
51     const char *m_moduleName;
52     filetype_t m_type;
53 };
54
55 class FileTypeList : public IFileTypeList {
56     struct filetype_copy_t {
57         filetype_copy_t(const filetype_pair_t &other)
58                 : m_moduleName(other.m_moduleName), m_name(other.m_type.name), m_pattern(other.m_type.pattern)
59         {
60         }
61
62         CopiedString m_moduleName;
63         CopiedString m_name;
64         CopiedString m_pattern;
65     };
66
67     typedef std::list<filetype_copy_t> Types;
68     Types m_types;
69 public:
70
71     typedef Types::const_iterator const_iterator;
72
73     const_iterator begin() const
74     {
75         return m_types.begin();
76     }
77
78     const_iterator end() const
79     {
80         return m_types.end();
81     }
82
83     std::size_t size() const
84     {
85         return m_types.size();
86     }
87
88     void addType(const char *moduleName, filetype_t type)
89     {
90         m_types.push_back(filetype_pair_t(moduleName, type));
91     }
92 };
93
94
95 class GTKMasks {
96     const FileTypeList &m_types;
97 public:
98     std::vector<CopiedString> m_filters;
99     std::vector<CopiedString> m_masks;
100
101     GTKMasks(const FileTypeList &types) : m_types(types)
102     {
103         m_masks.reserve(m_types.size());
104         for (FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i) {
105             std::size_t len = strlen((*i).m_name.c_str()) + strlen((*i).m_pattern.c_str()) + 3;
106             StringOutputStream buffer(len + 1); // length + null char
107
108             buffer << (*i).m_name.c_str() << " <" << (*i).m_pattern.c_str() << ">";
109
110             m_masks.push_back(buffer.c_str());
111         }
112
113         m_filters.reserve(m_types.size());
114         for (FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i) {
115             m_filters.push_back((*i).m_pattern);
116         }
117     }
118
119     filetype_pair_t GetTypeForGTKMask(const char *mask) const
120     {
121         std::vector<CopiedString>::const_iterator j = m_masks.begin();
122         for (FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i, ++j) {
123             if (string_equal((*j).c_str(), mask)) {
124                 return filetype_pair_t((*i).m_moduleName.c_str(),
125                                        filetype_t((*i).m_name.c_str(), (*i).m_pattern.c_str()));
126             }
127         }
128         return filetype_pair_t();
129     }
130
131 };
132
133 static char g_file_dialog_file[1024];
134
135 const char *
136 file_dialog_show(ui::Window parent, bool open, const char *title, const char *path, const char *pattern, bool want_load,
137                  bool want_import, bool want_save)
138 {
139     filetype_t type;
140
141     if (pattern == 0) {
142         pattern = "*";
143     }
144
145     FileTypeList typelist;
146     GlobalFiletypes().getTypeList(pattern, &typelist, want_load, want_import, want_save);
147
148     GTKMasks masks(typelist);
149
150     if (title == 0) {
151         title = open ? "Open File" : "Save File";
152     }
153
154     ui::Dialog dialog{ui::null};
155     if (open) {
156         dialog = ui::Dialog::from(gtk_file_chooser_dialog_new(title,
157                                                               parent,
158                                                               GTK_FILE_CHOOSER_ACTION_OPEN,
159                                                               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
160                                                               GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
161                                                               NULL));
162     } else {
163         dialog = ui::Dialog::from(gtk_file_chooser_dialog_new(title,
164                                                               parent,
165                                                               GTK_FILE_CHOOSER_ACTION_SAVE,
166                                                               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
167                                                               GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
168                                                               NULL));
169         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "unnamed");
170     }
171
172     gtk_window_set_modal(dialog, TRUE);
173     gtk_window_set_position(dialog, GTK_WIN_POS_CENTER_ON_PARENT);
174
175     // we expect an actual path below, if the path is 0 we might crash
176     if (path != 0 && !string_empty(path)) {
177         ASSERT_MESSAGE(path_is_absolute(path), "file_dialog_show: path not absolute: " << makeQuoted(path));
178
179         Array<char> new_path(strlen(path) + 1);
180
181         // copy path, replacing dir separators as appropriate
182         Array<char>::iterator w = new_path.begin();
183         for (const char *r = path; *r != '\0'; ++r) {
184             *w++ = (*r == '/') ? G_DIR_SEPARATOR : *r;
185         }
186         // remove separator from end of path if required
187         if (*(w - 1) == G_DIR_SEPARATOR) {
188             --w;
189         }
190         // terminate string
191         *w = '\0';
192
193         gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), new_path.data());
194     }
195
196     // we should add all important paths as shortcut folder...
197     // gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), "/tmp/", NULL);
198
199
200     for (std::size_t i = 0; i < masks.m_filters.size(); ++i) {
201         GtkFileFilter *filter = gtk_file_filter_new();
202         gtk_file_filter_add_pattern(filter, masks.m_filters[i].c_str());
203         gtk_file_filter_set_name(filter, masks.m_masks[i].c_str());
204         gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
205     }
206
207     if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
208         strcpy(g_file_dialog_file, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
209
210         if (!string_equal(pattern, "*")) {
211             GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
212             if (filter !=
213                 0) { // no filter set? some file-chooser implementations may allow the user to set no filter, which we treat as 'all files'
214                 type = masks.GetTypeForGTKMask(gtk_file_filter_get_name(filter)).m_type;
215                 // last ext separator
216                 const char *extension = path_get_extension(g_file_dialog_file);
217                 // no extension
218                 if (string_empty(extension)) {
219                     strcat(g_file_dialog_file, type.pattern + 1);
220                 } else {
221                     strcpy(g_file_dialog_file + (extension - g_file_dialog_file), type.pattern + 2);
222                 }
223             }
224         }
225
226         // convert back to unix format
227         for (char *w = g_file_dialog_file; *w != '\0'; w++) {
228             if (*w == '\\') {
229                 *w = '/';
230             }
231         }
232     } else {
233         g_file_dialog_file[0] = '\0';
234     }
235
236     ui::Widget(dialog).destroy();
237
238     // don't return an empty filename
239     if (g_file_dialog_file[0] == '\0') {
240         return NULL;
241     }
242
243     return g_file_dialog_file;
244 }
245
246 char *dir_dialog(ui::Window parent, const char *title, const char *path)
247 {
248     auto dialog = ui::Dialog::from(gtk_file_chooser_dialog_new(title,
249                                                                parent,
250                                                                GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
251                                                                GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
252                                                                GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
253                                                                NULL));
254
255     gtk_window_set_modal(dialog, TRUE);
256     gtk_window_set_position(dialog, GTK_WIN_POS_CENTER_ON_PARENT);
257
258     if (!string_empty(path)) {
259         gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path);
260     }
261
262     char *filename = 0;
263     if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
264         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
265     }
266
267     dialog.destroy();
268
269     return filename;
270 }
271
272 const char *
273 file_dialog(ui::Window parent, bool open, const char *title, const char *path, const char *pattern, bool want_load,
274             bool want_import, bool want_save)
275 {
276     for (;;) {
277         const char *file = file_dialog_show(parent, open, title, path, pattern, want_load, want_import, want_save);
278
279         if (open
280             || !file
281             || !file_exists(file)
282             || ui::alert(parent, "The file specified already exists.\nDo you want to replace it?", title,
283                          ui::alert_type::NOYES, ui::alert_icon::Question) == ui::alert_response::YES) {
284             return file;
285         }
286     }
287 }