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