stories order by name
[fanfix.git] / src / be / nikiroo / fanfix_swing / gui / browser / BasicTab.java
1 package be.nikiroo.fanfix_swing.gui.browser;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.util.ArrayList;
8 import java.util.Collection;
9 import java.util.Collections;
10 import java.util.Comparator;
11 import java.util.List;
12 import java.util.Map;
13
14 import javax.swing.JTree;
15 import javax.swing.SwingWorker;
16 import javax.swing.event.TreeSelectionEvent;
17 import javax.swing.event.TreeSelectionListener;
18 import javax.swing.plaf.basic.BasicTreeUI;
19 import javax.swing.tree.DefaultMutableTreeNode;
20 import javax.swing.tree.DefaultTreeCellRenderer;
21 import javax.swing.tree.DefaultTreeModel;
22 import javax.swing.tree.TreeCellRenderer;
23 import javax.swing.tree.TreePath;
24
25 import be.nikiroo.fanfix_swing.gui.SearchBar;
26 import be.nikiroo.fanfix_swing.gui.utils.ListenerPanel;
27 import be.nikiroo.fanfix_swing.gui.utils.TreeCellSpanner;
28 import be.nikiroo.fanfix_swing.gui.utils.TreeSnapshot;
29 import be.nikiroo.fanfix_swing.gui.utils.UiHelper;
30 import be.nikiroo.fanfix_swing.images.IconGenerator;
31 import be.nikiroo.fanfix_swing.images.IconGenerator.Icon;
32 import be.nikiroo.fanfix_swing.images.IconGenerator.Size;
33
34 public abstract class BasicTab<T> extends ListenerPanel {
35 private int totalCount = 0;
36 private List<String> selectedElements = new ArrayList<String>();
37 private T data;
38 private String baseTitle;
39 private String listenerCommand;
40 private int index;
41
42 private JTree tree;
43 private DefaultMutableTreeNode root;
44 private SearchBar searchBar;
45
46 public BasicTab(int index, String listenerCommand) {
47 setLayout(new BorderLayout());
48
49 this.index = index;
50 this.listenerCommand = listenerCommand;
51
52 data = createEmptyData();
53 totalCount = 0;
54
55 root = new DefaultMutableTreeNode();
56
57 tree = new JTree(root);
58 tree.setUI(new BasicTreeUI());
59 TreeCellSpanner spanner = new TreeCellSpanner(tree,
60 generateCellRenderer());
61 tree.setCellRenderer(spanner);
62 tree.setRootVisible(false);
63 tree.setShowsRootHandles(false);
64
65 tree.addTreeSelectionListener(new TreeSelectionListener() {
66 @Override
67 public void valueChanged(TreeSelectionEvent e) {
68 List<String> elements = new ArrayList<String>();
69 TreePath[] paths = tree.getSelectionPaths();
70 if (paths != null) {
71 for (TreePath path : paths) {
72 String key = path.getLastPathComponent().toString();
73 elements.add(keyToElement(key));
74 }
75 }
76
77 List<String> selectedElements = new ArrayList<String>();
78 for (String element : elements) {
79 if (!selectedElements.contains(element)) {
80 selectedElements.add(element);
81 }
82 }
83
84 BasicTab.this.selectedElements = selectedElements;
85
86 fireActionPerformed(BasicTab.this.listenerCommand);
87 }
88 });
89
90 add(UiHelper.scroll(tree), BorderLayout.CENTER);
91
92 searchBar = new SearchBar();
93 add(searchBar, BorderLayout.NORTH);
94 searchBar.addActionListener(new ActionListener() {
95 @Override
96 public void actionPerformed(ActionEvent e) {
97 reloadData();
98 }
99 });
100
101 reloadData();
102 }
103
104 public void reloadData() {
105 final TreeSnapshot snapshot = new TreeSnapshot(tree) {
106 @Override
107 protected boolean isSamePath(TreePath oldPath, TreePath newPath) {
108 String oldString = oldPath.toString();
109 if (oldString.endsWith("/]"))
110 oldString = oldString.substring(0, oldString.length() - 2)
111 + "]";
112
113 String newString = newPath.toString();
114 if (newString.endsWith("/]"))
115 newString = newString.substring(0, newString.length() - 2)
116 + "]";
117
118 return oldString.equals(newString);
119 }
120 };
121 SwingWorker<Map<String, List<String>>, Integer> worker = new SwingWorker<Map<String, List<String>>, Integer>() {
122 @Override
123 protected Map<String, List<String>> doInBackground()
124 throws Exception {
125 fillData(data);
126 return null;
127 }
128
129 @Override
130 protected void done() {
131 try {
132 get();
133 } catch (Exception e) {
134 // TODO: error
135 }
136
137 root.removeAllChildren();
138 totalCount = loadData(root, data, searchBar.getText());
139 ((DefaultTreeModel) tree.getModel()).reload();
140
141 snapshot.apply();
142
143 fireActionPerformed(listenerCommand);
144 }
145 };
146 worker.execute();
147 }
148
149 /**
150 * The currently selected elements, or an empty list.
151 *
152 * @return the sources (cannot be NULL)
153 */
154 public List<String> getSelectedElements() {
155 return selectedElements;
156 }
157
158 public int getTotalCount() {
159 return totalCount;
160 }
161
162 public String getBaseTitle() {
163 return baseTitle;
164 }
165
166 public void setBaseTitle(String baseTitle) {
167 this.baseTitle = baseTitle;
168 }
169
170 public String getTitle() {
171 String title = getBaseTitle();
172 String count = "";
173 if (totalCount > 0) {
174 int selected = selectedElements.size();
175 count = " (" + (selected > 0 ? selected + "/" : "") + totalCount
176 + ")";
177 }
178
179 return title + count;
180 }
181
182 public int getIndex() {
183 return index;
184 }
185
186 public void unselect() {
187 tree.clearSelection();
188 }
189
190 protected boolean checkFilter(String filter, String value) {
191 return (filter == null || filter.isEmpty()
192 || value.toLowerCase().contains(filter.toLowerCase()));
193 }
194
195 protected boolean checkFilter(String filter, List<String> list) {
196 for (String value : list) {
197 if (checkFilter(filter, value))
198 return true;
199 }
200 return false;
201 }
202
203 protected abstract T createEmptyData();
204
205 // beware: you should update it OR clean/re-add it, but previous data may
206 // still be there
207 protected abstract void fillData(T data);
208
209 protected abstract String keyToElement(String key);
210
211 protected abstract String keyToDisplay(String key);
212
213 protected abstract int loadData(DefaultMutableTreeNode root, T data,
214 String filter);
215
216 protected void sort(List<String> values) {
217 Collections.sort(values, new Comparator<String>() {
218 @Override
219 public int compare(String o1, String o2) {
220 return ("" + o1).compareToIgnoreCase("" + o2);
221 }
222 });
223 }
224
225 private TreeCellRenderer generateCellRenderer() {
226 DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer() {
227 @Override
228 public Component getTreeCellRendererComponent(JTree tree,
229 Object value, boolean selected, boolean expanded,
230 boolean leaf, int row, boolean hasFocus) {
231 if (value instanceof DefaultMutableTreeNode) {
232 if (((DefaultMutableTreeNode) value).getLevel() > 1) {
233 setLeafIcon(null);
234 setLeafIcon(IconGenerator.get(Icon.empty, Size.x4));
235 } else {
236 setLeafIcon(IconGenerator.get(Icon.empty, Size.x16));
237 }
238 }
239
240 String display = value == null ? "" : value.toString();
241 display = keyToDisplay(display);
242
243 return super.getTreeCellRendererComponent(tree, display,
244 selected, expanded, leaf, row, hasFocus);
245 }
246 };
247
248 renderer.setClosedIcon(IconGenerator.get(Icon.arrow_right, Size.x16));
249 renderer.setOpenIcon(IconGenerator.get(Icon.arrow_down, Size.x16));
250 renderer.setLeafIcon(IconGenerator.get(Icon.empty, Size.x16));
251
252 return renderer;
253 }
254 }