1 package be
.nikiroo
.fanfix_swing
.gui
;
3 import java
.awt
.BorderLayout
;
4 import java
.awt
.Component
;
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
;
16 import java
.util
.Queue
;
17 import java
.util
.concurrent
.ExecutionException
;
19 import javax
.swing
.DefaultListModel
;
20 import javax
.swing
.JList
;
21 import javax
.swing
.JPopupMenu
;
22 import javax
.swing
.ListCellRenderer
;
23 import javax
.swing
.ListSelectionModel
;
24 import javax
.swing
.SwingUtilities
;
25 import javax
.swing
.SwingWorker
;
27 import be
.nikiroo
.fanfix
.Instance
;
28 import be
.nikiroo
.fanfix
.data
.MetaData
;
29 import be
.nikiroo
.fanfix
.library
.BasicLibrary
;
30 import be
.nikiroo
.fanfix_swing
.Actions
;
31 import be
.nikiroo
.fanfix_swing
.gui
.book
.BookBlock
;
32 import be
.nikiroo
.fanfix_swing
.gui
.book
.BookInfo
;
33 import be
.nikiroo
.fanfix_swing
.gui
.book
.BookLine
;
34 import be
.nikiroo
.fanfix_swing
.gui
.book
.BookPopup
;
35 import be
.nikiroo
.fanfix_swing
.gui
.utils
.ListenerPanel
;
36 import be
.nikiroo
.fanfix_swing
.gui
.utils
.UiHelper
;
38 public class BooksPanel
extends ListenerPanel
{
39 private class ListModel
extends DefaultListModel
<BookInfo
> {
40 public void fireElementChanged(BookInfo element
) {
41 int index
= indexOf(element
);
43 fireContentsChanged(element
, index
, index
);
48 static public final String INVALIDATE_CACHE
= "invalidate_cache";
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
;
55 private JList
<BookInfo
> list
;
56 private int hoveredIndex
= -1;
57 private ListModel data
= new ListModel();
59 private SearchBar searchBar
;
61 private Queue
<BookBlock
> updateBookQueue
= new LinkedList
<BookBlock
>();
62 private Object updateBookQueueLock
= new Object();
64 public BooksPanel(boolean listMode
) {
65 setLayout(new BorderLayout());
67 searchBar
= new SearchBar();
68 add(searchBar
, BorderLayout
.NORTH
);
70 searchBar
.addActionListener(new ActionListener() {
72 public void actionPerformed(ActionEvent e
) {
73 filter(searchBar
.getText());
77 add(UiHelper
.scroll(initList(listMode
)), BorderLayout
.CENTER
);
79 Thread bookBlocksUpdater
= new Thread(new Runnable() {
83 BasicLibrary lib
= Instance
.getInstance().getLibrary();
86 synchronized (updateBookQueueLock
) {
87 if (!updateBookQueue
.isEmpty()) {
88 book
= updateBookQueue
.remove();
96 final Image coverImage
= BookBlock
.generateCoverImage(lib
, book
.getInfo());
97 SwingUtilities
.invokeLater(new Runnable() {
101 book
.setCoverImage(coverImage
);
102 data
.fireElementChanged(book
.getInfo());
103 } catch (Exception e
) {
107 } catch (Exception e
) {
113 } catch (InterruptedException e
) {
118 bookBlocksUpdater
.setName("BookBlocks visual updater");
119 bookBlocksUpdater
.setDaemon(true);
120 bookBlocksUpdater
.start();
123 // null or empty -> all sources
124 // sources hierarchy supported ("source/" will includes all "source" and
126 public void load(final List
<String
> sources
, final List
<String
> authors
, final List
<String
> tags
) {
127 new SwingWorker
<List
<BookInfo
>, Void
>() {
129 protected List
<BookInfo
> doInBackground() throws Exception
{
130 List
<BookInfo
> bookInfos
= new ArrayList
<BookInfo
>();
131 BasicLibrary lib
= Instance
.getInstance().getLibrary();
132 for (MetaData meta
: lib
.getList().filter(sources
, authors
, tags
)) {
133 bookInfos
.add(BookInfo
.fromMeta(lib
, meta
));
140 protected void done() {
143 } catch (InterruptedException e
) {
145 } catch (ExecutionException e
) {
153 public void load(List
<BookInfo
> bookInfos
) {
154 this.bookInfos
.clear();
155 this.bookInfos
.addAll(bookInfos
);
156 synchronized (updateBookQueueLock
) {
157 updateBookQueue
.clear();
160 filter(searchBar
.getText());
164 private void filter(String filter
) {
166 for (BookInfo bookInfo
: bookInfos
) {
167 if (bookInfo
.getMainInfo() == null || filter
.isEmpty()
168 || bookInfo
.getMainInfo().toLowerCase().contains(filter
.toLowerCase())) {
169 data
.addElement(bookInfo
);
176 * The secondary value content: word count or author.
178 * @return TRUE to see word counts, FALSE to see authors
180 public boolean isSeeWordCount() {
185 * The secondary value content: word count or author.
187 * @param seeWordCount TRUE to see word counts, FALSE to see authors
189 public void setSeeWordCount(boolean seeWordCount
) {
190 if (this.seeWordCount
!= seeWordCount
) {
192 for (BookLine book
: books
.values()) {
193 book
.setSeeWordCount(seeWordCount
);
201 private JList
<BookInfo
> initList(boolean listMode
) {
202 final JList
<BookInfo
> list
= new JList
<BookInfo
>(data
);
204 final JPopupMenu popup
= new BookPopup(Instance
.getInstance().getLibrary(), new BookPopup
.Informer() {
206 public void setCached(BookInfo book
, boolean cached
) {
207 book
.setCached(cached
);
208 fireElementChanged(book
);
211 public void fireElementChanged(BookInfo book
) {
212 data
.fireElementChanged(book
);
216 public List
<BookInfo
> getSelected() {
217 List
<BookInfo
> selected
= new ArrayList
<BookInfo
>();
218 for (int index
: list
.getSelectedIndices()) {
219 selected
.add(data
.get(index
));
226 public BookInfo
getUniqueSelected() {
227 List
<BookInfo
> selected
= getSelected();
228 if (selected
.size() == 1) {
229 return selected
.get(0);
235 public void invalidateCache() {
236 // TODO: also reset the popup menu for sources/author
237 fireActionPerformed(INVALIDATE_CACHE
);
241 list
.addMouseMotionListener(new MouseAdapter() {
243 public void mouseMoved(MouseEvent me
) {
244 if (popup
.isShowing())
247 Point p
= new Point(me
.getX(), me
.getY());
248 int index
= list
.locationToIndex(p
);
249 if (index
!= hoveredIndex
) {
250 hoveredIndex
= index
;
255 list
.addMouseListener(new MouseAdapter() {
257 public void mousePressed(MouseEvent e
) {
262 public void mouseReleased(MouseEvent e
) {
267 public void mouseExited(MouseEvent e
) {
268 if (popup
.isShowing())
271 if (hoveredIndex
> -1) {
278 public void mouseClicked(MouseEvent e
) {
279 super.mouseClicked(e
);
280 if (e
.getClickCount() == 2) {
281 int index
= list
.locationToIndex(e
.getPoint());
282 list
.setSelectedIndex(index
);
284 final BookInfo book
= data
.get(index
);
285 BasicLibrary lib
= Instance
.getInstance().getLibrary();
287 Actions
.openExternal(lib
, book
.getMeta(), BooksPanel
.this, new Runnable() {
290 book
.setCached(true);
291 data
.fireElementChanged(book
);
297 private void check(MouseEvent e
) {
298 if (e
.isPopupTrigger()) {
299 if (list
.getSelectedIndices().length
<= 1) {
300 list
.setSelectedIndex(list
.locationToIndex(e
.getPoint()));
303 popup
.show(list
, e
.getX(), e
.getY());
308 list
.setSelectionMode(ListSelectionModel
.MULTIPLE_INTERVAL_SELECTION
);
309 list
.setSelectedIndex(0);
310 list
.setCellRenderer(generateRenderer());
311 list
.setVisibleRowCount(0);
314 setListMode(listMode
);
318 private ListCellRenderer
<BookInfo
> generateRenderer() {
319 return new ListCellRenderer
<BookInfo
>() {
321 public Component
getListCellRendererComponent(JList
<?
extends BookInfo
> list
, BookInfo value
, int index
,
322 boolean isSelected
, boolean cellHasFocus
) {
323 BookLine book
= books
.get(value
);
326 book
= new BookLine(value
, seeWordCount
);
328 book
= new BookBlock(value
, seeWordCount
);
329 synchronized (updateBookQueueLock
) {
330 updateBookQueue
.add((BookBlock
) book
);
333 books
.put(value
, book
);
336 book
.setSelected(isSelected
);
337 book
.setHovered(index
== hoveredIndex
);
343 public boolean isListMode() {
347 public void setListMode(boolean listMode
) {
348 this.listMode
= listMode
;
350 list
.setLayoutOrientation(listMode ? JList
.VERTICAL
: JList
.HORIZONTAL_WRAP
);
353 synchronized (updateBookQueueLock
) {
354 updateBookQueue
.clear();