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