1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include <cctype>
19
20#include "ScrollBarWidget.hxx"
21#include "FileListWidget.hxx"
22#include "TimerManager.hxx"
23
24#include "bspf.hxx"
25
26// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
27FileListWidget::FileListWidget(GuiObject* boss, const GUI::Font& font,
28 int x, int y, int w, int h)
29 : StringListWidget(boss, font, x, y, w, h),
30 _fsmode(FilesystemNode::ListMode::All),
31 _selected(0),
32 _quickSelectTime(0)
33{
34 // This widget is special, in that it catches signals and redirects them
35 setTarget(this);
36
37 // By default, all filenames are valid
38 _filter = [](const FilesystemNode& node) { return true; };
39}
40
41// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
42void FileListWidget::setDirectory(const FilesystemNode& node, string select)
43{
44 _node = node;
45
46 // We always want a directory listing
47 if(!_node.isDirectory() && _node.hasParent())
48 {
49 select = _node.getName();
50 _node = _node.getParent();
51 }
52
53 // Initialize history
54 FilesystemNode tmp = _node;
55 while(tmp.hasParent())
56 {
57 string name = tmp.getName();
58 if(name.back() == '/' || name.back() == '\\')
59 name.pop_back();
60 if(!BSPF::startsWithIgnoreCase(name, " ["))
61 name = " [" + name + "]";
62
63 _history.push(name);
64 tmp = tmp.getParent();
65 }
66 // History is in reverse order; we need to fix that
67 _history.reverse();
68
69 // Finally, go to this location
70 setLocation(_node, select);
71}
72
73// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
74void FileListWidget::setLocation(const FilesystemNode& node, string select)
75{
76 _node = node;
77
78 // Read in the data from the file system (start with an empty list)
79 _fileList.clear();
80 _fileList.reserve(512);
81 _node.getChildren(_fileList, _fsmode, _filter);
82
83 // Now fill the list widget with the names from the file list
84 StringList l;
85 for(const auto& file: _fileList)
86 l.push_back(file.getName());
87
88 setList(l);
89 setSelected(select);
90
91 ListWidget::recalc();
92}
93
94// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
95void FileListWidget::selectDirectory()
96{
97 _history.push(selected().getName());
98 setLocation(selected());
99}
100
101// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
102void FileListWidget::selectParent()
103{
104 if(_node.hasParent())
105 setLocation(_node.getParent(), !_history.empty() ? _history.pop() : EmptyString);
106}
107
108// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
109void FileListWidget::reload()
110{
111 if(_node.exists())
112 setLocation(_node, selected().getName());
113}
114
115// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
116bool FileListWidget::handleText(char text)
117{
118 // Quick selection mode: Go to first list item starting with this key
119 // (or a substring accumulated from the last couple key presses).
120 // Only works in a useful fashion if the list entries are sorted.
121 uInt64 time = TimerManager::getTicks() / 1000;
122 if(_quickSelectTime < time)
123 {
124 if(std::isupper(text))
125 {
126 // Select directories when the first character is uppercase
127 _quickSelectStr = " [";
128 _quickSelectStr.push_back(text);
129 }
130 else
131 _quickSelectStr = text;
132 }
133 else
134 _quickSelectStr += text;
135 _quickSelectTime = time + _QUICK_SELECT_DELAY;
136
137 int selectedItem = 0;
138 for(const auto& i: _list)
139 {
140 if(BSPF::startsWithIgnoreCase(i, _quickSelectStr))
141 break;
142 selectedItem++;
143 }
144
145 if(selectedItem > 0)
146 setSelected(selectedItem);
147
148 return true;
149}
150
151// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
152void FileListWidget::handleCommand(CommandSender* sender, int cmd, int data, int id)
153{
154 switch (cmd)
155 {
156 case ListWidget::kPrevDirCmd:
157 selectParent();
158 break;
159
160 case ListWidget::kSelectionChangedCmd:
161 _selected = data;
162 cmd = ItemChanged;
163 break;
164
165 case ListWidget::kActivatedCmd:
166 case ListWidget::kDoubleClickedCmd:
167 _selected = data;
168 if(selected().isDirectory())
169 {
170 cmd = ItemChanged;
171 selectDirectory();
172 }
173 else
174 cmd = ItemActivated;
175 break;
176
177 case ListWidget::kLongButtonPressCmd:
178 // do nothing, let boss handle this one
179 break;
180
181 default:
182 // If we don't know about the command, send it to the parent and exit
183 StringListWidget::handleCommand(sender, cmd, data, id);
184 return;
185 }
186
187 // Send command to boss, then revert to target 'this'
188 setTarget(_boss);
189 sendCommand(cmd, data, id);
190 setTarget(this);
191}
192
193// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
194uInt64 FileListWidget::_QUICK_SELECT_DELAY = 300;
195