1 #include "ExplorerView.h"
2
3 #include <algorithm>
4 #include <utility>
5
6 #include <scx/CharsetHelper.h>
7 #include <scx/Env.h>
8 #include <scx/FileInfo.h>
9 #include <scx/Dir.h>
10 #include <scx/PinYinCompare.h>
11
12 #include "AppEnv.h"
13
14 using namespace std;
15 using namespace scx;
16
17 constexpr const char* const STR_TITLE = "[ Explorer ]";
18 constexpr const char* const SIZE_HINT = "BKMG";
19
ExplorerView()20 ExplorerView::ExplorerView()
21 {
22 //const AppEnv* config = GlobalAppEnv::Instance();
23
24 m_BeginStack = { 0 };
25 m_SelectionStack = { 0 };
26 m_Path = Env::Get("HOME");
27
28 BuildFileItems();
29 }
30
~ExplorerView()31 ExplorerView::~ExplorerView()
32 {
33 }
34
Refresh()35 void ExplorerView::Refresh()
36 {
37 using namespace CharsetHelper;
38 using namespace ncurses;
39
40 d.ColorOn(ncurses::Color::White, ncurses::Color::Black);
41 d.Clear();
42
43 if (m_Focused)
44 d.AttrOn(ncurses::Attr::Bold);
45 d.CenterPrint(0, STR_TITLE);
46 d.ResetAttrColor();
47
48 // content
49 // { {name~~~size }#}
50 // { {foo~~~1023K }#}
51 const int w = d.w - 2;
52 const int h = d.h - 2;
53 const int x = 1;
54 const int y = 1;
55 int xoff = x;
56 int yoff = y;
57
58 const int wText = w - 2;
59 const int hText = h - 1;
60
61 const int wSize = 5 + 1;
62 const int wPath = wText - wSize;
63
64 const int begin = m_BeginStack.back();
65 const int selection = m_SelectionStack.back();
66 if (!m_FileItems.empty()) {
67 int lcount = std::min(hText, (int)(m_FileItems.size()-begin));
68 for (int l = 0; l < lcount; ++l) {
69 int index = begin + l;
70 const FileItem& item = m_FileItems[index];
71
72 int pathNormalAttr = Attr::Normal;
73 int boldAttr = Attr::Bold;
74 int pathDirColorF = Color::Blue;
75 int pathRegColorF = Color::White;
76 int pathColorB = Color::Black;
77 int sizeColorF = Color::Magenta;
78 int sizeColorB = Color::Black;
79
80 if (index == selection) {
81 boldAttr = Attr::Normal;
82 pathRegColorF = sizeColorF = Color::Black;
83 pathColorB = sizeColorB = Color::White;
84
85 d.AttrSet(Attr::Normal | Attr::Reverse);
86 d.ColorOn(Color::White, Color::Black);
87 d.Print(x, yoff+l, string(w-1, ' '));
88 }
89
90 xoff = x + 1;
91 if (item.isDir) {
92 d.AttrSet(boldAttr);
93 d.ColorOn(pathDirColorF, pathColorB);
94 } else {
95 d.AttrSet(pathNormalAttr);
96 d.ColorOn(pathRegColorF, pathColorB);
97 }
98
99 if (!item.cacheOk) {
100 item.nameCache = MBStrWidth(item.name) <= wPath-1 ?
101 item.name : MBWidthStr(item.name, wPath-1-3) + "...";
102 }
103 d.Print(xoff, yoff+l, item.nameCache);
104 xoff += wPath;
105
106 const char* hint = SIZE_HINT;
107 off_t size = item.size;
108 for (int i = 0; i < 3; ++i, ++hint) {
109 off_t s = size / 1024;
110 if (s <= 0)
111 break;
112 size = s;
113 }
114 if (!item.cacheOk) {
115 string& str = item.sizeCache;
116 str = std::to_string(size) + *hint;
117 if (str.size() < 5)
118 str = string(5 - str.size(), ' ') + str;
119 }
120
121 d.AttrSet(boldAttr);
122 d.ColorOn(sizeColorF, sizeColorB);
123 d.Print(xoff, yoff+l, item.sizeCache);
124 xoff += wSize;
125
126 item.cacheOk = true;
127 }
128
129 xoff = x + 1 + wText;
130 if (m_FileItems.size() > (size_t)hText) {
131 double percent = (double)(selection+1) / m_FileItems.size() - 0.00001f;
132 yoff = y + hText*percent;
133 d.AttrSet(Attr::Bold | Attr::Reverse);
134 d.ColorOn(Color::Green, Color::Black);
135 d.Print(xoff, yoff, " ");
136 }
137
138 }
139
140 // status bar
141 if (m_PathCache.empty()) {
142 m_PathCache = m_Path;
143 if (MBStrWidth(m_PathCache) > wText) {
144 do {
145 m_PathCache = MBSubStr(m_PathCache, MBStrLen(m_PathCache)-1, 1);
146 } while (MBStrWidth(m_PathCache) > (wText - 3));
147 m_PathCache.insert(0, "...");
148 }
149 }
150 xoff = x + 1;
151 yoff = y + hText;
152 d.AttrSet(Attr::Bold);
153 d.ColorOn(Color::White, Color::Black);
154 d.Print(xoff, yoff, m_PathCache);
155
156 d.ResetAttrColor();
157
158 d.Refresh();
159 }
160
MoveTo(int x,int y)161 void ExplorerView::MoveTo(int x, int y)
162 {
163 d.MoveTo(x, y);
164 }
165
Resize(int w,int h)166 void ExplorerView::Resize(int w, int h)
167 {
168 // invalidate cache
169 m_PathCache.clear();
170 for (auto& item: m_FileItems) {
171 item.cacheOk = false;
172 }
173
174 d.Resize(w, h);
175 d.EnableKeypad(true);
176 }
177
InjectKey(int key)178 bool ExplorerView::InjectKey(int key)
179 {
180 switch (key) {
181 case KEY_LEFT:
182 case 'h':
183 CdUp();
184 break;
185
186 case KEY_RIGHT:
187 case 'l':
188 if (!m_FileItems.empty()) {
189 CdIn();
190 }
191 break;
192
193 case KEY_DOWN:
194 case 'j':
195 if (!m_FileItems.empty()) {
196 ScrollDown();
197 }
198 break;
199
200 case KEY_UP:
201 case 'k':
202 if (!m_FileItems.empty()) {
203 ScrollUp();
204 }
205 break;
206
207 case KEY_NPAGE:
208 if (!m_FileItems.empty()) {
209 int line= (d.h - 3) / 2;
210 for (int i = 0; i < line; ++i)
211 ScrollDown();
212 }
213 break;
214
215 case KEY_PPAGE:
216 if (!m_FileItems.empty()) {
217 int line= (d.h - 3) / 2;
218 for (int i = 0; i < line; ++i)
219 ScrollUp();
220 }
221 break;
222
223 case KEY_HOME:
224 if (!m_FileItems.empty()) {
225 m_BeginStack.back() = 0;
226 m_SelectionStack.back() = 0;
227 }
228 break;
229
230 case KEY_END:
231 if (!m_FileItems.empty()) {
232 m_SelectionStack.back() = m_FileItems.size() - 1;
233 m_BeginStack.back() = std::max((int)m_FileItems.size() - (d.h - 3), 0);
234 }
235 break;
236
237 case 'a':
238 if (!m_FileItems.empty()) {
239 int sel = m_SelectionStack.back();
240 if (!m_FileItems[sel].isDir)
241 SigUserOpen(m_Path + '/' + m_FileItems[sel].name);
242 }
243 return true;
244
245 case '\n':
246 if (!m_FileItems.empty()) {
247 int sel = m_SelectionStack.back();
248 if (!m_FileItems[sel].isDir) {
249 SigTmpOpen(m_Path + '/' + m_FileItems[sel].name);
250 return true;
251 } else {
252 CdIn();
253 }
254 }
255 break;
256
257 case '/':
258 if (!m_FileItems.empty()) {
259 }
260 break;
261
262 case '.':
263 m_BeginStack = { 0 };
264 m_SelectionStack = { 0 };
265 m_HideDot = !m_HideDot;
266 BuildFileItems();
267 break;
268
269 case 's':
270 m_BeginStack = { 0 };
271 m_SelectionStack = { 0 };
272 m_HideUnknown = !m_HideUnknown;
273 BuildFileItems();
274 break;
275
276 case 'r':
277 m_BeginStack = { 0 };
278 m_SelectionStack = { 0 };
279 BuildFileItems();
280 break;
281
282 default:
283 return false;
284 }
285
286 Refresh();
287 return true;
288 }
289
Show(bool show)290 void ExplorerView::Show(bool show)
291 {
292 d.Show(show);
293 }
294
IsShown() const295 bool ExplorerView::IsShown() const
296 {
297 return d.shown;
298 }
299
SetFocus(bool focus)300 void ExplorerView::SetFocus(bool focus)
301 {
302 m_Focused = focus;
303 }
304
HasFocus() const305 bool ExplorerView::HasFocus() const
306 {
307 return m_Focused;
308 }
309
AddSuffixes(const std::vector<std::string> & list)310 void ExplorerView::AddSuffixes(const std::vector<std::string>& list)
311 {
312 for (const string& ext: list) {
313 m_Suffixes.insert(ext);
314 }
315 }
316
BuildFileItems()317 void ExplorerView::BuildFileItems()
318 {
319 m_FileItems.clear();
320
321 const vector<string>& files = Dir::ListDir(m_Path);
322
323 std::vector<FileItem>& dirItems = m_FileItems;
324 dirItems.reserve(files.size());
325 std::vector<FileItem> otherItems;
326 otherItems.reserve(files.size());
327
328 for (const string& file: files) {
329 if (file == "." || file == "..")
330 continue;
331 if (m_HideDot && file[0] == '.')
332 continue;
333
334 FileInfo info(m_Path + "/" + file);
335
336 if (m_HideUnknown && (info.Type() != FileType::Directory)) {
337 if (m_Suffixes.find(info.Suffix()) == m_Suffixes.end())
338 continue;
339 }
340
341 FileItem item;
342 item.name = file;
343 item.isDir = info.Type() == FileType::Directory;
344 item.size = info.Size();
345 item.cacheOk = false;
346
347 if (item.isDir)
348 dirItems.push_back(std::move(item));
349 else
350 otherItems.push_back(std::move(item));
351 }
352
353 PinYinCompare pyc;
354 auto cmp = [&pyc](const FileItem& a, const FileItem& b) {
355 return pyc.CmpUtf8(a.name, b.name);
356 };
357 std::sort(dirItems.begin(), dirItems.end(), cmp);
358 std::sort(otherItems.begin(), otherItems.end(), cmp);
359
360 m_FileItems.insert(m_FileItems.end(), otherItems.begin(), otherItems.end());
361 }
362
CdUp()363 void ExplorerView::CdUp()
364 {
365 if (m_BeginStack.size() > 1) {
366 m_BeginStack.pop_back();
367 m_SelectionStack.pop_back();
368 } else {
369 m_BeginStack.back() = 0;
370 m_SelectionStack.back() = 0;
371 }
372
373 m_Path = FileInfo(m_Path).AbsPath();
374 m_PathCache.clear();
375 BuildFileItems();
376 }
377
CdIn()378 void ExplorerView::CdIn()
379 {
380 int sel = m_SelectionStack.back();
381 if (m_FileItems[sel].isDir) {
382 m_Path += (m_Path != "/" ? "/" : "") + m_FileItems[sel].name;
383 m_PathCache.clear();
384 BuildFileItems();
385
386 m_BeginStack.push_back(0);
387 m_SelectionStack.push_back(0);
388 }
389 }
390
ScrollDown()391 void ExplorerView::ScrollDown()
392 {
393 int& beg = m_BeginStack.back();
394 int& sel = m_SelectionStack.back();
395 if (sel < (int)m_FileItems.size()-1) {
396 ++sel;
397 }
398 if (sel > (d.h-2) / 2
399 && beg < (int)m_FileItems.size()-(d.h-2-1)) {
400 ++beg;
401 }
402 }
403
ScrollUp()404 void ExplorerView::ScrollUp()
405 {
406 int& beg = m_BeginStack.back();
407 int& sel = m_SelectionStack.back();
408 if (sel > 0) {
409 --sel;
410 }
411 if (sel < beg + (d.h-2) / 2
412 && beg > 0) {
413 --beg;
414 }
415 }
416