20130313
[gdash.git] / src / framework / selectfileactivity.cpp
blobcad12e53d7f50e711dde09e944fcbbea6f773fd6
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include <glib.h>
18 #include <glib/gstdio.h>
19 #include <glib/gi18n.h>
20 #include <string>
21 #include <algorithm>
23 #include "framework/selectfileactivity.hpp"
24 #include "framework/commands.hpp"
25 #include "cave/helper/colors.hpp"
26 #include "gfx/fontmanager.hpp"
27 #include "gfx/screen.hpp"
28 #include "misc/util.hpp"
29 #include "misc/printf.hpp"
30 #include "misc/logger.hpp"
32 // TODO utf8-filename charset audit
35 class JumpToDirectoryCommand: public Command1Param<std::string> {
36 public:
37 JumpToDirectoryCommand(App *app, SelectFileActivity *activity)
39 Command1Param<std::string>(app),
40 directory(p1),
41 activity(activity) {
43 private:
44 std::string &directory;
45 SelectFileActivity *activity;
46 virtual void execute() {
47 char *filename_in_locale_charset = g_filename_from_utf8(directory.c_str(), -1, NULL, NULL, NULL);
48 activity->jump_to_directory(filename_in_locale_charset);
49 g_free(filename_in_locale_charset);
54 class FileNameEnteredCommand: public Command1Param<std::string> {
55 public:
56 FileNameEnteredCommand(App *app, SelectFileActivity *activity)
58 Command1Param<std::string>(app),
59 filepath(p1),
60 activity(activity) {
62 private:
63 std::string &filepath;
64 SelectFileActivity *activity;
65 virtual void execute() {
66 activity->file_selected(filepath.c_str());
71 /* returns a string which contains the utf8 representation of the filename originally in system encoding */
72 static std::string filename_to_utf8(const char *filename) {
73 GError *error=NULL;
74 char *utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, &error);
75 if (error) {
76 g_error_free(error);
77 return filename; // return with filename without conversion
79 std::string s = utf8; // copy to std::string (return value)
80 g_free(utf8);
81 return s;
85 /* filename sort. directories on the top. */
86 static bool filename_sort(std::string const &s1, std::string const &s2) {
87 if (s1.empty())
88 return true;
89 if (s2.empty())
90 return false;
91 bool const s1_dir = s1[s1.length()-1]==G_DIR_SEPARATOR;
92 bool const s2_dir = s2[s2.length()-1]==G_DIR_SEPARATOR;
93 if (s1_dir && s2_dir)
94 return s1 < s2;
95 if (s1_dir)
96 return true;
97 if (s2_dir)
98 return false;
99 return s1 < s2;
103 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)
105 Activity(app),
106 command_when_successful(command_when_successful),
107 title(title),
108 for_save(for_save),
109 defaultname(defaultname),
110 start_dir(start_dir?start_dir:"")
112 yd = app->font_manager->get_line_height();
113 names_per_page = app->screen->get_height()/yd-5;
114 if (glob == NULL || g_str_equal(glob, ""))
115 glob = "*";
116 globs = g_strsplit_set(glob, ";", -1);
118 /* remember current directory, as we step into others */
119 directory_of_process = g_get_current_dir();
120 directory = g_strdup(directory_of_process);
124 void SelectFileActivity::pushed_event() {
125 if (start_dir!="")
126 jump_to_directory(start_dir.c_str());
127 else
128 jump_to_directory(directory_of_process);
132 SelectFileActivity::~SelectFileActivity() {
133 g_strfreev(globs);
137 void SelectFileActivity::jump_to_directory(char const *jump_to) {
138 GDir *dir;
139 /* directory we are looking at, and then to the selected one (which may be relative path!) */
140 if (g_chdir(directory)==-1 || g_chdir(jump_to)==-1 || NULL == (dir = g_dir_open(".", 0, NULL))) {
141 g_chdir(directory_of_process); /* step back to directory where we started */
142 dir = g_dir_open(".", 0, NULL);
143 if (!dir) {
144 /* problem: cannot change to requested directory, and cannot change to the process
145 * original directory as well. cannot read any of them. this is critical, the
146 * selectfileactivity cannot continue, so it deletes itself. */
147 app->enqueue_command(new PopActivityCommand(app));
148 app->show_message(SPrintf(_("Cannot change to directory: %s.")) % jump_to, SPrintf("Jumped back to directory: %s.") % directory_of_process);
149 } else {
150 /* cannot read the new directory, but managed to jump back to the original. issue
151 * an error then continue in the original dir. */
152 app->show_message(SPrintf(_("Cannot change to directory: %s.")) % jump_to, SPrintf("Jumped back to directory: %s.") % directory_of_process);
155 /* now get the directory we have stepped into, and it is readable as well. remember! */
156 g_free(directory);
157 directory = g_get_current_dir();
159 files.clear();
160 char const *name;
161 while ((name = g_dir_read_name(dir))!=NULL) {
162 #ifdef G_OS_WIN32
163 /* on windows, skip hidden files? */
164 #else
165 /* on unix, skip file names starting with a '.' - those are hidden files */
166 if (name[0]=='.')
167 continue;
168 #endif
169 if (g_file_test(name, G_FILE_TEST_IS_DIR))
170 files.push_back(std::string(name) + G_DIR_SEPARATOR_S); /* dirname/ or dirname\ */
171 else {
172 bool match = false;
173 for (int i=0; globs[i]!=NULL && !match; i++)
174 if (g_pattern_match_simple(globs[i], name))
175 match=true;
176 if (match)
177 files.push_back(filename_to_utf8(name));
180 g_dir_close(dir);
182 /* add "directory up" if we are NOT in a root directory */
183 #ifdef G_OS_WIN32
184 if (!g_str_has_suffix(directory, ":\\")) /* root directory is "X:\" */
185 files.push_back(std::string("..")+G_DIR_SEPARATOR_S); /* ..\ */
186 #else
187 if (!g_str_equal(directory, "/"))
188 files.push_back(std::string("..")+G_DIR_SEPARATOR_S); /* ../ */
189 #endif
190 /* sort the array */
191 sort(files.begin(), files.end(), filename_sort);
192 sel = 0;
194 /* step back to directory where we started */
195 g_chdir(directory_of_process);
197 redraw_event();
201 void SelectFileActivity::file_selected_do_command() {
202 app->enqueue_command(command_when_successful);
203 app->enqueue_command(new PopActivityCommand(app));
207 /** This command forces the SelectFileActivity object to do the save.
208 * It can be used as a command to be processed after a "do you really want to save"
209 * question shown by an AskYesNoActivity. . */
210 class SelectFileForceSaveCommand: public Command {
211 public:
212 SelectFileForceSaveCommand(App *app, SelectFileActivity *activity)
213 : Command(app),
214 activity(activity) {
216 private:
217 SelectFileActivity *activity;
218 virtual void execute() {
219 activity->file_selected_do_command();
224 void SelectFileActivity::file_selected(char const *filename) {
225 /* ok so set the filename in the pending command object. */
226 char *result_filename = g_build_path(G_DIR_SEPARATOR_S, directory, filename, NULL);
227 command_when_successful->set_param1(result_filename);
228 g_free(result_filename);
230 /* a file is selected, so enqueue the command. */
231 /* but first - check if it is an overwrite! if the user does not allow the overwriting, we
232 * should do nothing. so create an AskYesNoActivity, which will call the
233 * file_selected_do_command() method on the SelectFileActivity, when the user
234 * accepts the overwrite. */
235 if (for_save && g_file_test(filename, G_FILE_TEST_EXISTS)) {
236 /* ask the overwrite. */
237 app->ask_yesorno_and_do_command(_("File exists. Overwrite?"), "Yes", "No", new SelectFileForceSaveCommand(app, this), SmartPtr<Command>());
238 } else {
239 /* not a "save file" activity, so no problem if an existing file is
240 * selected. */
241 file_selected_do_command();
246 void SelectFileActivity::process_enter() {
247 if (g_str_has_suffix(files[sel].c_str(), G_DIR_SEPARATOR_S)) {
248 /* directory selected */
249 jump_to_directory(files[sel].c_str());
250 } else {
251 /* file selected */
252 file_selected(files[sel].c_str());
257 void SelectFileActivity::keypress_event(KeyCode keycode, int gfxlib_keycode) {
258 switch (keycode) {
259 /* movements */
260 case App::PageUp:
261 sel = gd_clamp(sel - names_per_page, 0, files.size()-1);
262 redraw_event();
263 break;
264 case App::PageDown:
265 sel = gd_clamp(sel + names_per_page, 0, files.size()-1);
266 redraw_event();
267 break;
268 case App::Up:
269 sel = gd_clamp(sel-1, 0, files.size()-1);
270 redraw_event();
271 break;
272 case App::Down:
273 sel=gd_clamp(sel+1, 0, files.size()-1);
274 redraw_event();
275 break;
276 case App::Home:
277 sel = 0;
278 redraw_event();
279 break;
280 case App::End:
281 sel = files.size()-1;
282 redraw_event();
283 break;
284 case App::Enter:
285 process_enter();
286 break;
288 /* jump to directory (name will be typed) */
289 case 'j':
290 case 'J':
291 // TRANSLATORS: 35 chars max
292 app->input_text_and_do_command(_("Jump to directory"), directory, new JumpToDirectoryCommand(app, this));
293 break;
294 /* enter new filename - only if saving allowed */
295 case 'n':
296 case 'N':
297 if (for_save) {
298 // TRANSLATORS: 35 chars max
299 app->input_text_and_do_command(_("Enter new file name"), defaultname.c_str(), new FileNameEnteredCommand(app, this));
301 break;
303 case App::Escape:
304 app->enqueue_command(new PopActivityCommand(app));
305 break;
307 default:
308 /* other keys do nothing */
309 break;
314 void SelectFileActivity::redraw_event() {
315 app->clear_screen();
317 /* show current directory */
318 app->title_line(title.c_str());
319 app->set_color(GD_GDASH_YELLOW);
320 app->blittext_n(-1, 1*yd, filename_to_utf8(directory).c_str());
321 if (for_save) {
322 // TRANSLATORS: 40 chars max
323 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==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 app->screen->flip();