| 1 | // Copyright 2004-2007 Jean-Francois Poilpret |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package net.sourceforge.hiveboard.drawing; |
| 16 | |
| 17 | import java.awt.Dimension; |
| 18 | import java.awt.Point; |
| 19 | import java.awt.Rectangle; |
| 20 | import java.awt.geom.AffineTransform; |
| 21 | import java.awt.image.BufferedImage; |
| 22 | |
| 23 | import javax.swing.JSpinner; |
| 24 | import javax.swing.SpinnerNumberModel; |
| 25 | import javax.swing.event.ChangeEvent; |
| 26 | import javax.swing.event.ChangeListener; |
| 27 | |
| 28 | import net.sourceforge.hiveboard.util.OutlineChangeListener; |
| 29 | import net.sourceforge.hiveboard.util.OutlineThumbnail; |
| 30 | import net.sourceforge.hivegui.imaging.ImageTools; |
| 31 | |
| 32 | /** |
| 33 | * |
| 34 | */ |
| 35 | public class ImageToolSettingsPanelHandler |
| 36 | implements OutlineChangeListener |
| 37 | { |
| 38 | public ImageToolSettingsPanelHandler( ImageToolSettings displaySettings, |
| 39 | OutlineThumbnail thbPreview, |
| 40 | JSpinner txfX, |
| 41 | JSpinner txfY, |
| 42 | JSpinner txfWidth, |
| 43 | JSpinner txfHeight, |
| 44 | ImageCompositor compositor, |
| 45 | boolean realTimePreview) |
| 46 | { |
| 47 | _displaySettings = displaySettings; |
| 48 | _previewSize = thbPreview.getThumbnailSize(); |
| 49 | _thbPreview = thbPreview; |
| 50 | _thbPreview.setOutlineChangeListener(this); |
| 51 | |
| 52 | _compositor = compositor; |
| 53 | _realTimePreview = realTimePreview; |
| 54 | |
| 55 | // Set limit values of spinners |
| 56 | initSpinner(txfX, _modelX, MIN_LOCATION, MAX_LOCATION); |
| 57 | initSpinner(txfY, _modelY, MIN_LOCATION, MAX_LOCATION); |
| 58 | initSpinner(txfWidth, _modelWidth, MIN_SIZE, MAX_SIZE); |
| 59 | initSpinner(txfHeight, _modelHeight, MIN_SIZE, MAX_SIZE); |
| 60 | |
| 61 | // Add listeners for automatically updating controls on size depnding on position |
| 62 | ChangeListener updateAreaListener = new ChangeListener() |
| 63 | { |
| 64 | public void stateChanged(ChangeEvent e) |
| 65 | { |
| 66 | updateSizeControl(); |
| 67 | updatePreview(); |
| 68 | } |
| 69 | }; |
| 70 | _modelX.addChangeListener(updateAreaListener); |
| 71 | _modelY.addChangeListener(updateAreaListener); |
| 72 | _modelWidth.addChangeListener(updateAreaListener); |
| 73 | _modelHeight.addChangeListener(updateAreaListener); |
| 74 | |
| 75 | // Add mouse listener on thumbnail to easily set the location/size |
| 76 | _thbPreview.setOutlineChangeListener(this); |
| 77 | } |
| 78 | |
| 79 | // ======================== START OF PUBLIC API ======================== // |
| 80 | public void setSourceImage(BufferedImage image) |
| 81 | { |
| 82 | _selectedImage = null; |
| 83 | _boardWidth = image.getWidth(); |
| 84 | _boardHeight = image.getHeight(); |
| 85 | // Scale original board to fit the Thumbnail |
| 86 | _board = ImageTools.scaleImage(image, BufferedImage.TYPE_INT_ARGB, _previewSize); |
| 87 | // Calculate scaling ratio for the board into preview |
| 88 | double scale; |
| 89 | if (_boardWidth > _boardHeight) |
| 90 | { |
| 91 | scale = (double) _previewSize / _boardWidth; |
| 92 | } |
| 93 | else |
| 94 | { |
| 95 | scale = (double) _previewSize / _boardHeight; |
| 96 | } |
| 97 | _boardScaleRatio = scale; |
| 98 | // Calculate transformation matrices |
| 99 | _transformToPreview = AffineTransform.getScaleInstance(scale, scale); |
| 100 | scale = 1.0 / scale; |
| 101 | _transformToOrigin = AffineTransform.getScaleInstance(scale, scale); |
| 102 | // Set limit values of outline drag |
| 103 | Point minLocation = new Point(MIN_LOCATION, MIN_LOCATION); |
| 104 | Point maxLocation = new Point(MAX_LOCATION, MAX_LOCATION); |
| 105 | Point minSize = new Point(MIN_SIZE, MIN_SIZE); |
| 106 | Point maxSize = new Point(MAX_SIZE, MAX_SIZE); |
| 107 | // Translate limits into outline geometry |
| 108 | _transformToPreview.transform(minLocation, minLocation); |
| 109 | _transformToPreview.transform(maxLocation, maxLocation); |
| 110 | _transformToPreview.transform(minSize, minSize); |
| 111 | _transformToPreview.transform(maxSize, maxSize); |
| 112 | _thbPreview.setOutlineLimits( minLocation, |
| 113 | maxLocation, |
| 114 | new Dimension(minSize.x, minSize.y), |
| 115 | new Dimension(maxSize.x, maxSize.y)); |
| 116 | // Initialize models for various components |
| 117 | setupModels(); |
| 118 | } |
| 119 | |
| 120 | public void reset() |
| 121 | { |
| 122 | _board = null; |
| 123 | _selectedImage = null; |
| 124 | _thbPreview.setFile(null); |
| 125 | } |
| 126 | |
| 127 | // Called when _displaySettings has changed (through the GUI) |
| 128 | public void stateChanged() |
| 129 | { |
| 130 | updateSizeControl(); |
| 131 | updatePreview(); |
| 132 | } |
| 133 | |
| 134 | public Rectangle getSpinnersArea() |
| 135 | { |
| 136 | return new Rectangle( getSpinnerValue(_modelX), |
| 137 | getSpinnerValue(_modelY), |
| 138 | getSpinnerValue(_modelWidth), |
| 139 | getSpinnerValue(_modelHeight)); |
| 140 | } |
| 141 | // ======================== END OF PUBLIC API ======================== // |
| 142 | |
| 143 | // ======================== START OF INTERNAL EVENT CALLBACKS ======================== // |
| 144 | // Callback (by preview) notifying a change in the user-dragged outline |
| 145 | public void outlineChanged(Rectangle sourceOutline, boolean temporary) |
| 146 | { |
| 147 | // Convert outline to Board coordinates |
| 148 | Rectangle outline = |
| 149 | _transformToOrigin.createTransformedShape(sourceOutline).getBounds(); |
| 150 | |
| 151 | // Update spinners |
| 152 | _skipChangeEvent = true; |
| 153 | setSpinnersArea(outline); |
| 154 | _skipChangeEvent = false; |
| 155 | |
| 156 | if (_realTimePreview || !temporary) |
| 157 | { |
| 158 | // Update preview with composited image |
| 159 | updatePreview(); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | // Callback notifying a change of image file selection |
| 164 | public void setSelectedImage(BufferedImage selection) |
| 165 | { |
| 166 | if (selection != null) |
| 167 | { |
| 168 | // Scale it to fit the proportions of board in preview |
| 169 | _selectedImage = ImageTools.scaleImage( selection, |
| 170 | BufferedImage.TYPE_INT_ARGB, |
| 171 | _boardScaleRatio); |
| 172 | _thbPreview.setAllowDrag(true); |
| 173 | } |
| 174 | else |
| 175 | { |
| 176 | _selectedImage = null; |
| 177 | _thbPreview.setAllowDrag(false); |
| 178 | } |
| 179 | // Update preview with composited image |
| 180 | updatePreview(); |
| 181 | } |
| 182 | |
| 183 | private void updatePreview() |
| 184 | { |
| 185 | if (_selectedImage != null && !_skipChangeEvent) |
| 186 | { |
| 187 | // Compute composited preview |
| 188 | BufferedImage image; |
| 189 | ImageToolSettings settings = getOutlineSettings(); |
| 190 | image = _compositor.computeUsefulSourceImage( _selectedImage, |
| 191 | settings, |
| 192 | _board.getWidth(), |
| 193 | _board.getHeight()); |
| 194 | Rectangle insertArea = settings.getInsertArea(); |
| 195 | image = _compositor.compose(image, |
| 196 | ImageTools.cloneImage( _board, |
| 197 | BufferedImage.TYPE_INT_ARGB), |
| 198 | insertArea.x, |
| 199 | insertArea.y); |
| 200 | // Update preview |
| 201 | _thbPreview.setIcon(image); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | private void updateSizeControl() |
| 206 | { |
| 207 | // Update preview outline |
| 208 | if (!_skipChangeEvent) |
| 209 | { |
| 210 | // Get outline from spinners to preview geometry |
| 211 | Rectangle outline = getSpinnersArea(); |
| 212 | // Transform outline to preview geometry |
| 213 | outline = _transformToPreview.createTransformedShape(outline).getBounds(); |
| 214 | _thbPreview.setOutline(outline); |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | private ImageToolSettings getOutlineSettings() |
| 219 | { |
| 220 | Rectangle outline = getSpinnersArea(); |
| 221 | // Convert outline to preview geometry |
| 222 | outline = _transformToPreview.createTransformedShape(outline).getBounds(); |
| 223 | _displaySettings.setInsertArea(outline); |
| 224 | return _displaySettings; |
| 225 | } |
| 226 | |
| 227 | // ======================== END OF INTERNAL EVENT CALLBACKS ======================== // |
| 228 | |
| 229 | // ======================== START OF INTERNAL API FOR VIEW ======================== // |
| 230 | static private void initSpinner(JSpinner spinner, |
| 231 | SpinnerNumberModel model, |
| 232 | int min, |
| 233 | int max) |
| 234 | { |
| 235 | spinner.setModel(model); |
| 236 | model.setMinimum(min); |
| 237 | model.setMaximum(max); |
| 238 | model.setStepSize(SPINNER_STEP); |
| 239 | } |
| 240 | |
| 241 | // Re-initializes components values for a new board (model) |
| 242 | private void setupModels() |
| 243 | { |
| 244 | _thbPreview.setIcon(_board); |
| 245 | int width = _boardWidth; |
| 246 | int height = _boardHeight; |
| 247 | Rectangle outline = new Rectangle(0, 0, width, height); |
| 248 | // Update spinners |
| 249 | setSpinnersArea(outline); |
| 250 | // Transform outline to preview geometry |
| 251 | outline = _transformToPreview.createTransformedShape(outline).getBounds(); |
| 252 | // Update preview outline |
| 253 | _skipChangeEvent = true; |
| 254 | _thbPreview.setOutline(outline); |
| 255 | _skipChangeEvent = false; |
| 256 | } |
| 257 | |
| 258 | private void setSpinnersArea(Rectangle area) |
| 259 | { |
| 260 | _modelX.setValue(area.x); |
| 261 | _modelY.setValue(area.y); |
| 262 | _modelWidth.setValue(area.width); |
| 263 | _modelHeight.setValue(area.height); |
| 264 | } |
| 265 | |
| 266 | static private int getSpinnerValue(SpinnerNumberModel model) |
| 267 | { |
| 268 | return ((Number) model.getValue()).intValue(); |
| 269 | } |
| 270 | |
| 271 | static final private int MAXIMUM = 2000; |
| 272 | static final private int MIN_LOCATION = -MAXIMUM; |
| 273 | static final private int MAX_LOCATION = MAXIMUM; |
| 274 | static final private int MIN_SIZE = 10; |
| 275 | static final private int MAX_SIZE = MAXIMUM; |
| 276 | static final private Integer SPINNER_STEP = new Integer(1); |
| 277 | |
| 278 | final private int _previewSize; |
| 279 | final private boolean _realTimePreview; |
| 280 | |
| 281 | // Conversion between original space and thumbnail space |
| 282 | private AffineTransform _transformToPreview; |
| 283 | private AffineTransform _transformToOrigin; |
| 284 | |
| 285 | // Information about the board to be transformed (only thumbnail image is stored) |
| 286 | private BufferedImage _board; |
| 287 | private int _boardWidth; |
| 288 | private int _boardHeight; |
| 289 | private double _boardScaleRatio; |
| 290 | |
| 291 | final private ImageCompositor _compositor; |
| 292 | |
| 293 | private boolean _skipChangeEvent; |
| 294 | |
| 295 | // Currently selected image (scaled in same ratio as board preview) |
| 296 | private BufferedImage _selectedImage; |
| 297 | |
| 298 | // Settings are created only once on the heap |
| 299 | final private ImageToolSettings _displaySettings; |
| 300 | |
| 301 | // All Swing components in the panel (besides labels) |
| 302 | final private OutlineThumbnail _thbPreview; |
| 303 | final private SpinnerNumberModel _modelX = new SpinnerNumberModel(); |
| 304 | final private SpinnerNumberModel _modelY = new SpinnerNumberModel(); |
| 305 | final private SpinnerNumberModel _modelWidth = new SpinnerNumberModel(); |
| 306 | final private SpinnerNumberModel _modelHeight = new SpinnerNumberModel(); |
| 307 | } |