Merge remote-tracking branch 'upstream/edge' into upstream-master
[clinton/Smoothieware.git] / src / modules / utils / panel / screens / FileScreen.cpp
1 /*
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 #include "libs/Kernel.h"
9 #include "Panel.h"
10 #include "PanelScreen.h"
11 #include "LcdBase.h"
12 #include "MainMenuScreen.h"
13 #include "FileScreen.h"
14 #include "libs/nuts_bolts.h"
15 #include "libs/utils.h"
16 #include <string>
17 #include "libs/SerialMessage.h"
18 #include "StreamOutput.h"
19 #include "DirHandle.h"
20 #include "mri.h"
21
22 using std::string;
23
24 FileScreen::FileScreen()
25 {
26 this->start_play = false;
27 }
28
29 // When entering this screen
30 void FileScreen::on_enter()
31 {
32 THEPANEL->lcd->clear();
33
34 // Default folder to enter
35 this->enter_folder(THEKERNEL->current_path.c_str());
36 }
37
38 void FileScreen::on_exit()
39 {
40 // reset to root directory, I think this is less confusing
41 THEKERNEL->current_path= "/";
42 }
43
44 // For every ( potential ) refresh of the screen
45 void FileScreen::on_refresh()
46 {
47 if ( THEPANEL->menu_change() ) {
48 this->refresh_menu();
49 }
50 if ( THEPANEL->click() ) {
51 this->clicked_line(THEPANEL->get_menu_current_line());
52 }
53 }
54
55 // Enter a new folder
56 void FileScreen::enter_folder(const char *folder)
57 {
58 // Remember where we are
59 THEKERNEL->current_path= folder;
60
61 // We need the number of lines to setup the menu
62 uint16_t number_of_files_in_folder = this->count_folder_content();
63
64 // Setup menu
65 THEPANEL->setup_menu(number_of_files_in_folder + 1); // same number of files as menu items
66 THEPANEL->enter_menu_mode();
67
68 // Display menu
69 this->refresh_menu();
70 }
71
72 // Called by the panel when refreshing the menu, display .. then all files in the current dir
73 void FileScreen::display_menu_line(uint16_t line)
74 {
75 if ( line == 0 ) {
76 THEPANEL->lcd->printf("..");
77 } else {
78 bool isdir;
79 string fn= this->file_at(line - 1, isdir).substr(0, 18);
80 if(isdir) {
81 if(fn.size() >= 18) fn.back()= '/';
82 else fn.append("/");
83 }
84 THEPANEL->lcd->printf("%s", fn.c_str());
85 }
86 }
87
88 // When a line is clicked in the menu, act
89 void FileScreen::clicked_line(uint16_t line)
90 {
91 if ( line == 0 ) {
92 string path= THEKERNEL->current_path;
93 if(path == "/") {
94 // Exit file navigation
95 THEPANEL->enter_screen(this->parent);
96 } else {
97 // Go up one folder
98 path = path.substr(0, path.find_last_of('/'));
99 if (path.empty()) {
100 path= "/";
101 }
102 this->enter_folder(path.c_str());
103 }
104 } else {
105 // Enter file or dir
106 bool isdir;
107 string path= absolute_from_relative(this->file_at(line - 1, isdir));
108 if(isdir) {
109 this->enter_folder(path.c_str());
110 return;
111 }
112
113 // start printing that file...
114 this->play_path = path;
115 this->start_play = true;
116 }
117 }
118
119 // only filter files that have a .g, .ngc or .nc in them and does not start with a .
120 bool FileScreen::filter_file(const char *f)
121 {
122 string fn= lc(f);
123 return (fn.at(0) != '.') &&
124 ((fn.find(".g") != string::npos) ||
125 (fn.find(".ngc") != string::npos) ||
126 (fn.find(".nc") != string::npos));
127 }
128
129 // Find the "line"th file in the current folder
130 string FileScreen::file_at(uint16_t line, bool& isdir)
131 {
132 DIR *d;
133 struct dirent *p;
134 uint16_t count = 0;
135 d = opendir(THEKERNEL->current_path.c_str());
136 if (d != NULL) {
137 while ((p = readdir(d)) != NULL) {
138 // only filter files that have a .g in them and directories not starting with a .
139 if(((p->d_isdir && p->d_name[0] != '.') || filter_file(p->d_name)) && count++ == line ) {
140 isdir= p->d_isdir;
141 string fn= p->d_name;
142 closedir(d);
143 return fn;
144 }
145 }
146 }
147
148 if (d != NULL) closedir(d);
149 isdir= false;
150 return "";
151 }
152
153 // Count how many files there are in the current folder that have a .g in them and does not start with a .
154 uint16_t FileScreen::count_folder_content()
155 {
156 DIR *d;
157 struct dirent *p;
158 uint16_t count = 0;
159 d = opendir(THEKERNEL->current_path.c_str());
160 if (d != NULL) {
161 while ((p = readdir(d)) != NULL) {
162 if((p->d_isdir && p->d_name[0] != '.') || filter_file(p->d_name)) count++;
163 }
164 closedir(d);
165 return count;
166 }
167 return 0;
168 }
169
170 void FileScreen::on_main_loop()
171 {
172 if (this->start_play) {
173 this->start_play = false;
174 THEPANEL->set_playing_file(this->play_path);
175 this->play(this->play_path.c_str());
176 THEPANEL->enter_screen(this->parent);
177 return;
178 }
179 }
180
181 void FileScreen::play(const char *path)
182 {
183 struct SerialMessage message;
184 message.message = string("play ") + path;
185 message.stream = &(StreamOutput::NullStream);
186 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );
187 }