README.md edited online with Bitbucket
[gdash.git] / src / framework / selectfileactivity.cpp
blobb85f4e072ba4b82e8baa4d7d7ab712cddf1f063d
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
19 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include "framework/selectfileactivity.hpp"
25 #include "cave/colors.hpp"
26 #include "framework/app.hpp"
27 #include <glib/gi18n.h>
28 #include "framework/commands.hpp"
29 #include "gfx/fontmanager.hpp"
31 #include <glib.h>
32 #include <glib/gstdio.h>
33 #include <algorithm>
34 #include "gfx/screen.hpp"
35 #include "misc/util.hpp"
36 #include "misc/autogfreeptr.hpp"
38 // TODO utf8-filename charset audit
41 class JumpToDirectoryCommand: public Command1Param<std::string> {
42 public:
43 JumpToDirectoryCommand(App *app, SelectFileActivity *activity)
45 Command1Param<std::string>(app),
46 directory(p1),
47 activity(activity) {
49 private:
50 std::string &directory;
51 SelectFileActivity *activity;
52 virtual void execute() {
53 AutoGFreePtr<char> filename_in_locale_charset(g_filename_from_utf8(directory.c_str(), -1, NULL, NULL, NULL));
54 activity->jump_to_directory(filename_in_locale_charset);
59 class FileNameEnteredCommand: public Command1Param<std::string> {
60 public:
61 FileNameEnteredCommand(App *app, SelectFileActivity *activity)
63 Command1Param<std::string>(app),
64 filepath(p1),
65 activity(activity) {
67 private:
68 std::string &filepath;
69 SelectFileActivity *activity;
70 virtual void execute() {
71 activity->file_selected(filepath.c_str());
76 /* returns a string which contains the utf8 representation of the filename originally in system encoding */
77 static std::string filename_to_utf8(const char *filename) {
78 GError *error = NULL;
79 AutoGFreePtr<char> utf8(g_filename_to_utf8(filename, -1, NULL, NULL, &error));
80 if (error) {
81 g_error_free(error);
82 return filename; // return with filename without conversion
84 return std::string(utf8);
88 /* filename sort. directories on the top. */
89 static bool filename_sort(std::string const &s1, std::string const &s2) {
90 if (s1.empty())
91 return true;
92 if (s2.empty())
93 return false;
94 bool const s1_dir = s1[s1.length() - 1] == G_DIR_SEPARATOR;
95 bool const s2_dir = s2[s2.length() - 1] == G_DIR_SEPARATOR;
96 if (s1_dir && s2_dir)
97 return s1 < s2;
98 if (s1_dir)
99 return true;
100 if (s2_dir)
101 return false;
102 return s1 < s2;
106 SelectFileActivity::SelectFileActivity(App *app, const char *title, const char *start_dir, const char *glob, bool for_save, const char *defaultname, SmartPtr<Command1Param<std::string> > command_when_successful)
108 Activity(app),
109 command_when_successful(command_when_successful),
110 title(title),
111 for_save(for_save),
112 defaultname(defaultname),
113 start_dir(start_dir ? start_dir : "") {
114 yd = app->font_manager->get_line_height();
115 names_per_page = app->screen->get_height() / yd - 5;
116 if (glob == NULL || g_str_equal(glob, ""))
117 glob = "*";
118 globs = g_strsplit_set(glob, ";", -1);
120 /* remember current directory, as we step into others */
121 directory_of_process = g_get_current_dir();
122 directory = g_strdup(directory_of_process);
126 void SelectFileActivity::pushed_event() {
127 if (start_dir != "")
128 jump_to_directory(start_dir.c_str());
129 else
130 jump_to_directory(directory_of_process);
134 SelectFileActivity::~SelectFileActivity() {
135 g_strfreev(globs);
139 void SelectFileActivity::jump_to_directory(char const *jump_to) {
140 GDir *dir;
141 /* directory we are looking at, and then to the selected one (which may be relative path!) */
142 if (g_chdir(directory) == -1 || g_chdir(jump_to) == -1 || NULL == (dir = g_dir_open(".", 0, NULL))) {
143 g_chdir(directory_of_process); /* step back to directory where we started */
144 dir = g_dir_open(".", 0, NULL);
145 if (!dir) {
146 /* problem: cannot change to requested directory, and cannot change to the process
147 * original directory as well. cannot read any of them. this is critical, the
148 * selectfileactivity cannot continue, so it deletes itself. */
149 app->enqueue_command(new PopActivityCommand(app));
150 app->show_message(SPrintf(_("Cannot change to directory: %s.")) % jump_to, SPrintf("Jumped back to directory: %s.") % directory_of_process);
151 } else {
152 /* cannot read the new directory, but managed to jump back to the original. issue
153 * an error then continue in the original dir. */
154 app->show_message(SPrintf(_("Cannot change to directory: %s.")) % jump_to, SPrintf("Jumped back to directory: %s.") % directory_of_process);
157 /* now get the directory we have stepped into, and it is readable as well. remember! */
158 g_free(directory);
159 directory = g_get_current_dir();
161 files.clear();
162 char const *name;
163 while ((name = g_dir_read_name(dir)) != NULL) {
164 #ifdef G_OS_WIN32
165 /* on windows, skip hidden files? */
166 #else
167 /* on unix, skip file names starting with a '.' - those are hidden files */
168 if (name[0] == '.')
169 continue;
170 #endif
171 if (g_file_test(name, G_FILE_TEST_IS_DIR))
172 files.push_back(std::string(name) + G_DIR_SEPARATOR_S); /* dirname/ or dirname\ */
173 else {
174 bool match = false;
175 for (int i = 0; globs[i] != NULL && !match; i++)
176 if (g_pattern_match_simple(globs[i], name))
177 match = true;
178 if (match)
179 files.push_back(filename_to_utf8(name));
182 g_dir_close(dir);
184 /* add "directory up" if we are NOT in a root directory */
185 #ifdef G_OS_WIN32
186 if (!g_str_has_suffix(directory, ":\\")) /* root directory is "X:\" */
187 files.push_back(std::string("..") + G_DIR_SEPARATOR_S); /* ..\ */
188 #else
189 if (!g_str_equal(directory, "/"))
190 files.push_back(std::string("..") + G_DIR_SEPARATOR_S); /* ../ */
191 #endif
192 /* sort the array */
193 sort(files.begin(), files.end(), filename_sort);
194 sel = 0;
196 /* step back to directory where we started */
197 g_chdir(directory_of_process);
199 queue_redraw();
203 void SelectFileActivity::file_selected_do_command() {
204 app->enqueue_command(command_when_successful);
205 app->enqueue_command(new PopActivityCommand(app));
209 /** This command forces the SelectFileActivity object to do the save.
210 * It can be used as a command to be processed after a "do you really want to save"
211 * question shown by an AskYesNoActivity. . */
212 class SelectFileForceSaveCommand: public Command {
213 public:
214 SelectFileForceSaveCommand(App *app, SelectFileActivity *activity)
215 : Command(app),
216 activity(activity) {
218 private:
219 SelectFileActivity *activity;
220 virtual void execute() {
221 activity->file_selected_do_command();
226 void SelectFileActivity::file_selected(char const *filename) {
227 /* ok so set the filename in the pending command object. */
228 AutoGFreePtr<char> result_filename(g_build_path(G_DIR_SEPARATOR_S, directory, filename, NULL));
229 command_when_successful->set_param1(std::string(result_filename));
231 /* a file is selected, so enqueue the command. */
232 /* but first - check if it is an overwrite! if the user does not allow the overwriting, we
233 * should do nothing. so create an AskYesNoActivity, which will call the
234 * file_selected_do_command() method on the SelectFileActivity, when the user
235 * accepts the overwrite. */
236 if (for_save && g_file_test(filename, G_FILE_TEST_EXISTS)) {
237 /* ask the overwrite. */
238 app->ask_yesorno_and_do_command(_("File exists. Overwrite?"), "Yes", "No", new SelectFileForceSaveCommand(app, this), SmartPtr<Command>());
239 } else {
240 /* not a "save file" activity, so no problem if an existing file is
241 * selected. */
242 file_selected_do_command();
247 void SelectFileActivity::process_enter() {
248 if (g_str_has_suffix(files[sel].c_str(), G_DIR_SEPARATOR_S)) {
249 /* directory selected */
250 jump_to_directory(files[sel].c_str());
251 } else {
252 /* file selected */
253 file_selected(files[sel].c_str());
258 void SelectFileActivity::keypress_event(KeyCode keycode, int gfxlib_keycode) {
259 switch (keycode) {
260 /* movements */
261 case App::PageUp:
262 sel = gd_clamp(sel - names_per_page, 0, files.size() - 1);
263 queue_redraw();
264 break;
265 case App::PageDown:
266 sel = gd_clamp(sel + names_per_page, 0, files.size() - 1);
267 queue_redraw();
268 break;
269 case App::Up:
270 sel = gd_clamp(sel - 1, 0, files.size() - 1);
271 queue_redraw();
272 break;
273 case App::Down:
274 sel = gd_clamp(sel + 1, 0, files.size() - 1);
275 queue_redraw();
276 break;
277 case App::Home:
278 sel = 0;
279 queue_redraw();
280 break;
281 case App::End:
282 sel = files.size() - 1;
283 queue_redraw();
284 break;
285 case App::Enter:
286 process_enter();
287 break;
289 /* jump to directory (name will be typed) */
290 case 'j':
291 case 'J':
292 // TRANSLATORS: 35 chars max
293 app->input_text_and_do_command(_("Jump to directory"), directory, new JumpToDirectoryCommand(app, this));
294 break;
295 /* enter new filename - only if saving allowed */
296 case 'n':
297 case 'N':
298 if (for_save) {
299 // TRANSLATORS: 35 chars max
300 app->input_text_and_do_command(_("Enter new file name"), defaultname.c_str(), new FileNameEnteredCommand(app, this));
302 break;
304 case App::Escape:
305 app->enqueue_command(new PopActivityCommand(app));
306 break;
308 default:
309 /* other keys do nothing */
310 break;
315 void SelectFileActivity::redraw_event(bool full) const {
316 app->clear_screen();
318 /* show current directory */
319 app->title_line(title.c_str());
320 app->set_color(GD_GDASH_YELLOW);
321 app->blittext_n(-1, 1 * yd, filename_to_utf8(directory).c_str());
322 if (for_save) {
323 // TRANSLATORS: 40 chars max
324 app->status_line(_("Crsr:select N:new J:jump Esc:cancel")); /* for saving, we allow the user to select a new filename. */
325 } else {
326 // TRANSLATORS: 40 chars max
327 app->status_line(_("Crsr: select J: jump Esc: cancel"));
329 unsigned i, page = sel / names_per_page, cur;
330 for (i = 0, cur = page * names_per_page; i < names_per_page; i++, cur++) {
331 if (cur < files.size()) { /* may not be as much filenames as it would fit on the screen */
332 app->set_color((cur == unsigned(sel)) ? GD_GDASH_YELLOW : GD_GDASH_LIGHTBLUE);
333 app->blittext_n(app->font_manager->get_font_width_narrow(), (i + 3)*yd, files[cur].c_str());
337 if (files.size() > names_per_page)
338 app->draw_scrollbar(0, sel, files.size() - 1);
340 app->screen->drawing_finished();