1 | // Jomic - a viewer for comic book archives. |
2 | // Copyright (C) 2004-2011 Thomas Aglassinger |
3 | // |
4 | // This program is free software: you can redistribute it and/or modify |
5 | // it under the terms of the GNU General Public License as published by |
6 | // the Free Software Foundation, either version 3 of the License, or |
7 | // (at your option) any later version. |
8 | // |
9 | // This program is distributed in the hope that it will be useful, |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | // GNU General Public License for more details. |
13 | // |
14 | // You should have received a copy of the GNU General Public License |
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | package net.sf.jomic.comic; |
17 | |
18 | import java.awt.BorderLayout; |
19 | import java.awt.FlowLayout; |
20 | import java.awt.GridLayout; |
21 | import java.awt.event.ActionEvent; |
22 | import java.awt.event.ActionListener; |
23 | import java.beans.PropertyChangeEvent; |
24 | import java.beans.PropertyChangeListener; |
25 | import java.text.NumberFormat; |
26 | import java.util.Map; |
27 | import java.util.TreeMap; |
28 | |
29 | import javax.swing.BorderFactory; |
30 | import javax.swing.BoxLayout; |
31 | import javax.swing.ButtonGroup; |
32 | import javax.swing.JCheckBox; |
33 | import javax.swing.JFormattedTextField; |
34 | import javax.swing.JLabel; |
35 | import javax.swing.JPanel; |
36 | import javax.swing.JRadioButton; |
37 | import javax.swing.JToggleButton; |
38 | |
39 | import net.sf.jomic.common.JomicTools; |
40 | import net.sf.jomic.tools.LocaleTools; |
41 | import net.sf.jomic.tools.StringTools; |
42 | |
43 | /** |
44 | * JavaBean to show and edit ConversionSettings. |
45 | * |
46 | * @see Conversion |
47 | * @author Thomas Aglassinger |
48 | */ |
49 | public class ConversionBean extends JPanel implements ActionListener, PropertyChangeListener |
50 | { |
51 | private static final String ACTION_SET = "set_"; |
52 | private static final String ACTION_TOGGLE = "toggle_"; |
53 | private static final int MAX_IMAGE_SIZE = 9999999; |
54 | private JCheckBox addOnlyImagesCheckBox; |
55 | private Map comicFormatToRadioButtonMap; |
56 | private Conversion conversion; |
57 | private JCheckBox excludeThumbPagesCheckBox; |
58 | private JPanel imageFormatPane; |
59 | private Map imageFormatToRadioButtonMap; |
60 | private JomicTools jomicTools; |
61 | private JCheckBox limitPageSizeCheckBox; |
62 | private LocaleTools localeTools; |
63 | private JFormattedTextField maxImageHeightField; |
64 | private JFormattedTextField maxImageWidthField; |
65 | private JFormattedTextField maxThumbPageHeightField; |
66 | private JFormattedTextField maxThumbPageWidthField; |
67 | private StringTools stringTools; |
68 | |
69 | public ConversionBean() { |
70 | this(null); |
71 | } |
72 | |
73 | public ConversionBean(Conversion newConversionSettings) { |
74 | jomicTools = JomicTools.instance(); |
75 | localeTools = LocaleTools.instance(); |
76 | stringTools = StringTools.instance(); |
77 | |
78 | if (newConversionSettings == null) { |
79 | conversion = new Conversion(); |
80 | } else { |
81 | conversion = newConversionSettings; |
82 | } |
83 | comicFormatToRadioButtonMap = new TreeMap(); |
84 | imageFormatToRadioButtonMap = new TreeMap(); |
85 | setupUserInterface(); |
86 | conversion.addPropertyChangeListener(this); |
87 | } |
88 | |
89 | private void setupUserInterface() { |
90 | // Create components |
91 | JPanel comicFormatPane = createRadioButtonPanel( |
92 | Conversion.PROPERTY_COMIC_FORMAT, comicFormatToRadioButtonMap, |
93 | new String[]{"CBZ", "PDF"}, |
94 | new String[]{Conversion.COMIC_FORMAT_CBZ, Conversion.COMIC_FORMAT_PDF} |
95 | ); |
96 | |
97 | String comicFormatText = localeTools.getMessage("beans.conversion.comicFormat"); |
98 | String limitPageSizeToText = localeTools.getMessage("beans.conversion.limitPageSizeTo"); |
99 | String limitByText = localeTools.getMessage("beans.conversion.limitBy"); |
100 | String excludePageThumbsText = localeTools.getMessage("beans.conversion.excludePagesThumbsUpTo"); |
101 | String imageFormatText = localeTools.getMessage("beans.conversion.imageFormat"); |
102 | String keepFormatText = localeTools.getMessage("beans.conversion.keepFormat"); |
103 | String addOnlyImagesText = localeTools.getMessage("beans.conversion.addOnlyImages"); |
104 | |
105 | comicFormatPane.setBorder(BorderFactory.createTitledBorder(null, comicFormatText)); |
106 | maxImageHeightField = createIntTextField(Conversion.PROPERTY_MAX_PAGE_HEIGHT, 0, MAX_IMAGE_SIZE); |
107 | maxImageWidthField = createIntTextField(Conversion.PROPERTY_MAX_PAGE_WIDTH, 0, MAX_IMAGE_SIZE); |
108 | limitPageSizeCheckBox = createCheckBox(limitPageSizeToText, Conversion.PROPERTY_LIMIT_PAGE_SIZE); |
109 | maxThumbPageHeightField = createIntTextField(Conversion.PROPERTY_MAX_THUMB_PAGE_HEIGHT, 0, MAX_IMAGE_SIZE); |
110 | maxThumbPageWidthField = createIntTextField(Conversion.PROPERTY_MAX_THUMB_PAGE_WIDTH, 0, MAX_IMAGE_SIZE); |
111 | excludeThumbPagesCheckBox = createCheckBox(excludePageThumbsText, Conversion.PROPERTY_EXCLUDE_THUMB_PAGES); |
112 | addOnlyImagesCheckBox = createCheckBox(addOnlyImagesText, Conversion.PROPERTY_ADD_ONLY_IMAGES); |
113 | |
114 | JPanel limitSizeHeadingPane = new JPanel(); |
115 | |
116 | limitSizeHeadingPane.setBorder(null); |
117 | limitSizeHeadingPane.setLayout(new FlowLayout(FlowLayout.LEADING)); |
118 | limitSizeHeadingPane.add(limitPageSizeCheckBox); |
119 | |
120 | JPanel limitSizeFieldPane = new JPanel(); |
121 | |
122 | limitSizeFieldPane.setBorder(null); |
123 | limitSizeFieldPane.add(maxImageWidthField); |
124 | limitSizeFieldPane.add(new JLabel(limitByText)); |
125 | limitSizeFieldPane.add(maxImageHeightField); |
126 | |
127 | JPanel excludeThumbPagesHeadingPane = new JPanel(); |
128 | |
129 | excludeThumbPagesHeadingPane.setBorder(null); |
130 | excludeThumbPagesHeadingPane.setLayout(new FlowLayout(FlowLayout.LEADING)); |
131 | excludeThumbPagesHeadingPane.add(excludeThumbPagesCheckBox); |
132 | |
133 | JPanel excludeThumbPagesPane = new JPanel(); |
134 | |
135 | excludeThumbPagesPane.setBorder(null); |
136 | excludeThumbPagesPane.add(maxThumbPageWidthField); |
137 | excludeThumbPagesPane.add(new JLabel(limitByText)); |
138 | excludeThumbPagesPane.add(maxThumbPageHeightField); |
139 | imageFormatPane = createRadioButtonPanel( |
140 | Conversion.PROPERTY_IMAGE_FORMAT, imageFormatToRadioButtonMap, |
141 | new String[]{keepFormatText, "PNG", "JFIF/JPEG"}, |
142 | new String[]{ |
143 | Conversion.IMAGE_FORMAT_PRESERVE, |
144 | Conversion.IMAGE_FORMAT_PNG, |
145 | Conversion.IMAGE_FORMAT_JFIF} |
146 | ); |
147 | imageFormatPane.setBorder(BorderFactory.createTitledBorder(null, imageFormatText)); |
148 | |
149 | JPanel addOnlyImagesPane = new JPanel(); |
150 | |
151 | addOnlyImagesPane.setBorder(null); |
152 | addOnlyImagesPane.setLayout(new FlowLayout(FlowLayout.LEADING)); |
153 | addOnlyImagesPane.add(addOnlyImagesCheckBox); |
154 | |
155 | propertyLimitImageSizeChanged(conversion.getBooleanProperty(Conversion.PROPERTY_LIMIT_PAGE_SIZE)); |
156 | propertyExcludeThumbPagesChanged(conversion.getBooleanProperty(Conversion.PROPERTY_EXCLUDE_THUMB_PAGES)); |
157 | propertComicFormatChanged(conversion.getComicFormat()); |
158 | |
159 | // Build the layout. |
160 | JPanel controlPane = new JPanel(); |
161 | |
162 | controlPane.setLayout(new BoxLayout(controlPane, BoxLayout.PAGE_AXIS)); |
163 | controlPane.add(comicFormatPane); |
164 | controlPane.add(imageFormatPane); |
165 | controlPane.add(limitSizeHeadingPane); |
166 | controlPane.add(limitSizeFieldPane); |
167 | controlPane.add(excludeThumbPagesHeadingPane); |
168 | controlPane.add(excludeThumbPagesPane); |
169 | controlPane.add(addOnlyImagesPane); |
170 | |
171 | setLayout(new BorderLayout()); |
172 | add(controlPane, BorderLayout.PAGE_START); |
173 | } |
174 | |
175 | public Conversion getConversion() { |
176 | return conversion; |
177 | } |
178 | |
179 | public void actionPerformed(ActionEvent event) { |
180 | try { |
181 | assert event != null; |
182 | Object source = event.getSource(); |
183 | |
184 | assert source != null; |
185 | String command = event.getActionCommand(); |
186 | |
187 | if (command != null) { |
188 | if (command.startsWith(ACTION_SET)) { |
189 | String propertyAndValue = command.substring(ACTION_SET.length()); |
190 | int indexOfEqalSign = propertyAndValue.indexOf('='); |
191 | |
192 | if (indexOfEqalSign > 0) { |
193 | String propertyName = propertyAndValue.substring(0, indexOfEqalSign); |
194 | String propertyValue = propertyAndValue.substring(indexOfEqalSign + 1); |
195 | |
196 | conversion.setProperty(propertyName, propertyValue); |
197 | } else { |
198 | String message = localeTools.getMessage( |
199 | "errors.setActionCommandMustContainEqualSign", command); |
200 | |
201 | throw new IllegalArgumentException(message); |
202 | } |
203 | } else if (command.startsWith(ACTION_TOGGLE)) { |
204 | String propertyName = command.substring(ACTION_TOGGLE.length()); |
205 | JToggleButton sourceButton = (JToggleButton) source; |
206 | boolean selected = sourceButton.isSelected(); |
207 | |
208 | conversion.setBooleanProperty(propertyName, selected); |
209 | } else { |
210 | assert false : "command=" + command; |
211 | } |
212 | } else { |
213 | assert false : "event=" + event; |
214 | } |
215 | } catch (Throwable error) { |
216 | jomicTools.showError(event, error); |
217 | } |
218 | } |
219 | |
220 | public void propertyChange(PropertyChangeEvent event) { |
221 | assert event != null; |
222 | String propertyName = event.getPropertyName(); |
223 | |
224 | assert propertyName != null; |
225 | String newValue = (String) event.getNewValue(); |
226 | |
227 | if (propertyName.equals(Conversion.PROPERTY_ADD_ONLY_IMAGES)) { |
228 | // Do nothing. |
229 | } else if (propertyName.equals(Conversion.PROPERTY_COMIC_FORMAT)) { |
230 | String fileFormat = conversion.getComicFormat(); |
231 | JRadioButton selectedButton = (JRadioButton) comicFormatToRadioButtonMap.get(fileFormat); |
232 | |
233 | assert selectedButton != null : "no button for format " + fileFormat + " in fileFormatToRadioButtonMap"; |
234 | |
235 | selectedButton.setSelected(true); |
236 | propertComicFormatChanged(fileFormat); |
237 | } else if (propertyName.equals(Conversion.PROPERTY_EXCLUDE_THUMB_PAGES)) { |
238 | boolean selected = Boolean.valueOf(newValue).booleanValue(); |
239 | |
240 | propertyExcludeThumbPagesChanged(selected); |
241 | } else if (propertyName.equals(Conversion.PROPERTY_IMAGE_FORMAT)) { |
242 | String imageFormat = conversion.getImageFormat(); |
243 | JRadioButton selectedButton = (JRadioButton) imageFormatToRadioButtonMap.get(imageFormat); |
244 | |
245 | assert selectedButton != null : "no button for image format " + imageFormat |
246 | + " in imageFormatToRadioButtonMap"; |
247 | |
248 | selectedButton.setSelected(true); |
249 | } else if (propertyName.equals(Conversion.PROPERTY_MAX_PAGE_HEIGHT)) { |
250 | maxImageHeightField.setValue(newValue); |
251 | } else if (propertyName.equals(Conversion.PROPERTY_LIMIT_PAGE_SIZE)) { |
252 | boolean selected = Boolean.valueOf(newValue).booleanValue(); |
253 | |
254 | propertyLimitImageSizeChanged(selected); |
255 | } else if (propertyName.equals(Conversion.PROPERTY_MAX_PAGE_WIDTH)) { |
256 | maxImageWidthField.setValue(newValue); |
257 | } else { |
258 | assert false : "handling for property \"" + propertyName + "\" must be implemented"; |
259 | } |
260 | } |
261 | |
262 | private JCheckBox createCheckBox(String newLabel, String propertyName) { |
263 | JCheckBox result = new JCheckBox(newLabel); |
264 | |
265 | result.setSelected(conversion.getBooleanProperty(propertyName)); |
266 | result.setActionCommand(ACTION_TOGGLE + propertyName); |
267 | result.addActionListener(this); |
268 | return result; |
269 | } |
270 | |
271 | private JFormattedTextField createIntTextField(String propertyName, int minValue, int maxValue) { |
272 | assert propertyName != null; |
273 | assert minValue <= maxValue; |
274 | |
275 | // TODO: validate for min/maxValue |
276 | JFormattedTextField result; |
277 | NumberFormat intFormat = NumberFormat.getNumberInstance(); |
278 | int minDigits = stringTools.getDigitCount(minValue); |
279 | int maxDigits = stringTools.getDigitCount(maxValue); |
280 | |
281 | intFormat.setParseIntegerOnly(true); |
282 | intFormat.setMaximumIntegerDigits(Math.max(minDigits, maxDigits)); |
283 | result = new JFormattedTextField(intFormat); |
284 | result.setValue(new Integer(conversion.getIntProperty(propertyName))); |
285 | result.setColumns(intFormat.getMaximumIntegerDigits()); |
286 | return result; |
287 | } |
288 | |
289 | private JRadioButton createRadioButton(String newLabel, String propertyName, |
290 | String newValueToSet) { |
291 | assert newLabel != null; |
292 | assert propertyName != null; |
293 | assert propertyName.indexOf('=') == -1 : "propertyName must not contain '=': " + propertyName; |
294 | assert newValueToSet != null; |
295 | JRadioButton result = new JRadioButton(newLabel); |
296 | |
297 | if (conversion.getProperty(propertyName).equals(newValueToSet)) { |
298 | result.setSelected(true); |
299 | } |
300 | result.setActionCommand(ACTION_SET + propertyName + "=" + newValueToSet); |
301 | result.addActionListener(this); |
302 | |
303 | return result; |
304 | } |
305 | |
306 | private JPanel createRadioButtonPanel( |
307 | String propertyName, Map valueToRadioButtonMap, String[] labels, String[] values) { |
308 | assert propertyName != null; |
309 | assert valueToRadioButtonMap != null; |
310 | assert valueToRadioButtonMap.isEmpty(); |
311 | assert labels != null; |
312 | assert values != null; |
313 | int labelLength = labels.length; |
314 | int valuesLength = values.length; |
315 | |
316 | assert labelLength == valuesLength : "labels.length is " + labelLength + " but must be " + valuesLength; |
317 | JPanel result = new JPanel(new GridLayout(0, 1)); |
318 | ButtonGroup buttonGroup = new ButtonGroup(); |
319 | |
320 | result.setBorder(null); |
321 | for (int i = 0; i < labelLength; i += 1) { |
322 | String value = values[i]; |
323 | JRadioButton radioButton = createRadioButton(labels[i], propertyName, value); |
324 | |
325 | assert !valueToRadioButtonMap.containsKey(value) : "radio button value must be unique: " + value; |
326 | valueToRadioButtonMap.put(value, radioButton); |
327 | buttonGroup.add(radioButton); |
328 | result.add(radioButton); |
329 | } |
330 | return result; |
331 | } |
332 | |
333 | private void propertComicFormatChanged(String fileFormat) { |
334 | boolean imageFormatChoiceAndOtherFilesSupported = fileFormat.equals(Conversion.COMIC_FORMAT_CBZ); |
335 | |
336 | for (int i = 0; i < imageFormatPane.getComponentCount(); i += 1) { |
337 | imageFormatPane.getComponent(i).setEnabled(imageFormatChoiceAndOtherFilesSupported); |
338 | } |
339 | addOnlyImagesCheckBox.setEnabled(imageFormatChoiceAndOtherFilesSupported); |
340 | } |
341 | |
342 | private void propertyExcludeThumbPagesChanged(boolean newValue) { |
343 | maxThumbPageHeightField.setEnabled(newValue); |
344 | maxThumbPageWidthField.setEnabled(newValue); |
345 | } |
346 | |
347 | private void propertyLimitImageSizeChanged(boolean newValue) { |
348 | maxImageHeightField.setEnabled(newValue); |
349 | maxImageWidthField.setEnabled(newValue); |
350 | } |
351 | } |