aboutsummaryrefslogtreecommitdiffstats
path: root/draw9patch
diff options
context:
space:
mode:
Diffstat (limited to 'draw9patch')
-rw-r--r--draw9patch/Android.mk17
-rw-r--r--draw9patch/MODULE_LICENSE_APACHE20
-rw-r--r--draw9patch/etc/Android.mk20
-rwxr-xr-xdraw9patch/etc/draw9patch63
-rwxr-xr-xdraw9patch/etc/draw9patch.bat41
-rw-r--r--draw9patch/etc/manifest.txt2
-rw-r--r--draw9patch/src/Android.mk26
-rw-r--r--draw9patch/src/com/android/draw9patch/Application.java54
-rw-r--r--draw9patch/src/com/android/draw9patch/graphics/GraphicsUtilities.java96
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/GradientPanel.java47
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java1129
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java63
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/MainFrame.java164
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/OpenFilePanel.java51
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/PngFileFilter.java32
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/action/BackgroundAction.java29
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/action/ExitAction.java44
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/action/OpenAction.java43
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/action/SaveAction.java43
-rw-r--r--draw9patch/src/resources/images/checker.pngbin0 -> 1889 bytes
-rw-r--r--draw9patch/src/resources/images/drop.pngbin0 -> 5479 bytes
21 files changed, 1964 insertions, 0 deletions
diff --git a/draw9patch/Android.mk b/draw9patch/Android.mk
new file mode 100644
index 0000000..934495d
--- /dev/null
+++ b/draw9patch/Android.mk
@@ -0,0 +1,17 @@
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+DRAW9PATCH_LOCAL_DIR := $(call my-dir)
+include $(DRAW9PATCH_LOCAL_DIR)/etc/Android.mk
+include $(DRAW9PATCH_LOCAL_DIR)/src/Android.mk
diff --git a/draw9patch/MODULE_LICENSE_APACHE2 b/draw9patch/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/draw9patch/MODULE_LICENSE_APACHE2
diff --git a/draw9patch/etc/Android.mk b/draw9patch/etc/Android.mk
new file mode 100644
index 0000000..8d7d080
--- /dev/null
+++ b/draw9patch/etc/Android.mk
@@ -0,0 +1,20 @@
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_EXECUTABLES := draw9patch
+include $(BUILD_HOST_PREBUILT)
+
diff --git a/draw9patch/etc/draw9patch b/draw9patch/etc/draw9patch
new file mode 100755
index 0000000..5d272a6
--- /dev/null
+++ b/draw9patch/etc/draw9patch
@@ -0,0 +1,63 @@
+#!/bin/sh
+# Copyright 2008, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=draw9patch.jar
+frameworkdir="$progdir"
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/tools/lib
+ libdir=`dirname "$progdir"`/tools/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/framework
+ libdir=`dirname "$progdir"`/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ echo `basename "$prog"`": can't find $jarfile"
+ exit 1
+fi
+
+if [ "$OSTYPE" = "cygwin" ] ; then
+ jarpath=`cygpath -w "$frameworkdir/$jarfile"`
+ progdir=`cygpath -w "$progdir"`
+else
+ jarpath="$frameworkdir/$jarfile"
+fi
+
+# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
+# might need more memory, e.g. -Xmx128M
+exec java -Djava.ext.dirs="$frameworkdir" -jar "$jarpath" "$@"
diff --git a/draw9patch/etc/draw9patch.bat b/draw9patch/etc/draw9patch.bat
new file mode 100755
index 0000000..e267b06
--- /dev/null
+++ b/draw9patch/etc/draw9patch.bat
@@ -0,0 +1,41 @@
+@echo off
+rem Copyright (C) 2008 The Android Open Source Project
+rem
+rem Licensed under the Apache License, Version 2.0 (the "License");
+rem you may not use this file except in compliance with the License.
+rem You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+rem don't modify the caller's environment
+setlocal
+
+rem Set up prog to be the path of this script, including following symlinks,
+rem and set up progdir to be the fully-qualified pathname of its directory.
+set prog=%~f0
+
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
+
+set jarfile=draw9patch.jar
+set frameworkdir=
+set libdir=
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=lib\
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=..\framework\
+
+:JarFileOk
+
+set jarpath=%frameworkdir%%jarfile%
+
+call java -Djava.ext.dirs=%frameworkdir% -jar %jarpath% %*
diff --git a/draw9patch/etc/manifest.txt b/draw9patch/etc/manifest.txt
new file mode 100644
index 0000000..b2e3528
--- /dev/null
+++ b/draw9patch/etc/manifest.txt
@@ -0,0 +1,2 @@
+Main-Class: com.android.draw9patch.Application
+Class-Path: swing-worker-1.1.jar
diff --git a/draw9patch/src/Android.mk b/draw9patch/src/Android.mk
new file mode 100644
index 0000000..3dc9db4
--- /dev/null
+++ b/draw9patch/src/Android.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_JAVA_RESOURCE_DIRS := resources
+
+LOCAL_JAR_MANIFEST := ../etc/manifest.txt
+LOCAL_JAVA_LIBRARIES := \
+ swing-worker-1.1
+LOCAL_MODULE := draw9patch
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/draw9patch/src/com/android/draw9patch/Application.java b/draw9patch/src/com/android/draw9patch/Application.java
new file mode 100644
index 0000000..c7c6aaf
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/Application.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch;
+
+import com.android.draw9patch.ui.MainFrame;
+
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+
+public class Application {
+ private static void initUserInterface() {
+ System.setProperty("apple.laf.useScreenMenuBar", "true");
+ System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Draw 9-patch");
+
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (UnsupportedLookAndFeelException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String... args) {
+ initUserInterface();
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ MainFrame frame = new MainFrame();
+ frame.setDefaultCloseOperation(MainFrame.EXIT_ON_CLOSE);
+ frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
+ }
+ });
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/graphics/GraphicsUtilities.java b/draw9patch/src/com/android/draw9patch/graphics/GraphicsUtilities.java
new file mode 100644
index 0000000..c6c182c
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/graphics/GraphicsUtilities.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.graphics;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsEnvironment;
+import java.awt.Graphics;
+import java.awt.Transparency;
+import java.net.URL;
+import java.io.IOException;
+
+public class GraphicsUtilities {
+ public static BufferedImage loadCompatibleImage(URL resource) throws IOException {
+ BufferedImage image = ImageIO.read(resource);
+ return toCompatibleImage(image);
+ }
+
+ public static BufferedImage createCompatibleImage(int width, int height) {
+ return getGraphicsConfiguration().createCompatibleImage(width, height);
+ }
+
+ public static BufferedImage toCompatibleImage(BufferedImage image) {
+ if (isHeadless()) {
+ return image;
+ }
+
+ if (image.getColorModel().equals(getGraphicsConfiguration().getColorModel())) {
+ return image;
+ }
+
+ BufferedImage compatibleImage = getGraphicsConfiguration().createCompatibleImage(
+ image.getWidth(), image.getHeight(), image.getTransparency());
+ Graphics g = compatibleImage.getGraphics();
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+
+ return compatibleImage;
+ }
+
+ public static BufferedImage createCompatibleImage(BufferedImage image, int width, int height) {
+ return getGraphicsConfiguration().createCompatibleImage(width, height,
+ image.getTransparency());
+ }
+
+ private static GraphicsConfiguration getGraphicsConfiguration() {
+ GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ return environment.getDefaultScreenDevice().getDefaultConfiguration();
+ }
+
+ private static boolean isHeadless() {
+ return GraphicsEnvironment.isHeadless();
+ }
+
+ public static BufferedImage createTranslucentCompatibleImage(int width, int height) {
+ return getGraphicsConfiguration().createCompatibleImage(width, height,
+ Transparency.TRANSLUCENT);
+ }
+
+ public static int[] getPixels(BufferedImage img, int x, int y, int w, int h, int[] pixels) {
+ if (w == 0 || h == 0) {
+ return new int[0];
+ }
+
+ if (pixels == null) {
+ pixels = new int[w * h];
+ } else if (pixels.length < w * h) {
+ throw new IllegalArgumentException("Pixels array must have a length >= w * h");
+ }
+
+ int imageType = img.getType();
+ if (imageType == BufferedImage.TYPE_INT_ARGB || imageType == BufferedImage.TYPE_INT_RGB) {
+ Raster raster = img.getRaster();
+ return (int[]) raster.getDataElements(x, y, w, h, pixels);
+ }
+
+ // Unmanages the image
+ return img.getRGB(x, y, w, h, pixels, 0, w);
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/GradientPanel.java b/draw9patch/src/com/android/draw9patch/ui/GradientPanel.java
new file mode 100644
index 0000000..bc1465f
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/GradientPanel.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.ui;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.GradientPaint;
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.awt.BorderLayout;
+import javax.swing.JPanel;
+
+class GradientPanel extends JPanel {
+ private static final int DARK_BLUE = 0x202737;
+
+ GradientPanel() {
+ super(new BorderLayout());
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2 = (Graphics2D) g;
+ Rectangle clip = g2.getClipBounds();
+ Paint paint = g2.getPaint();
+
+ g2.setPaint(new GradientPaint(0.0f, getHeight() * 0.22f, new Color(DARK_BLUE),
+ 0.0f, getHeight() * 0.9f, Color.BLACK));
+ g2.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ g2.setPaint(paint);
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
new file mode 100644
index 0000000..86c801f
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
@@ -0,0 +1,1129 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.ui;
+
+import com.android.draw9patch.graphics.GraphicsUtilities;
+
+import javax.swing.JPanel;
+import javax.swing.JLabel;
+import javax.swing.BorderFactory;
+import javax.swing.JSlider;
+import javax.swing.JComponent;
+import javax.swing.JScrollPane;
+import javax.swing.JCheckBox;
+import javax.swing.Box;
+import javax.swing.JFileChooser;
+import javax.swing.JSplitPane;
+import javax.swing.JButton;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.Graphics2D;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Dimension;
+import java.awt.TexturePaint;
+import java.awt.Shape;
+import java.awt.BasicStroke;
+import java.awt.RenderingHints;
+import java.awt.Rectangle;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.awt.AWTEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.AWTEventListener;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Area;
+import java.awt.geom.RoundRectangle2D;
+import java.io.IOException;
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+class ImageEditorPanel extends JPanel {
+ private static final String EXTENSION_9PATCH = ".9.png";
+ private static final int DEFAULT_ZOOM = 8;
+ private static final float DEFAULT_SCALE = 2.0f;
+
+ private String name;
+ private BufferedImage image;
+ private boolean is9Patch;
+
+ private ImageViewer viewer;
+ private StretchesViewer stretchesViewer;
+ private JLabel xLabel;
+ private JLabel yLabel;
+
+ private TexturePaint texture;
+
+ private List<Rectangle> patches;
+ private List<Rectangle> horizontalPatches;
+ private List<Rectangle> verticalPatches;
+ private List<Rectangle> fixed;
+ private boolean verticalStartWithPatch;
+ private boolean horizontalStartWithPatch;
+
+ private Pair<Integer> horizontalPadding;
+ private Pair<Integer> verticalPadding;
+
+ ImageEditorPanel(MainFrame mainFrame, BufferedImage image, String name) {
+ this.image = image;
+ this.name = name;
+
+ setTransferHandler(new ImageTransferHandler(mainFrame));
+
+ checkImage();
+
+ setOpaque(false);
+ setLayout(new BorderLayout());
+
+ loadSupport();
+ buildImageViewer();
+ buildStatusPanel();
+ }
+
+ private void loadSupport() {
+ try {
+ URL resource = getClass().getResource("/images/checker.png");
+ BufferedImage checker = GraphicsUtilities.loadCompatibleImage(resource);
+ texture = new TexturePaint(checker, new Rectangle2D.Double(0, 0,
+ checker.getWidth(), checker.getHeight()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void buildImageViewer() {
+ viewer = new ImageViewer();
+
+ JSplitPane splitter = new JSplitPane();
+ splitter.setContinuousLayout(true);
+ splitter.setResizeWeight(0.8);
+ splitter.setBorder(null);
+
+ JScrollPane scroller = new JScrollPane(viewer);
+ scroller.setOpaque(false);
+ scroller.setBorder(null);
+ scroller.getViewport().setBorder(null);
+ scroller.getViewport().setOpaque(false);
+
+ splitter.setLeftComponent(scroller);
+ splitter.setRightComponent(buildStretchesViewer());
+
+ add(splitter);
+ }
+
+ private JComponent buildStretchesViewer() {
+ stretchesViewer = new StretchesViewer();
+ JScrollPane scroller = new JScrollPane(stretchesViewer);
+ scroller.setBorder(null);
+ scroller.getViewport().setBorder(null);
+ scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ return scroller;
+ }
+
+ private void buildStatusPanel() {
+ JPanel status = new JPanel(new GridBagLayout());
+ status.setOpaque(false);
+
+ JLabel label = new JLabel();
+ label.setForeground(Color.WHITE);
+ label.setText("Zoom: ");
+ label.putClientProperty("JComponent.sizeVariant", "small");
+ status.add(label, new GridBagConstraints(0, 0, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_END, GridBagConstraints.NONE,
+ new Insets(0, 6, 0, 0), 0, 0));
+
+ label = new JLabel();
+ label.setForeground(Color.WHITE);
+ label.setText("100%");
+ label.putClientProperty("JComponent.sizeVariant", "small");
+ status.add(label, new GridBagConstraints(1, 0, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_END, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ JSlider zoomSlider = new JSlider(1, 16, DEFAULT_ZOOM);
+ zoomSlider.setSnapToTicks(true);
+ zoomSlider.putClientProperty("JComponent.sizeVariant", "small");
+ zoomSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent evt) {
+ viewer.setZoom(((JSlider) evt.getSource()).getValue());
+ }
+ });
+ status.add(zoomSlider, new GridBagConstraints(2, 0, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_START, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ JLabel maxZoomLabel = new JLabel();
+ maxZoomLabel.setForeground(Color.WHITE);
+ maxZoomLabel.putClientProperty("JComponent.sizeVariant", "small");
+ maxZoomLabel.setText("800%");
+ status.add(maxZoomLabel, new GridBagConstraints(3, 0, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_START, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ label = new JLabel();
+ label.setForeground(Color.WHITE);
+ label.setText("Patch scale: ");
+ label.putClientProperty("JComponent.sizeVariant", "small");
+ status.add(label, new GridBagConstraints(0, 1, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_START, GridBagConstraints.NONE,
+ new Insets(0, 6, 0, 0), 0, 0));
+
+ label = new JLabel();
+ label.setForeground(Color.WHITE);
+ label.setText("2x");
+ label.putClientProperty("JComponent.sizeVariant", "small");
+ status.add(label, new GridBagConstraints(1, 1, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_END, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ zoomSlider = new JSlider(200, 600, (int) (DEFAULT_SCALE * 100.0f));
+ zoomSlider.setSnapToTicks(true);
+ zoomSlider.putClientProperty("JComponent.sizeVariant", "small");
+ zoomSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent evt) {
+ stretchesViewer.setScale(((JSlider) evt.getSource()).getValue() / 100.0f);
+ }
+ });
+ status.add(zoomSlider, new GridBagConstraints(2, 1, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_START, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ maxZoomLabel = new JLabel();
+ maxZoomLabel.setForeground(Color.WHITE);
+ maxZoomLabel.putClientProperty("JComponent.sizeVariant", "small");
+ maxZoomLabel.setText("6x");
+ status.add(maxZoomLabel, new GridBagConstraints(3, 1, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_START, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ JCheckBox showLock = new JCheckBox("Show lock");
+ showLock.setOpaque(false);
+ showLock.setForeground(Color.WHITE);
+ showLock.setSelected(true);
+ showLock.putClientProperty("JComponent.sizeVariant", "small");
+ showLock.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ viewer.setLockVisible(((JCheckBox) event.getSource()).isSelected());
+ }
+ });
+ status.add(showLock, new GridBagConstraints(4, 0, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_START, GridBagConstraints.NONE,
+ new Insets(0, 12, 0, 0), 0, 0));
+
+ JCheckBox showPatches = new JCheckBox("Show patches");
+ showPatches.setOpaque(false);
+ showPatches.setForeground(Color.WHITE);
+ showPatches.putClientProperty("JComponent.sizeVariant", "small");
+ showPatches.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ viewer.setPatchesVisible(((JCheckBox) event.getSource()).isSelected());
+ }
+ });
+ status.add(showPatches, new GridBagConstraints(4, 1, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_START, GridBagConstraints.NONE,
+ new Insets(0, 12, 0, 0), 0, 0));
+
+ JCheckBox showPadding = new JCheckBox("Show content");
+ showPadding.setOpaque(false);
+ showPadding.setForeground(Color.WHITE);
+ showPadding.putClientProperty("JComponent.sizeVariant", "small");
+ showPadding.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ stretchesViewer.setPaddingVisible(((JCheckBox) event.getSource()).isSelected());
+ }
+ });
+ status.add(showPadding, new GridBagConstraints(5, 0, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_START, GridBagConstraints.NONE,
+ new Insets(0, 12, 0, 0), 0, 0));
+
+ status.add(Box.createHorizontalGlue(), new GridBagConstraints(6, 0, 1, 1, 1.0f, 1.0f,
+ GridBagConstraints.LINE_START, GridBagConstraints.BOTH,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ label = new JLabel("X: ");
+ label.setForeground(Color.WHITE);
+ label.putClientProperty("JComponent.sizeVariant", "small");
+ status.add(label, new GridBagConstraints(7, 0, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_END, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ xLabel = new JLabel("0px");
+ xLabel.setForeground(Color.WHITE);
+ xLabel.putClientProperty("JComponent.sizeVariant", "small");
+ status.add(xLabel, new GridBagConstraints(8, 0, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_END, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 6), 0, 0));
+
+ label = new JLabel("Y: ");
+ label.setForeground(Color.WHITE);
+ label.putClientProperty("JComponent.sizeVariant", "small");
+ status.add(label, new GridBagConstraints(7, 1, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_END, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ yLabel = new JLabel("0px");
+ yLabel.setForeground(Color.WHITE);
+ yLabel.putClientProperty("JComponent.sizeVariant", "small");
+ status.add(yLabel, new GridBagConstraints(8, 1, 1, 1, 0.0f, 0.0f,
+ GridBagConstraints.LINE_END, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 6), 0, 0));
+
+ add(status, BorderLayout.SOUTH);
+ }
+
+ private void checkImage() {
+ is9Patch = name.endsWith(EXTENSION_9PATCH);
+ if (!is9Patch) {
+ convertTo9Patch();
+ } else {
+ ensure9Patch();
+ }
+ }
+
+ private void ensure9Patch() {
+ int width = image.getWidth();
+ int height = image.getHeight();
+ for (int i = 0; i < width; i++) {
+ int pixel = image.getRGB(i, 0);
+ if (pixel != 0 && pixel != 0xFF000000) {
+ image.setRGB(i, 0, 0);
+ }
+ pixel = image.getRGB(i, height - 1);
+ if (pixel != 0 && pixel != 0xFF000000) {
+ image.setRGB(i, height - 1, 0);
+ }
+ }
+ for (int i = 0; i < height; i++) {
+ int pixel = image.getRGB(0, i);
+ if (pixel != 0 && pixel != 0xFF000000) {
+ image.setRGB(0, i, 0);
+ }
+ pixel = image.getRGB(width - 1, i);
+ if (pixel != 0 && pixel != 0xFF000000) {
+ image.setRGB(width - 1, i, 0);
+ }
+ }
+ }
+
+ private void convertTo9Patch() {
+ BufferedImage buffer = GraphicsUtilities.createTranslucentCompatibleImage(
+ image.getWidth() + 2, image.getHeight() + 2);
+
+ Graphics2D g2 = buffer.createGraphics();
+ g2.drawImage(image, 1, 1, null);
+ g2.dispose();
+
+ image = buffer;
+ name = name.substring(0, name.lastIndexOf('.')) + ".9.png";
+ }
+
+ File chooseSaveFile() {
+ if (is9Patch) {
+ return new File(name);
+ } else {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setFileFilter(new PngFileFilter());
+ int choice = chooser.showSaveDialog(this);
+ if (choice == JFileChooser.APPROVE_OPTION) {
+ File file = chooser.getSelectedFile();
+ if (!file.getAbsolutePath().endsWith(EXTENSION_9PATCH)) {
+ String path = file.getAbsolutePath();
+ if (path.endsWith(".png")) {
+ path = path.substring(0, path.lastIndexOf(".png")) + EXTENSION_9PATCH;
+ } else {
+ path = path + EXTENSION_9PATCH;
+ }
+ name = path;
+ is9Patch = true;
+ return new File(path);
+ }
+ is9Patch = true;
+ return file;
+ }
+ }
+ return null;
+ }
+
+ RenderedImage getImage() {
+ return image;
+ }
+
+ private class StretchesViewer extends JPanel {
+ private static final int MARGIN = 24;
+
+ private StretchView horizontal;
+ private StretchView vertical;
+ private StretchView both;
+
+ private Dimension size;
+
+ private float horizontalPatchesSum;
+ private float verticalPatchesSum;
+
+ private boolean showPadding;
+
+ StretchesViewer() {
+ setOpaque(false);
+ setLayout(new GridBagLayout());
+ setBorder(BorderFactory.createEmptyBorder(MARGIN, MARGIN, MARGIN, MARGIN));
+
+ horizontal = new StretchView();
+ vertical = new StretchView();
+ both = new StretchView();
+
+ setScale(DEFAULT_SCALE);
+
+ add(vertical, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
+ GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+ add(horizontal, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
+ GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+ add(both, new GridBagConstraints(0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
+ GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2 = (Graphics2D) g.create();
+ g2.setPaint(texture);
+ g2.fillRect(0, 0, getWidth(), getHeight());
+ g2.dispose();
+ }
+
+ void setScale(float scale) {
+ int patchWidth = image.getWidth() - 2;
+ int patchHeight = image.getHeight() - 2;
+
+ int scaledWidth = (int) (patchWidth * scale);
+ int scaledHeight = (int) (patchHeight * scale);
+
+ horizontal.scaledWidth = scaledWidth;
+ vertical.scaledHeight = scaledHeight;
+ both.scaledWidth = scaledWidth;
+ both.scaledHeight = scaledHeight;
+
+ size = new Dimension(scaledWidth, scaledHeight);
+
+ computePatches();
+ }
+
+ void computePatches() {
+ boolean measuredWidth = false;
+ boolean endRow = true;
+
+ int remainderHorizontal = 0;
+ int remainderVertical = 0;
+
+ if (fixed.size() > 0) {
+ int start = fixed.get(0).y;
+ for (Rectangle rect : fixed) {
+ if (rect.y > start) {
+ endRow = true;
+ measuredWidth = true;
+ }
+ if (!measuredWidth) {
+ remainderHorizontal += rect.width;
+ }
+ if (endRow) {
+ remainderVertical += rect.height;
+ endRow = false;
+ start = rect.y;
+ }
+ }
+ }
+
+ horizontal.remainderHorizontal = horizontal.scaledWidth - remainderHorizontal;
+ vertical.remainderHorizontal = vertical.scaledWidth - remainderHorizontal;
+ both.remainderHorizontal = both.scaledWidth - remainderHorizontal;
+
+ horizontal.remainderVertical = horizontal.scaledHeight - remainderVertical;
+ vertical.remainderVertical = vertical.scaledHeight - remainderVertical;
+ both.remainderVertical = both.scaledHeight - remainderVertical;
+
+ horizontalPatchesSum = 0;
+ if (horizontalPatches.size() > 0) {
+ int start = -1;
+ for (Rectangle rect : horizontalPatches) {
+ if (rect.x > start) {
+ horizontalPatchesSum += rect.width;
+ start = rect.x;
+ }
+ }
+ }
+
+ verticalPatchesSum = 0;
+ if (verticalPatches.size() > 0) {
+ int start = -1;
+ for (Rectangle rect : verticalPatches) {
+ if (rect.y > start) {
+ verticalPatchesSum += rect.height;
+ start = rect.y;
+ }
+ }
+ }
+
+ setSize(size);
+ ImageEditorPanel.this.validate();
+ repaint();
+ }
+
+ void setPaddingVisible(boolean visible) {
+ showPadding = visible;
+ repaint();
+ }
+
+ private class StretchView extends JComponent {
+ private final Color PADDING_COLOR = new Color(0.37f, 0.37f, 1.0f, 0.5f);
+
+ int scaledWidth;
+ int scaledHeight;
+
+ int remainderHorizontal;
+ int remainderVertical;
+
+ StretchView() {
+ scaledWidth = image.getWidth();
+ scaledHeight = image.getHeight();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ int x = (getWidth() - scaledWidth) / 2;
+ int y = (getHeight() - scaledHeight) / 2;
+
+ Graphics2D g2 = (Graphics2D) g.create();
+ g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ g.translate(x, y);
+
+ x = 0;
+ y = 0;
+
+ if (patches.size() == 0 || horizontalPatches.size() == 0 ||
+ verticalPatches.size() == 0) {
+ g.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);
+ g2.dispose();
+ return;
+ }
+
+ int fixedIndex = 0;
+ int horizontalIndex = 0;
+ int verticalIndex = 0;
+ int patchIndex = 0;
+
+ boolean hStretch;
+ boolean vStretch;
+
+ float vWeightSum = 1.0f;
+ float vRemainder = remainderVertical;
+
+ vStretch = verticalStartWithPatch;
+ while (y < scaledHeight - 1) {
+ hStretch = horizontalStartWithPatch;
+
+ int height = 0;
+ float vExtra = 0.0f;
+
+ float hWeightSum = 1.0f;
+ float hRemainder = remainderHorizontal;
+
+ while (x < scaledWidth - 1) {
+ Rectangle r;
+ if (!vStretch) {
+ if (hStretch) {
+ r = horizontalPatches.get(horizontalIndex++);
+ float extra = r.width / horizontalPatchesSum;
+ int width = (int) (extra * hRemainder / hWeightSum);
+ hWeightSum -= extra;
+ hRemainder -= width;
+ g.drawImage(image, x, y, x + width, y + r.height, r.x, r.y,
+ r.x + r.width, r.y + r.height, null);
+ x += width;
+ } else {
+ r = fixed.get(fixedIndex++);
+ g.drawImage(image, x, y, x + r.width, y + r.height, r.x, r.y,
+ r.x + r.width, r.y + r.height, null);
+ x += r.width;
+ }
+ height = r.height;
+ } else {
+ if (hStretch) {
+ r = patches.get(patchIndex++);
+ vExtra = r.height / verticalPatchesSum;
+ height = (int) (vExtra * vRemainder / vWeightSum);
+ float extra = r.width / horizontalPatchesSum;
+ int width = (int) (extra * hRemainder / hWeightSum);
+ hWeightSum -= extra;
+ hRemainder -= width;
+ g.drawImage(image, x, y, x + width, y + height, r.x, r.y,
+ r.x + r.width, r.y + r.height, null);
+ x += width;
+ } else {
+ r = verticalPatches.get(verticalIndex++);
+ vExtra = r.height / verticalPatchesSum;
+ height = (int) (vExtra * vRemainder / vWeightSum);
+ g.drawImage(image, x, y, x + r.width, y + height, r.x, r.y,
+ r.x + r.width, r.y + r.height, null);
+ x += r.width;
+ }
+
+ }
+ hStretch = !hStretch;
+ }
+ x = 0;
+ y += height;
+ if (vStretch) {
+ vWeightSum -= vExtra;
+ vRemainder -= height;
+ }
+ vStretch = !vStretch;
+ }
+
+ if (showPadding) {
+ g.setColor(PADDING_COLOR);
+ g.fillRect(horizontalPadding.first, verticalPadding.first,
+ scaledWidth - horizontalPadding.first - horizontalPadding.second,
+ scaledHeight - verticalPadding.first - verticalPadding.second);
+ }
+
+ g2.dispose();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return size;
+ }
+ }
+ }
+
+ private class ImageViewer extends JComponent {
+ private final Color CORRUPTED_COLOR = new Color(1.0f, 0.0f, 0.0f, 0.7f);
+ private final Color LOCK_COLOR = new Color(0.0f, 0.0f, 0.0f, 0.7f);
+ private final Color STRIPES_COLOR = new Color(1.0f, 0.0f, 0.0f, 0.5f);
+ private final Color BACK_COLOR = new Color(0xc0c0c0);
+ private final Color HELP_COLOR = new Color(0xffffe1);
+ private final Color PATCH_COLOR = new Color(1.0f, 0.37f, 0.99f, 0.5f);
+ private final Color PATCH_ONEWAY_COLOR = new Color(0.37f, 1.0f, 0.37f, 0.5f);
+
+ private static final float STRIPES_WIDTH = 4.0f;
+ private static final double STRIPES_SPACING = 6.0;
+ private static final int STRIPES_ANGLE = 45;
+
+ private int zoom;
+ private boolean showPatches;
+ private boolean showLock = true;
+
+ private Dimension size;
+
+ private boolean locked;
+
+ private int[] row;
+ private int[] column;
+
+ private int lastPositionX;
+ private int lastPositionY;
+ private boolean showCursor;
+
+ private JLabel helpLabel;
+ private boolean eraseMode;
+
+ private JButton checkButton;
+ private List<Rectangle> corruptedPatches;
+ private boolean showBadPatches;
+
+ private JPanel helpPanel;
+
+ ImageViewer() {
+ setLayout(new GridBagLayout());
+ helpPanel = new JPanel(new BorderLayout());
+ helpPanel.setBorder(new EmptyBorder(0, 6, 0, 6));
+ helpPanel.setBackground(HELP_COLOR);
+ helpLabel = new JLabel("Press Shift to erase pixels");
+ helpLabel.putClientProperty("JComponent.sizeVariant", "small");
+ helpPanel.add(helpLabel, BorderLayout.WEST);
+ checkButton = new JButton("Show bad patches");
+ checkButton.putClientProperty("JComponent.sizeVariant", "small");
+ checkButton.putClientProperty("JButton.buttonType", "roundRect");
+ helpPanel.add(checkButton, BorderLayout.EAST);
+
+ add(helpPanel, new GridBagConstraints(0, 0, 1, 1,
+ 1.0f, 1.0f, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ setOpaque(true);
+
+ setZoom(DEFAULT_ZOOM);
+ findPatches();
+
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent event) {
+ paint(event.getX(), event.getY(), event.isShiftDown() ? MouseEvent.BUTTON3 :
+ event.getButton());
+ }
+ });
+ addMouseMotionListener(new MouseMotionAdapter() {
+ @Override
+ public void mouseDragged(MouseEvent event) {
+ if (!checkLockedRegion(event.getX(), event.getY())) {
+ paint(event.getX(), event.getY(), event.isShiftDown() ? MouseEvent.BUTTON3 :
+ event.getButton());
+ }
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent event) {
+ checkLockedRegion(event.getX(), event.getY());
+ }
+ });
+ Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
+ public void eventDispatched(AWTEvent event) {
+ enableEraseMode((KeyEvent) event);
+ }
+ }, AWTEvent.KEY_EVENT_MASK);
+
+ checkButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ if (!showBadPatches) {
+ findBadPatches();
+ checkButton.setText("Hide bad patches");
+ } else {
+ checkButton.setText("Show bad patches");
+ corruptedPatches = null;
+ }
+ repaint();
+ showBadPatches = !showBadPatches;
+ }
+ });
+ }
+
+ private void findBadPatches() {
+ corruptedPatches = new ArrayList<Rectangle>();
+
+ for (Rectangle patch : patches) {
+ if (corruptPatch(patch)) {
+ corruptedPatches.add(patch);
+ }
+ }
+
+ for (Rectangle patch : horizontalPatches) {
+ if (corruptHorizontalPatch(patch)) {
+ corruptedPatches.add(patch);
+ }
+ }
+
+ for (Rectangle patch : verticalPatches) {
+ if (corruptVerticalPatch(patch)) {
+ corruptedPatches.add(patch);
+ }
+ }
+ }
+
+ private boolean corruptPatch(Rectangle patch) {
+ int[] pixels = GraphicsUtilities.getPixels(image, patch.x, patch.y,
+ patch.width, patch.height, null);
+
+ if (pixels.length > 0) {
+ int reference = pixels[0];
+ for (int pixel : pixels) {
+ if (pixel != reference) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean corruptHorizontalPatch(Rectangle patch) {
+ int[] reference = new int[patch.height];
+ int[] column = new int[patch.height];
+ reference = GraphicsUtilities.getPixels(image, patch.x, patch.y,
+ 1, patch.height, reference);
+
+ for (int i = 1; i < patch.width; i++) {
+ column = GraphicsUtilities.getPixels(image, patch.x + i, patch.y,
+ 1, patch.height, column);
+ if (!Arrays.equals(reference, column)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean corruptVerticalPatch(Rectangle patch) {
+ int[] reference = new int[patch.width];
+ int[] row = new int[patch.width];
+ reference = GraphicsUtilities.getPixels(image, patch.x, patch.y,
+ patch.width, 1, reference);
+
+ for (int i = 1; i < patch.height; i++) {
+ row = GraphicsUtilities.getPixels(image, patch.x, patch.y + i, patch.width, 1, row);
+ if (!Arrays.equals(reference, row)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void enableEraseMode(KeyEvent event) {
+ boolean oldEraseMode = eraseMode;
+ eraseMode = event.isShiftDown();
+ if (eraseMode != oldEraseMode) {
+ if (eraseMode) {
+ helpLabel.setText("Release Shift to draw pixels");
+ } else {
+ helpLabel.setText("Press Shift to erase pixels");
+ }
+ }
+ }
+
+ private void paint(int x, int y, int button) {
+ int color;
+ switch (button) {
+ case MouseEvent.BUTTON1:
+ color = 0xFF000000;
+ break;
+ case MouseEvent.BUTTON3:
+ color = 0;
+ break;
+ default:
+ return;
+ }
+
+ int left = (getWidth() - size.width) / 2;
+ int top = (helpPanel.getHeight() + getHeight() - size.height) / 2;
+
+ x = (x - left) / zoom;
+ y = (y - top) / zoom;
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+ if (((x == 0 || x == width - 1) && (y > 0 && y < height - 1)) ||
+ ((x > 0 && x < width - 1) && (y == 0 || y == height - 1))) {
+ image.setRGB(x, y, color);
+ findPatches();
+ stretchesViewer.computePatches();
+ if (showBadPatches) {
+ findBadPatches();
+ }
+ repaint();
+ }
+ }
+
+ private boolean checkLockedRegion(int x, int y) {
+ int oldX = lastPositionX;
+ int oldY = lastPositionY;
+ lastPositionX = x;
+ lastPositionY = y;
+
+ int left = (getWidth() - size.width) / 2;
+ int top = (helpPanel.getHeight() + getHeight() - size.height) / 2;
+
+ x = (x - left) / zoom;
+ y = (y - top) / zoom;
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ xLabel.setText(Math.max(0, Math.min(x, width - 1)) + " px");
+ yLabel.setText(Math.max(0, Math.min(y, height - 1)) + " px");
+
+ boolean previousLock = locked;
+ locked = x > 0 && x < width - 1 && y > 0 && y < height - 1;
+
+ boolean previousCursor = showCursor;
+ showCursor = ((x == 0 || x == width - 1) && (y > 0 && y < height - 1)) ||
+ ((x > 0 && x < width - 1) && (y == 0 || y == height - 1));
+
+ if (locked != previousLock) {
+ repaint();
+ } else if (showCursor || (showCursor != previousCursor)) {
+ Rectangle clip = new Rectangle(lastPositionX - 1 - zoom / 2,
+ lastPositionY - 1 - zoom / 2, zoom + 2, zoom + 2);
+ clip = clip.union(new Rectangle(oldX - 1 - zoom / 2,
+ oldY - 1 - zoom / 2, zoom + 2, zoom + 2));
+ repaint(clip);
+ }
+
+ return locked;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ int x = (getWidth() - size.width) / 2;
+ int y = (helpPanel.getHeight() + getHeight() - size.height) / 2;
+
+ Graphics2D g2 = (Graphics2D) g.create();
+ g2.setColor(BACK_COLOR);
+ g2.fillRect(0, 0, getWidth(), getHeight());
+
+ g2.translate(x, y);
+ g2.setPaint(texture);
+ g2.fillRect(0, 0, size.width, size.height);
+ g2.scale(zoom, zoom);
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+ g2.drawImage(image, 0, 0, null);
+
+ if (showPatches) {
+ g2.setColor(PATCH_COLOR);
+ for (Rectangle patch : patches) {
+ g2.fillRect(patch.x, patch.y, patch.width, patch.height);
+ }
+ g2.setColor(PATCH_ONEWAY_COLOR);
+ for (Rectangle patch : horizontalPatches) {
+ g2.fillRect(patch.x, patch.y, patch.width, patch.height);
+ }
+ for (Rectangle patch : verticalPatches) {
+ g2.fillRect(patch.x, patch.y, patch.width, patch.height);
+ }
+ }
+
+ if (corruptedPatches != null) {
+ g2.setColor(CORRUPTED_COLOR);
+ g2.setStroke(new BasicStroke(3.0f / zoom));
+ for (Rectangle patch : corruptedPatches) {
+ g2.draw(new RoundRectangle2D.Float(patch.x - 2.0f / zoom, patch.y - 2.0f / zoom,
+ patch.width + 2.0f / zoom, patch.height + 2.0f / zoom,
+ 6.0f / zoom, 6.0f / zoom));
+ }
+ }
+
+ if (showLock && locked) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ g2.setColor(LOCK_COLOR);
+ g2.fillRect(1, 1, width - 2, height - 2);
+
+ g2.setColor(STRIPES_COLOR);
+ g2.translate(1, 1);
+ paintStripes(g2, width - 2, height - 2);
+ g2.translate(-1, -1);
+ }
+
+ g2.dispose();
+
+ if (showCursor) {
+ Graphics cursor = g.create();
+ cursor.setXORMode(Color.WHITE);
+ cursor.setColor(Color.BLACK);
+ cursor.drawRect(lastPositionX - zoom / 2, lastPositionY - zoom / 2, zoom, zoom);
+ cursor.dispose();
+ }
+ }
+
+ private void paintStripes(Graphics2D g, int width, int height) {
+ //draws pinstripes at the angle specified in this class
+ //and at the given distance apart
+ Shape oldClip = g.getClip();
+ Area area = new Area(new Rectangle(0, 0, width, height));
+ if(oldClip != null) {
+ area = new Area(oldClip);
+ }
+ area.intersect(new Area(new Rectangle(0,0,width,height)));
+ g.setClip(area);
+
+ g.setStroke(new BasicStroke(STRIPES_WIDTH));
+
+ double hypLength = Math.sqrt((width * width) +
+ (height * height));
+
+ double radians = Math.toRadians(STRIPES_ANGLE);
+ g.rotate(radians);
+
+ double spacing = STRIPES_SPACING;
+ spacing += STRIPES_WIDTH;
+ int numLines = (int)(hypLength / spacing);
+
+ for (int i=0; i<numLines; i++) {
+ double x = i * spacing;
+ Line2D line = new Line2D.Double(x, -hypLength, x, hypLength);
+ g.draw(line);
+ }
+ g.setClip(oldClip);
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return size;
+ }
+
+ void setZoom(int value) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ zoom = value;
+ size = new Dimension(width * zoom, height * zoom);
+
+ setSize(size);
+ ImageEditorPanel.this.validate();
+ repaint();
+ }
+
+ void setPatchesVisible(boolean visible) {
+ showPatches = visible;
+ findPatches();
+ repaint();
+ }
+
+ private void findPatches() {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ row = GraphicsUtilities.getPixels(image, 0, 0, width, 1, row);
+ column = GraphicsUtilities.getPixels(image, 0, 0, 1, height, column);
+
+ boolean[] result = new boolean[1];
+ Pair<List<Pair<Integer>>> left = getPatches(column, result);
+ verticalStartWithPatch = result[0];
+
+ result = new boolean[1];
+ Pair<List<Pair<Integer>>> top = getPatches(row, result);
+ horizontalStartWithPatch = result[0];
+
+ fixed = getRectangles(left.first, top.first);
+ patches = getRectangles(left.second, top.second);
+
+ if (fixed.size() > 0) {
+ horizontalPatches = getRectangles(left.first, top.second);
+ verticalPatches = getRectangles(left.second, top.first);
+ } else {
+ horizontalPatches = verticalPatches = new ArrayList<Rectangle>(0);
+ }
+
+ row = GraphicsUtilities.getPixels(image, 0, height - 1, width, 1, row);
+ column = GraphicsUtilities.getPixels(image, width - 1, 0, 1, height, column);
+
+ top = getPatches(row, result);
+ horizontalPadding = getPadding(top.first);
+
+ left = getPatches(column, result);
+ verticalPadding = getPadding(left.first);
+ }
+
+ private Pair<Integer> getPadding(List<Pair<Integer>> pairs) {
+ if (pairs.size() == 0) {
+ return new Pair<Integer>(0, 0);
+ } else if (pairs.size() == 1) {
+ if (pairs.get(0).first == 1) {
+ return new Pair<Integer>(pairs.get(0).second - pairs.get(0).first, 0);
+ } else {
+ return new Pair<Integer>(0, pairs.get(0).second - pairs.get(0).first);
+ }
+ } else {
+ int index = pairs.size() - 1;
+ return new Pair<Integer>(pairs.get(0).second - pairs.get(0).first,
+ pairs.get(index).second - pairs.get(index).first);
+ }
+ }
+
+ private List<Rectangle> getRectangles(List<Pair<Integer>> leftPairs,
+ List<Pair<Integer>> topPairs) {
+ List<Rectangle> rectangles = new ArrayList<Rectangle>();
+ for (Pair<Integer> left : leftPairs) {
+ int y = left.first;
+ int height = left.second - left.first;
+ for (Pair<Integer> top: topPairs) {
+ int x = top.first;
+ int width = top.second - top.first;
+
+ rectangles.add(new Rectangle(x, y, width, height));
+ }
+ }
+ return rectangles;
+ }
+
+ private Pair<List<Pair<Integer>>> getPatches(int[] pixels, boolean[] startWithPatch) {
+ int lastIndex = 1;
+ int lastPixel = pixels[1];
+ boolean first = true;
+
+ List<Pair<Integer>> fixed = new ArrayList<Pair<Integer>>();
+ List<Pair<Integer>> patches = new ArrayList<Pair<Integer>>();
+
+ for (int i = 1; i < pixels.length - 1; i++) {
+ int pixel = pixels[i];
+ if (pixel != lastPixel) {
+ if (lastPixel == 0xFF000000) {
+ if (first) startWithPatch[0] = true;
+ patches.add(new Pair<Integer>(lastIndex, i));
+ } else {
+ fixed.add(new Pair<Integer>(lastIndex, i));
+ }
+ first = false;
+
+ lastIndex = i;
+ lastPixel = pixel;
+ }
+ }
+ if (lastPixel == 0xFF000000) {
+ if (first) startWithPatch[0] = true;
+ patches.add(new Pair<Integer>(lastIndex, pixels.length - 1));
+ } else {
+ fixed.add(new Pair<Integer>(lastIndex, pixels.length - 1));
+ }
+
+ if (patches.size() == 0) {
+ patches.add(new Pair<Integer>(1, pixels.length - 1));
+ startWithPatch[0] = true;
+ fixed.clear();
+ }
+ return new Pair<List<Pair<Integer>>>(fixed, patches);
+ }
+
+ void setLockVisible(boolean visible) {
+ showLock = visible;
+ repaint();
+ }
+ }
+
+ static class Pair<E> {
+ E first;
+ E second;
+
+ Pair(E first, E second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ @Override
+ public String toString() {
+ return "Pair[" + first + ", " + second + "]";
+ }
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java b/draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java
new file mode 100644
index 0000000..a62884f
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.ui;
+
+import javax.swing.TransferHandler;
+import javax.swing.JComponent;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.net.MalformedURLException;
+
+class ImageTransferHandler extends TransferHandler {
+ private final MainFrame mainFrame;
+
+ ImageTransferHandler(MainFrame mainFrame) {
+ this.mainFrame = mainFrame;
+ }
+
+ @Override
+ public boolean importData(JComponent component, Transferable transferable) {
+ try {
+ Object data = transferable.getTransferData(DataFlavor.javaFileListFlavor);
+ //noinspection unchecked
+ final File file = ((List<File>) data).get(0);
+ mainFrame.open(file).execute();
+ } catch (UnsupportedFlavorException e) {
+ return false;
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean canImport(JComponent component, DataFlavor[] dataFlavors) {
+ for (DataFlavor flavor : dataFlavors) {
+ if (flavor.isFlavorJavaFileListType()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/MainFrame.java b/draw9patch/src/com/android/draw9patch/ui/MainFrame.java
new file mode 100644
index 0000000..9ffd93e
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/MainFrame.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.ui;
+
+import com.android.draw9patch.ui.action.ExitAction;
+import com.android.draw9patch.ui.action.OpenAction;
+import com.android.draw9patch.ui.action.SaveAction;
+import com.android.draw9patch.graphics.GraphicsUtilities;
+
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.ActionMap;
+import javax.swing.JFileChooser;
+import javax.imageio.ImageIO;
+import java.awt.HeadlessException;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.concurrent.ExecutionException;
+
+import org.jdesktop.swingworker.SwingWorker;
+
+public class MainFrame extends JFrame {
+ private ActionMap actionsMap;
+ private JMenuItem saveMenuItem;
+ private ImageEditorPanel imageEditor;
+
+ public MainFrame() throws HeadlessException {
+ super("Draw 9-patch");
+
+ buildActions();
+ buildMenuBar();
+ buildContent();
+
+ showOpenFilePanel();
+
+ // pack();
+ setSize(1024, 600);
+ }
+
+ private void buildActions() {
+ actionsMap = new ActionMap();
+ actionsMap.put(OpenAction.ACTION_NAME, new OpenAction(this));
+ actionsMap.put(SaveAction.ACTION_NAME, new SaveAction(this));
+ actionsMap.put(ExitAction.ACTION_NAME, new ExitAction(this));
+ }
+
+ private void buildMenuBar() {
+ JMenu fileMenu = new JMenu("File");
+ JMenuItem openMenuItem = new JMenuItem();
+ saveMenuItem = new JMenuItem();
+ JMenuItem exitMenuItem = new JMenuItem();
+
+ openMenuItem.setAction(actionsMap.get(OpenAction.ACTION_NAME));
+ fileMenu.add(openMenuItem);
+
+ saveMenuItem.setAction(actionsMap.get(SaveAction.ACTION_NAME));
+ saveMenuItem.setEnabled(false);
+ fileMenu.add(saveMenuItem);
+
+ exitMenuItem.setAction(actionsMap.get(ExitAction.ACTION_NAME));
+ fileMenu.add(exitMenuItem);
+
+ JMenuBar menuBar = new JMenuBar();
+ menuBar.add(fileMenu);
+ setJMenuBar(menuBar);
+ }
+
+ private void buildContent() {
+ setContentPane(new GradientPanel());
+ }
+
+ private void showOpenFilePanel() {
+ add(new OpenFilePanel(this));
+ }
+
+ public SwingWorker<?, ?> open(File file) {
+ if (file == null) {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setFileFilter(new PngFileFilter());
+ int choice = chooser.showOpenDialog(this);
+ if (choice == JFileChooser.APPROVE_OPTION) {
+ return new OpenTask(chooser.getSelectedFile());
+ } else {
+ return null;
+ }
+ } else {
+ return new OpenTask(file);
+ }
+ }
+
+ void showImageEditor(BufferedImage image, String name) {
+ getContentPane().removeAll();
+ imageEditor = new ImageEditorPanel(this, image, name);
+ add(imageEditor);
+ saveMenuItem.setEnabled(true);
+ validate();
+ repaint();
+ }
+
+ public SwingWorker<?, ?> save() {
+ if (imageEditor == null) {
+ return null;
+ }
+
+ File file = imageEditor.chooseSaveFile();
+ return file != null ? new SaveTask(file) : null;
+ }
+
+ private class SaveTask extends SwingWorker<Boolean, Void> {
+ private final File file;
+
+ SaveTask(File file) {
+ this.file = file;
+ }
+
+ protected Boolean doInBackground() throws Exception {
+ try {
+ ImageIO.write(imageEditor.getImage(), "PNG", file);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+ }
+
+ private class OpenTask extends SwingWorker<BufferedImage, Void> {
+ private final File file;
+
+ OpenTask(File file) {
+ this.file = file;
+ }
+
+ protected BufferedImage doInBackground() throws Exception {
+ return GraphicsUtilities.loadCompatibleImage(file.toURI().toURL());
+ }
+
+ @Override
+ protected void done() {
+ try {
+ showImageEditor(get(), file.getAbsolutePath());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/OpenFilePanel.java b/draw9patch/src/com/android/draw9patch/ui/OpenFilePanel.java
new file mode 100644
index 0000000..a444332
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/OpenFilePanel.java
@@ -0,0 +1,51 @@
+package com.android.draw9patch.ui;
+
+import com.android.draw9patch.graphics.GraphicsUtilities;
+
+import javax.swing.JComponent;
+import java.awt.image.BufferedImage;
+import java.awt.Graphics;
+import java.io.IOException;
+import java.net.URL;/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class OpenFilePanel extends JComponent {
+ private BufferedImage dropHere;
+
+ OpenFilePanel(MainFrame mainFrame) {
+ setOpaque(false);
+ loadSupportImage();
+ setTransferHandler(new ImageTransferHandler(mainFrame));
+ }
+
+ private void loadSupportImage() {
+ try {
+ URL resource = getClass().getResource("/images/drop.png");
+ dropHere = GraphicsUtilities.loadCompatibleImage(resource);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ int x = (getWidth() - dropHere.getWidth()) / 2;
+ int y = (getHeight() - dropHere.getHeight()) / 2;
+
+ g.drawImage(dropHere, x, y, null);
+ }
+
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/PngFileFilter.java b/draw9patch/src/com/android/draw9patch/ui/PngFileFilter.java
new file mode 100644
index 0000000..8f8885a
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/PngFileFilter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.ui;
+
+import javax.swing.filechooser.FileFilter;
+import java.io.File;
+
+class PngFileFilter extends FileFilter {
+ @Override
+ public boolean accept(File f) {
+ return f.isDirectory() || f.getName().toLowerCase().endsWith(".png");
+ }
+
+ @Override
+ public String getDescription() {
+ return "PNG Image (*.png)";
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/action/BackgroundAction.java b/draw9patch/src/com/android/draw9patch/ui/action/BackgroundAction.java
new file mode 100644
index 0000000..85d9d4f
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/action/BackgroundAction.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.ui.action;
+
+import org.jdesktop.swingworker.SwingWorker;
+
+import javax.swing.AbstractAction;
+
+public abstract class BackgroundAction extends AbstractAction {
+ protected void executeBackgroundTask(SwingWorker<?, ?> worker) {
+ if (worker != null) {
+ worker.execute();
+ }
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/action/ExitAction.java b/draw9patch/src/com/android/draw9patch/ui/action/ExitAction.java
new file mode 100644
index 0000000..b6f047d
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/action/ExitAction.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.ui.action;
+
+import javax.swing.AbstractAction;
+import javax.swing.KeyStroke;
+import javax.swing.JFrame;
+import java.awt.event.KeyEvent;
+import java.awt.event.ActionEvent;
+import java.awt.Toolkit;
+
+public class ExitAction extends AbstractAction {
+ public static final String ACTION_NAME = "exit";
+ private JFrame frame;
+
+ public ExitAction(JFrame frame) {
+ putValue(NAME, "Quit");
+ putValue(SHORT_DESCRIPTION, "Quit");
+ putValue(LONG_DESCRIPTION, "Quit");
+ putValue(MNEMONIC_KEY, KeyEvent.VK_Q);
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
+ this.frame = frame;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ frame.dispose();
+ System.exit(0);
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/action/OpenAction.java b/draw9patch/src/com/android/draw9patch/ui/action/OpenAction.java
new file mode 100644
index 0000000..45ee5be
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/action/OpenAction.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.ui.action;
+
+import com.android.draw9patch.ui.MainFrame;
+
+import javax.swing.KeyStroke;
+import java.awt.event.KeyEvent;
+import java.awt.event.ActionEvent;
+import java.awt.Toolkit;
+
+public class OpenAction extends BackgroundAction {
+ public static final String ACTION_NAME = "open";
+ private MainFrame frame;
+
+ public OpenAction(MainFrame frame) {
+ this.frame = frame;
+ putValue(NAME, "Open 9-patch...");
+ putValue(SHORT_DESCRIPTION, "Open...");
+ putValue(LONG_DESCRIPTION, "Open 9-patch...");
+ putValue(MNEMONIC_KEY, KeyEvent.VK_O);
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ executeBackgroundTask(frame.open(null));
+ }
+}
diff --git a/draw9patch/src/com/android/draw9patch/ui/action/SaveAction.java b/draw9patch/src/com/android/draw9patch/ui/action/SaveAction.java
new file mode 100644
index 0000000..5c1dc52
--- /dev/null
+++ b/draw9patch/src/com/android/draw9patch/ui/action/SaveAction.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.draw9patch.ui.action;
+
+import com.android.draw9patch.ui.MainFrame;
+
+import javax.swing.KeyStroke;
+import java.awt.event.KeyEvent;
+import java.awt.event.ActionEvent;
+import java.awt.Toolkit;
+
+public class SaveAction extends BackgroundAction {
+ public static final String ACTION_NAME = "save";
+ private MainFrame frame;
+
+ public SaveAction(MainFrame frame) {
+ this.frame = frame;
+ putValue(NAME, "Save 9-patch...");
+ putValue(SHORT_DESCRIPTION, "Save...");
+ putValue(LONG_DESCRIPTION, "Save 9-patch...");
+ putValue(MNEMONIC_KEY, KeyEvent.VK_S);
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S,
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ executeBackgroundTask(frame.save());
+ }
+}
diff --git a/draw9patch/src/resources/images/checker.png b/draw9patch/src/resources/images/checker.png
new file mode 100644
index 0000000..78908f4
--- /dev/null
+++ b/draw9patch/src/resources/images/checker.png
Binary files differ
diff --git a/draw9patch/src/resources/images/drop.png b/draw9patch/src/resources/images/drop.png
new file mode 100644
index 0000000..7a7436a
--- /dev/null
+++ b/draw9patch/src/resources/images/drop.png
Binary files differ