working on ui refresh for books
[fanfix.git] / src / be / nikiroo / fanfix_swing / gui / BooksPanel.java
1 package be.nikiroo.fanfix_swing.gui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Image;
6 import java.awt.Point;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.event.MouseAdapter;
10 import java.awt.event.MouseEvent;
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.concurrent.ExecutionException;
16
17 import javax.swing.DefaultListModel;
18 import javax.swing.JList;
19 import javax.swing.JPopupMenu;
20 import javax.swing.ListCellRenderer;
21 import javax.swing.ListSelectionModel;
22 import javax.swing.SwingWorker;
23
24 import be.nikiroo.fanfix.Instance;
25 import be.nikiroo.fanfix.data.MetaData;
26 import be.nikiroo.fanfix.library.BasicLibrary;
27 import be.nikiroo.fanfix_swing.Actions;
28 import be.nikiroo.fanfix_swing.gui.book.BookBlock;
29 import be.nikiroo.fanfix_swing.gui.book.BookInfo;
30 import be.nikiroo.fanfix_swing.gui.book.BookLine;
31 import be.nikiroo.fanfix_swing.gui.book.BookPopup;
32 import be.nikiroo.fanfix_swing.gui.utils.DelayWorker;
33 import be.nikiroo.fanfix_swing.gui.utils.ListenerPanel;
34 import be.nikiroo.fanfix_swing.gui.utils.UiHelper;
35
36 public class BooksPanel extends ListenerPanel {
37 private class ListModel extends DefaultListModel<BookInfo> {
38 public void fireElementChanged(BookInfo element) {
39 int index = indexOf(element);
40 if (index >= 0) {
41 fireContentsChanged(element, index, index);
42 }
43 }
44 }
45
46 static public final String INVALIDATE_CACHE = "invalidate_cache";
47
48 private List<BookInfo> bookInfos = new ArrayList<BookInfo>();
49 private Map<BookInfo, BookLine> books = new HashMap<BookInfo, BookLine>();
50 private boolean seeWordCount;
51 private boolean listMode;
52
53 private JList<BookInfo> list;
54 private int hoveredIndex = -1;
55 private ListModel data = new ListModel();
56 private DelayWorker bookCoverUpdater;
57
58 private SearchBar searchBar;
59
60 public BooksPanel(boolean listMode) {
61 setLayout(new BorderLayout());
62
63 searchBar = new SearchBar();
64 add(searchBar, BorderLayout.NORTH);
65
66 searchBar.addActionListener(new ActionListener() {
67 @Override
68 public void actionPerformed(ActionEvent e) {
69 filter(searchBar.getText());
70 }
71 });
72
73 bookCoverUpdater = new DelayWorker(20);
74 bookCoverUpdater.start();
75 add(UiHelper.scroll(initList(listMode)), BorderLayout.CENTER);
76 }
77
78 // null or empty -> all sources
79 // sources hierarchy supported ("source/" will includes all "source" and
80 // "source/*")
81 public void load(final List<String> sources, final List<String> authors,
82 final List<String> tags) {
83 new SwingWorker<List<BookInfo>, Void>() {
84 @Override
85 protected List<BookInfo> doInBackground() throws Exception {
86 List<BookInfo> bookInfos = new ArrayList<BookInfo>();
87 BasicLibrary lib = Instance.getInstance().getLibrary();
88 for (MetaData meta : lib.getList().filter(sources, authors,
89 tags)) {
90 bookInfos.add(BookInfo.fromMeta(lib, meta));
91 }
92
93 return bookInfos;
94 }
95
96 @Override
97 protected void done() {
98 try {
99 load(get());
100 } catch (InterruptedException e) {
101 e.printStackTrace();
102 } catch (ExecutionException e) {
103 e.printStackTrace();
104 }
105 // TODO: error
106 }
107 }.execute();
108 }
109
110 public void load(List<BookInfo> bookInfos) {
111 this.bookInfos.clear();
112 this.bookInfos.addAll(bookInfos);
113 bookCoverUpdater.clear();
114
115 filter(searchBar.getText());
116 }
117
118 // cannot be NULL
119 private void filter(String filter) {
120 data.clear();
121 for (BookInfo bookInfo : bookInfos) {
122 if (bookInfo.getMainInfo() == null || filter.isEmpty()
123 || bookInfo.getMainInfo().toLowerCase()
124 .contains(filter.toLowerCase())) {
125 data.addElement(bookInfo);
126 }
127 }
128 list.repaint();
129 }
130
131 /**
132 * The secondary value content: word count or author.
133 *
134 * @return TRUE to see word counts, FALSE to see authors
135 */
136 public boolean isSeeWordCount() {
137 return seeWordCount;
138 }
139
140 /**
141 * The secondary value content: word count or author.
142 *
143 * @param seeWordCount
144 * TRUE to see word counts, FALSE to see authors
145 */
146 public void setSeeWordCount(boolean seeWordCount) {
147 if (this.seeWordCount != seeWordCount) {
148 if (books != null) {
149 for (BookLine book : books.values()) {
150 book.setSeeWordCount(seeWordCount);
151 }
152
153 list.repaint();
154 }
155 }
156 }
157
158 private JList<BookInfo> initList(boolean listMode) {
159 final JList<BookInfo> list = new JList<BookInfo>(data);
160
161 final JPopupMenu popup = new BookPopup(
162 Instance.getInstance().getLibrary(), new BookPopup.Informer() {
163 @Override
164 public void setCached(BookInfo book, boolean cached) {
165 book.setCached(cached);
166 fireElementChanged(book);
167 }
168
169 public void fireElementChanged(BookInfo book) {
170 data.fireElementChanged(book);
171 }
172
173 @Override
174 public List<BookInfo> getSelected() {
175 List<BookInfo> selected = new ArrayList<BookInfo>();
176 for (int index : list.getSelectedIndices()) {
177 selected.add(data.get(index));
178 }
179
180 return selected;
181 }
182
183 @Override
184 public BookInfo getUniqueSelected() {
185 List<BookInfo> selected = getSelected();
186 if (selected.size() == 1) {
187 return selected.get(0);
188 }
189 return null;
190 }
191
192 @Override
193 public void invalidateCache() {
194 // TODO: also reset the popup menu for sources/author
195 fireActionPerformed(INVALIDATE_CACHE);
196 }
197 });
198
199 list.addMouseMotionListener(new MouseAdapter() {
200 @Override
201 public void mouseMoved(MouseEvent me) {
202 if (popup.isShowing())
203 return;
204
205 Point p = new Point(me.getX(), me.getY());
206 int index = list.locationToIndex(p);
207 if (index != hoveredIndex) {
208 hoveredIndex = index;
209 list.repaint();
210 }
211 }
212 });
213 list.addMouseListener(new MouseAdapter() {
214 @Override
215 public void mousePressed(MouseEvent e) {
216 check(e);
217 }
218
219 @Override
220 public void mouseReleased(MouseEvent e) {
221 check(e);
222 }
223
224 @Override
225 public void mouseExited(MouseEvent e) {
226 if (popup.isShowing())
227 return;
228
229 if (hoveredIndex > -1) {
230 hoveredIndex = -1;
231 list.repaint();
232 }
233 }
234
235 @Override
236 public void mouseClicked(MouseEvent e) {
237 super.mouseClicked(e);
238 if (e.getClickCount() == 2) {
239 int index = list.locationToIndex(e.getPoint());
240 list.setSelectedIndex(index);
241
242 final BookInfo book = data.get(index);
243 BasicLibrary lib = Instance.getInstance().getLibrary();
244
245 Actions.openExternal(lib, book.getMeta(), BooksPanel.this,
246 new Runnable() {
247 @Override
248 public void run() {
249 book.setCached(true);
250 data.fireElementChanged(book);
251 }
252 });
253 }
254 }
255
256 private void check(MouseEvent e) {
257 if (e.isPopupTrigger()) {
258 if (list.getSelectedIndices().length <= 1) {
259 list.setSelectedIndex(
260 list.locationToIndex(e.getPoint()));
261 }
262
263 popup.show(list, e.getX(), e.getY());
264 }
265 }
266 });
267
268 list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
269 list.setSelectedIndex(0);
270 list.setCellRenderer(generateRenderer());
271 list.setVisibleRowCount(0);
272
273 this.list = list;
274 setListMode(listMode);
275 return this.list;
276 }
277
278 private ListCellRenderer<BookInfo> generateRenderer() {
279 return new ListCellRenderer<BookInfo>() {
280 @Override
281 public Component getListCellRendererComponent(
282 JList<? extends BookInfo> list, BookInfo value, int index,
283 boolean isSelected, boolean cellHasFocus) {
284 BookLine book = books.get(value);
285 if (book == null) {
286 if (listMode) {
287 book = new BookLine(value, seeWordCount);
288 } else {
289 book = new BookBlock(value, seeWordCount);
290 startUpdateBookCover((BookBlock) book);
291 }
292 books.put(value, book);
293 }
294
295 book.setSelected(isSelected);
296 book.setHovered(index == hoveredIndex);
297 return book;
298 }
299 };
300 }
301
302 private void startUpdateBookCover(final BookBlock book) {
303 bookCoverUpdater.delay(book.getInfo().getId(),
304 new SwingWorker<Image, Void>() {
305 @Override
306 protected Image doInBackground() throws Exception {
307 BasicLibrary lib = Instance.getInstance().getLibrary();
308 return BookBlock.generateCoverImage(lib,
309 book.getInfo());
310 }
311
312 protected void done() {
313 try {
314 book.setCoverImage(get());
315 data.fireElementChanged(book.getInfo());
316 } catch (Exception e) {
317 // TODO ? probably just log
318 }
319 }
320 });
321 }
322
323 public boolean isListMode() {
324 return listMode;
325 }
326
327 public void setListMode(boolean listMode) {
328 this.listMode = listMode;
329 books.clear();
330 list.setLayoutOrientation(
331 listMode ? JList.VERTICAL : JList.HORIZONTAL_WRAP);
332
333 if (listMode) {
334 bookCoverUpdater.clear();
335 }
336 }
337 }