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"
32 #include <glib/gstdio.h>
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
> {
43 JumpToDirectoryCommand(App
*app
, SelectFileActivity
*activity
)
45 Command1Param
<std::string
>(app
),
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
> {
61 FileNameEnteredCommand(App
*app
, SelectFileActivity
*activity
)
63 Command1Param
<std::string
>(app
),
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
) {
79 AutoGFreePtr
<char> utf8(g_filename_to_utf8(filename
, -1, NULL
, NULL
, &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
) {
94 bool const s1_dir
= s1
[s1
.length() - 1] == G_DIR_SEPARATOR
;
95 bool const s2_dir
= s2
[s2
.length() - 1] == G_DIR_SEPARATOR
;
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
)
109 command_when_successful(command_when_successful
),
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
, ""))
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() {
128 jump_to_directory(start_dir
.c_str());
130 jump_to_directory(directory_of_process
);
134 SelectFileActivity::~SelectFileActivity() {
139 void SelectFileActivity::jump_to_directory(char const *jump_to
) {
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
);
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
);
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! */
159 directory
= g_get_current_dir();
163 while ((name
= g_dir_read_name(dir
)) != NULL
) {
165 /* on windows, skip hidden files? */
167 /* on unix, skip file names starting with a '.' - those are hidden files */
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\ */
175 for (int i
= 0; globs
[i
] != NULL
&& !match
; i
++)
176 if (g_pattern_match_simple(globs
[i
], name
))
179 files
.push_back(filename_to_utf8(name
));
184 /* add "directory up" if we are NOT in a root directory */
186 if (!g_str_has_suffix(directory
, ":\\")) /* root directory is "X:\" */
187 files
.push_back(std::string("..") + G_DIR_SEPARATOR_S
); /* ..\ */
189 if (!g_str_equal(directory
, "/"))
190 files
.push_back(std::string("..") + G_DIR_SEPARATOR_S
); /* ../ */
193 sort(files
.begin(), files
.end(), filename_sort
);
196 /* step back to directory where we started */
197 g_chdir(directory_of_process
);
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
{
214 SelectFileForceSaveCommand(App
*app
, SelectFileActivity
*activity
)
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
>());
240 /* not a "save file" activity, so no problem if an existing file is
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());
253 file_selected(files
[sel
].c_str());
258 void SelectFileActivity::keypress_event(KeyCode keycode
, int gfxlib_keycode
) {
262 sel
= gd_clamp(sel
- names_per_page
, 0, files
.size() - 1);
266 sel
= gd_clamp(sel
+ names_per_page
, 0, files
.size() - 1);
270 sel
= gd_clamp(sel
- 1, 0, files
.size() - 1);
274 sel
= gd_clamp(sel
+ 1, 0, files
.size() - 1);
282 sel
= files
.size() - 1;
289 /* jump to directory (name will be typed) */
292 // TRANSLATORS: 35 chars max
293 app
->input_text_and_do_command(_("Jump to directory"), directory
, new JumpToDirectoryCommand(app
, this));
295 /* enter new filename - only if saving allowed */
299 // TRANSLATORS: 35 chars max
300 app
->input_text_and_do_command(_("Enter new file name"), defaultname
.c_str(), new FileNameEnteredCommand(app
, this));
305 app
->enqueue_command(new PopActivityCommand(app
));
309 /* other keys do nothing */
315 void SelectFileActivity::redraw_event(bool full
) const {
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());
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. */
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();