| 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 | } |