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