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.
18 #include <glib/gstdio.h>
19 #include <glib/gi18n.h>
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
> {
37 JumpToDirectoryCommand(App
*app
, SelectFileActivity
*activity
)
39 Command1Param
<std::string
>(app
),
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
> {
56 FileNameEnteredCommand(App
*app
, SelectFileActivity
*activity
)
58 Command1Param
<std::string
>(app
),
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
) {
74 char *utf8
= g_filename_to_utf8(filename
, -1, NULL
, NULL
, &error
);
77 return filename
; // return with filename without conversion
79 std::string s
= utf8
; // copy to std::string (return value)
85 /* filename sort. directories on the top. */
86 static bool filename_sort(std::string
const &s1
, std::string
const &s2
) {
91 bool const s1_dir
= s1
[s1
.length()-1]==G_DIR_SEPARATOR
;
92 bool const s2_dir
= s2
[s2
.length()-1]==G_DIR_SEPARATOR
;
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
)
106 command_when_successful(command_when_successful
),
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
, ""))
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() {
126 jump_to_directory(start_dir
.c_str());
128 jump_to_directory(directory_of_process
);
132 SelectFileActivity::~SelectFileActivity() {
137 void SelectFileActivity::jump_to_directory(char const *jump_to
) {
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
);
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
);
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! */
157 directory
= g_get_current_dir();
161 while ((name
= g_dir_read_name(dir
))!=NULL
) {
163 /* on windows, skip hidden files? */
165 /* on unix, skip file names starting with a '.' - those are hidden files */
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\ */
173 for (int i
=0; globs
[i
]!=NULL
&& !match
; i
++)
174 if (g_pattern_match_simple(globs
[i
], name
))
177 files
.push_back(filename_to_utf8(name
));
182 /* add "directory up" if we are NOT in a root directory */
184 if (!g_str_has_suffix(directory
, ":\\")) /* root directory is "X:\" */
185 files
.push_back(std::string("..")+G_DIR_SEPARATOR_S
); /* ..\ */
187 if (!g_str_equal(directory
, "/"))
188 files
.push_back(std::string("..")+G_DIR_SEPARATOR_S
); /* ../ */
191 sort(files
.begin(), files
.end(), filename_sort
);
194 /* step back to directory where we started */
195 g_chdir(directory_of_process
);
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
{
212 SelectFileForceSaveCommand(App
*app
, SelectFileActivity
*activity
)
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
>());
239 /* not a "save file" activity, so no problem if an existing file is
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());
252 file_selected(files
[sel
].c_str());
257 void SelectFileActivity::keypress_event(KeyCode keycode
, int gfxlib_keycode
) {
261 sel
= gd_clamp(sel
- names_per_page
, 0, files
.size()-1);
265 sel
= gd_clamp(sel
+ names_per_page
, 0, files
.size()-1);
269 sel
= gd_clamp(sel
-1, 0, files
.size()-1);
273 sel
=gd_clamp(sel
+1, 0, files
.size()-1);
281 sel
= files
.size()-1;
288 /* jump to directory (name will be typed) */
291 // TRANSLATORS: 35 chars max
292 app
->input_text_and_do_command(_("Jump to directory"), directory
, new JumpToDirectoryCommand(app
, this));
294 /* enter new filename - only if saving allowed */
298 // TRANSLATORS: 35 chars max
299 app
->input_text_and_do_command(_("Enter new file name"), defaultname
.c_str(), new FileNameEnteredCommand(app
, this));
304 app
->enqueue_command(new PopActivityCommand(app
));
308 /* other keys do nothing */
314 void SelectFileActivity::redraw_event() {
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());
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. */
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());