tui: improve mode selection
[fanfix.git] / src / be / nikiroo / fanfix / reader / tui / TuiReaderMainWindow.java
1 package be.nikiroo.fanfix.reader.tui;
2
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.List;
6
7 import jexer.TAction;
8 import jexer.TComboBox;
9 import jexer.TCommand;
10 import jexer.TField;
11 import jexer.TFileOpenBox.Type;
12 import jexer.TKeypress;
13 import jexer.TLabel;
14 import jexer.TList;
15 import jexer.TStatusBar;
16 import jexer.TWindow;
17 import jexer.event.TCommandEvent;
18 import jexer.event.TKeypressEvent;
19 import jexer.event.TMenuEvent;
20 import jexer.event.TResizeEvent;
21 import be.nikiroo.fanfix.Instance;
22 import be.nikiroo.fanfix.data.MetaData;
23 import be.nikiroo.fanfix.library.BasicLibrary;
24 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
25 import be.nikiroo.fanfix.reader.Reader;
26
27 /**
28 * The library window, that will list all the (filtered) stories available in
29 * this {@link BasicLibrary}.
30 *
31 * @author niki
32 */
33 class TuiReaderMainWindow extends TWindow {
34 public static final int MENU_SEARCH = 1100;
35 public static final TCommand CMD_SEARCH = new TCommand(MENU_SEARCH) {
36 };
37
38 public enum Mode {
39 SOURCE, AUTHOR,
40 }
41
42 private TList list;
43 private List<MetaData> listKeys;
44 private List<String> listItems;
45 private Reader reader;
46
47 private Mode mode = Mode.SOURCE;
48 private String target = null;
49 private String filter = "";
50
51 private List<TSizeConstraint> sizeConstraints = new ArrayList<TSizeConstraint>();
52
53 // TODO: because no way to find out the current index!!
54 private TComboBox select;
55 private TComboBox option;
56
57 /**
58 * Create a new {@link TuiReaderMainWindow} without any stories in the list.
59 *
60 * @param reader
61 * the reader and main application
62 */
63 public TuiReaderMainWindow(TuiReaderApplication reader) {
64 // Construct a demo window. X and Y don't matter because it will be
65 // centred on screen.
66 super(reader, "Library", 0, 0, 60, 18, CENTERED | RESIZABLE);
67
68 this.reader = reader;
69
70 listKeys = new ArrayList<MetaData>();
71 listItems = new ArrayList<String>();
72
73 addSearch();
74 addList();
75 addSelect(); // TODO: last so it can draw over the rest
76
77 TStatusBar statusBar = reader.setStatusBar(this, "Library");
78 statusBar.addShortcutKeypress(TKeypress.kbCtrlF, CMD_SEARCH, "Search");
79
80 TSizeConstraint.resize(sizeConstraints);
81
82 // TODO: remove when not used anymore
83
84 // addLabel("Label (1,1)", 1, 1);
85 // addButton("&Button (35,1)", 35, 1, new TAction() {
86 // public void DO() {
87 // }
88 // });
89 // addCheckbox(1, 2, "Checky (1,2)", false);
90 // addProgressBar(1, 3, 30, 42);
91 // TRadioGroup groupy = addRadioGroup(1, 4, "Radio groupy");
92 // groupy.addRadioButton("Fanfan");
93 // groupy.addRadioButton("Tulipe");
94 // addField(1, 10, 20, false, "text not fixed.");
95 // addField(1, 11, 20, true, "text fixed.");
96 // addText("20x4 Text in (12,20)", 1, 12, 20, 4);
97 //
98 // TTreeView tree = addTreeView(30, 5, 20, 5);
99 // TTreeItem root = new TTreeItem(tree, "expended root", true);
100 // tree.setSelected(root); // needed to allow arrow navigation without
101 // // mouse-clicking before
102 //
103 // root.addChild("child");
104 // root.addChild("child 2").addChild("sub child");
105 }
106
107 private void addSearch() {
108 TLabel lblSearch = addLabel("Search: ", 0, 0);
109
110 TField search = new TField(this, 0, 0, 1, true) {
111 @Override
112 public void onKeypress(TKeypressEvent keypress) {
113 super.onKeypress(keypress);
114 TKeypress key = keypress.getKey();
115 if (key.isFnKey() && key.getKeyCode() == TKeypress.ENTER) {
116 TuiReaderMainWindow.this.filter = getText();
117 TuiReaderMainWindow.this.refreshStories();
118 }
119 }
120 };
121
122 TSizeConstraint.setSize(sizeConstraints, lblSearch, 5, 1, null, null);
123 TSizeConstraint.setSize(sizeConstraints, search, 15, 1, -5, null);
124 }
125
126 private void addList() {
127 list = addList(listItems, 0, 0, 10, 10, new TAction() {
128 @Override
129 public void DO() {
130 MetaData meta = getSelectedMeta();
131 if (meta != null) {
132 readStory(meta);
133 }
134 }
135 });
136
137 TSizeConstraint.setSize(sizeConstraints, list, 0, 7, 0, 0);
138 }
139
140 private void addSelect() {
141 // TODO: make a full list
142 final List<String> options = new ArrayList<String>();
143 options.add("(show all)");
144 options.add("Sources");
145 options.add("Author");
146
147 // TODO
148 final List<String> selects = new ArrayList<String>();
149 selects.add("(show all)");
150 for (String source : reader.getLibrary().getSources()) {
151 selects.add(source);
152 }
153
154 TLabel lblSelect = addLabel("Select: ", 0, 0);
155
156 // TODO: why must be last so to be able to draw over the rest
157 // TODO: make it so we cannot add manual entries
158 // TODO: how to select the item via keyboard? why double-click via
159 // mouse?
160 // TODO: how to change the values size on resize?
161 // TODO: setWidth() does not impact the display width, only the control
162 // and the down arrow on the right
163 // TODO: width 1 +resize + click on down arrow = bad format exception
164 select = addComboBox(0, 0, 10, selects, 0,
165 Math.min(selects.size() + 1, getHeight() - 1 - 1),
166 new TAction() {
167 @Override
168 public void DO() {
169 // TODO: detect (show all)
170 if (select.getText().equals("(show all)")) {
171 setMode(mode, null);
172 } else {
173 setMode(mode, select.getText());
174 }
175 }
176 });
177
178 option = addComboBox(0, 0, 10, options, 0,
179 Math.min(selects.size() + 1, getHeight() - 1 - 1),
180 new TAction() {
181 @Override
182 public void DO() {
183 // TODO clear not working!!
184
185 String smode = option.getText();
186 Mode mode;
187 if (smode == null || smode.equals("(show all)")) {
188 mode = null;
189 select.setVisible(false);
190 select.setEnabled(false);
191 } else if (smode.equals("Sources")) {
192 mode = Mode.SOURCE;
193 select.setVisible(true);
194 select.setEnabled(true);
195 selects.clear();
196 selects.add("(show all)");
197 for (String source : reader.getLibrary()
198 .getSources()) {
199 selects.add(source);
200 }
201 } else {
202 mode = Mode.AUTHOR;
203 select.setVisible(true);
204 select.setEnabled(true);
205 selects.clear();
206 selects.add("(show all)");
207 for (String author : reader.getLibrary()
208 .getAuthors()) {
209 selects.add(author);
210 }
211 }
212
213 // TODO: detect (show all)
214 if (select.getText().equals("(show all)")) {
215 setMode(mode, null);
216 } else {
217 setMode(mode, select.getText());
218 }
219 }
220 });
221
222 TSizeConstraint.setSize(sizeConstraints, lblSelect, 5, 3, null, null);
223 TSizeConstraint.setSize(sizeConstraints, option, 15, 3, -5, null);
224 TSizeConstraint.setSize(sizeConstraints, select, 15, 4, -5, null);
225 }
226
227 @Override
228 public void onResize(TResizeEvent resize) {
229 super.onResize(resize);
230 TSizeConstraint.resize(sizeConstraints);
231 }
232
233 @Override
234 public void onClose() {
235 setVisible(false);
236 super.onClose();
237 }
238
239 /**
240 * Refresh the list of stories displayed in this library.
241 * <p>
242 * Will take the current settings into account (filter, source...).
243 */
244 public void refreshStories() {
245 List<MetaData> metas;
246 if (mode == Mode.SOURCE) {
247 metas = reader.getLibrary().getListBySource(target);
248 } else if (mode == Mode.AUTHOR) {
249 metas = reader.getLibrary().getListByAuthor(target);
250 } else {
251 metas = reader.getLibrary().getList();
252 }
253
254 setMetas(metas);
255 }
256
257 /**
258 * Change the author/source filter and display all stories matching this
259 * target.
260 *
261 * @param mode
262 * the new mode or NULL for no sorting
263 * @param target
264 * the actual target for the given mode, or NULL for all of them
265 */
266 public void setMode(Mode mode, String target) {
267 this.mode = mode;
268 this.target = target;
269 refreshStories();
270 }
271
272 /**
273 * Update the list of stories displayed in this {@link TWindow}.
274 * <p>
275 * If a filter is set, only the stories which pass the filter will be
276 * displayed.
277 *
278 * @param metas
279 * the new list of stories to display
280 */
281 private void setMetas(List<MetaData> metas) {
282 listKeys.clear();
283 listItems.clear();
284
285 if (metas != null) {
286 for (MetaData meta : metas) {
287 String desc = desc(meta);
288 if (filter.isEmpty()
289 || desc.toLowerCase().contains(filter.toLowerCase())) {
290 listKeys.add(meta);
291 listItems.add(desc);
292 }
293 }
294 }
295
296 list.setList(listItems);
297 }
298
299 public MetaData getSelectedMeta() {
300 if (list.getSelectedIndex() >= 0) {
301 return listKeys.get(list.getSelectedIndex());
302 }
303
304 return null;
305 }
306
307 public void readStory(MetaData meta) {
308 try {
309 reader.setChapter(-1);
310 reader.setMeta(meta);
311 reader.read();
312 } catch (IOException e) {
313 Instance.getTraceHandler().error(e);
314 }
315 }
316
317 private String desc(MetaData meta) {
318 return String.format("%5s: %s", meta.getLuid(), meta.getTitle());
319 }
320
321 @Override
322 public void onCommand(TCommandEvent command) {
323 if (command.getCmd().equals(TuiReaderApplication.CMD_EXIT)) {
324 TuiReaderApplication.close(this);
325 } else {
326 // Handle our own event if needed here
327 super.onCommand(command);
328 }
329 }
330
331 @Override
332 public void onMenu(TMenuEvent menu) {
333 MetaData meta = getSelectedMeta();
334 if (meta != null) {
335 switch (menu.getId()) {
336 case TuiReaderApplication.MENU_OPEN:
337 readStory(meta);
338
339 return;
340 case TuiReaderApplication.MENU_EXPORT:
341
342 try {
343 // TODO: choose type, pg, error
344 OutputType outputType = OutputType.EPUB;
345 String path = fileOpenBox(".", Type.SAVE);
346 reader.getLibrary().export(meta.getLuid(), outputType,
347 path, null);
348 } catch (IOException e) {
349 // TODO
350 e.printStackTrace();
351 }
352
353 return;
354
355 case -1:
356 try {
357 reader.getLibrary().delete(meta.getLuid());
358 } catch (IOException e) {
359 // TODO
360 }
361
362 return;
363 }
364 }
365
366 super.onMenu(menu);
367 }
368 }