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