From 24651f91d4dd8904f6f3d28eba64a71955edc89e Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Tue, 2 Mar 2010 17:07:03 -0800 Subject: Add the ability to export any window as a layered PSD file. --- .../hierarchyviewer/scene/CaptureLoader.java | 97 ++++- .../com/android/hierarchyviewer/ui/Workspace.java | 44 ++ .../ui/action/CaptureLayersAction.java | 42 ++ .../android/hierarchyviewer/ui/util/PsdFile.java | 442 +++++++++++++++++++++ .../hierarchyviewer/ui/util/PsdFileFilter.java | 32 ++ 5 files changed, 656 insertions(+), 1 deletion(-) create mode 100644 hierarchyviewer/src/com/android/hierarchyviewer/ui/action/CaptureLayersAction.java create mode 100644 hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFile.java create mode 100644 hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFileFilter.java (limited to 'hierarchyviewer') diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/scene/CaptureLoader.java b/hierarchyviewer/src/com/android/hierarchyviewer/scene/CaptureLoader.java index c512ac2..ca51b4e 100644 --- a/hierarchyviewer/src/com/android/hierarchyviewer/scene/CaptureLoader.java +++ b/hierarchyviewer/src/com/android/hierarchyviewer/scene/CaptureLoader.java @@ -17,13 +17,20 @@ package com.android.hierarchyviewer.scene; import com.android.ddmlib.IDevice; -import com.android.hierarchyviewer.device.Configuration; import com.android.hierarchyviewer.device.Window; import com.android.hierarchyviewer.device.DeviceBridge; +import com.android.hierarchyviewer.ui.util.PsdFile; +import java.awt.Graphics2D; import java.awt.Image; +import java.awt.Point; +import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.InetSocketAddress; @@ -31,6 +38,94 @@ import java.net.Socket; import javax.imageio.ImageIO; public class CaptureLoader { + public static boolean saveLayers(IDevice device, Window window, File file) { + Socket socket = null; + DataInputStream in = null; + BufferedWriter out = null; + boolean result = false; + + try { + socket = new Socket(); + socket.connect(new InetSocketAddress("127.0.0.1", + DeviceBridge.getDeviceLocalPort(device))); + + out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + in = new DataInputStream(new BufferedInputStream(socket.getInputStream())); + + out.write("CAPTURE_LAYERS " + window.encode()); + out.newLine(); + out.flush(); + + int width = in.readInt(); + int height = in.readInt(); + + PsdFile psd = new PsdFile(width, height); + + while (readLayer(in, psd)) { + } + + psd.write(new FileOutputStream(file)); + + result = true; + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + if (socket != null) { + socket.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + return result; + } + + private static boolean readLayer(DataInputStream in, PsdFile psd) { + try { + if (in.read() == 2) { + System.out.println("Found end of layers list"); + return false; + } + String name = in.readUTF(); + System.out.println("name = " + name); + boolean visible = in.read() == 1; + int x = in.readInt(); + int y = in.readInt(); + int dataSize = in.readInt(); + + byte[] data = new byte[dataSize]; + int read = 0; + while (read < dataSize) { + read += in.read(data, read, dataSize - read); + } + + ByteArrayInputStream arrayIn = new ByteArrayInputStream(data); + BufferedImage chunk = ImageIO.read(arrayIn); + + // Ensure the image is in the right format + BufferedImage image = new BufferedImage(chunk.getWidth(), chunk.getHeight(), + BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + g.drawImage(chunk, null, 0, 0); + g.dispose(); + + psd.addLayer(name, image, new Point(x, y), visible); + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + public static Image loadCapture(IDevice device, Window window, String params) { Socket socket = null; BufferedInputStream in = null; diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java index 5686496..a7db985 100644 --- a/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java +++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java @@ -29,12 +29,14 @@ import com.android.hierarchyviewer.scene.ViewManager; import com.android.hierarchyviewer.scene.ViewNode; import com.android.hierarchyviewer.scene.WindowsLoader; import com.android.hierarchyviewer.scene.ProfilesLoader; +import com.android.hierarchyviewer.ui.util.PsdFileFilter; import com.android.hierarchyviewer.util.OS; import com.android.hierarchyviewer.util.WorkerThread; import com.android.hierarchyviewer.ui.action.ShowDevicesAction; import com.android.hierarchyviewer.ui.action.RequestLayoutAction; import com.android.hierarchyviewer.ui.action.InvalidateAction; import com.android.hierarchyviewer.ui.action.CaptureNodeAction; +import com.android.hierarchyviewer.ui.action.CaptureLayersAction; import com.android.hierarchyviewer.ui.action.RefreshWindowsAction; import com.android.hierarchyviewer.ui.action.StopServerAction; import com.android.hierarchyviewer.ui.action.StartServerAction; @@ -152,6 +154,7 @@ public class Workspace extends JFrame { private Window currentWindow = Window.FOCUSED_WINDOW; private JButton displayNodeButton; + private JButton captureLayersButton; private JButton invalidateButton; private JButton requestLayoutButton; private JButton loadButton; @@ -200,6 +203,7 @@ public class Workspace extends JFrame { actionsMap.put(InvalidateAction.ACTION_NAME, new InvalidateAction(this)); actionsMap.put(RequestLayoutAction.ACTION_NAME, new RequestLayoutAction(this)); actionsMap.put(CaptureNodeAction.ACTION_NAME, new CaptureNodeAction(this)); + actionsMap.put(CaptureLayersAction.ACTION_NAME, new CaptureLayersAction(this)); actionsMap.put(RefreshWindowsAction.ACTION_NAME, new RefreshWindowsAction(this)); } @@ -477,6 +481,12 @@ public class Workspace extends JFrame { displayNodeButton.putClientProperty("JButton.segmentPosition", "first"); toolBar.add(displayNodeButton); + captureLayersButton = new JButton(); + captureLayersButton.setAction(actionsMap.get(CaptureLayersAction.ACTION_NAME)); + captureLayersButton.putClientProperty("JButton.buttonType", "segmentedTextured"); + captureLayersButton.putClientProperty("JButton.segmentPosition", "middle"); + toolBar.add(captureLayersButton); + invalidateButton = new JButton(); invalidateButton.setAction(actionsMap.get(InvalidateAction.ACTION_NAME)); invalidateButton.putClientProperty("JButton.buttonType", "segmentedTextured"); @@ -707,6 +717,7 @@ public class Workspace extends JFrame { mainSplitter.setDividerLocation(getWidth() - mainSplitter.getDividerSize() - buttonsPanel.getPreferredSize().width); + captureLayersButton.setEnabled(true); saveMenuItem.setEnabled(true); showPixelPerfectTree(); @@ -872,6 +883,7 @@ public class Workspace extends JFrame { showDevicesMenuItem.setEnabled(false); showDevicesButton.setEnabled(false); displayNodeButton.setEnabled(false); + captureLayersButton.setEnabled(false); invalidateButton.setEnabled(false); requestLayoutButton.setEnabled(false); graphViewButton.setEnabled(false); @@ -900,6 +912,7 @@ public class Workspace extends JFrame { saveMenuItem.setEnabled(false); loadButton.setEnabled(false); displayNodeButton.setEnabled(false); + captureLayersButton.setEnabled(false); invalidateButton.setEnabled(false); graphViewButton.setEnabled(false); pixelPerfectViewButton.setEnabled(false); @@ -994,6 +1007,17 @@ public class Workspace extends JFrame { } return new CaptureNodeTask(); } + + public SwingWorker captureLayers() { + JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(new PsdFileFilter()); + int choice = chooser.showSaveDialog(sceneView); + if (choice == JFileChooser.APPROVE_OPTION) { + return new CaptureLayersTask(chooser.getSelectedFile()); + } else { + return null; + } + } public SwingWorker startServer() { return new StartServerTask(); @@ -1077,6 +1101,26 @@ public class Workspace extends JFrame { endTask(); } } + + private class CaptureLayersTask extends SwingWorker { + private File file; + + private CaptureLayersTask(File file) { + this.file = file; + beginTask(); + } + + @Override + @WorkerThread + protected Boolean doInBackground() throws Exception { + return CaptureLoader.saveLayers(currentDevice, currentWindow, file); + } + + @Override + protected void done() { + endTask(); + } + } private class CaptureNodeTask extends SwingWorker { private String captureParams; diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/action/CaptureLayersAction.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/action/CaptureLayersAction.java new file mode 100644 index 0000000..2fff041 --- /dev/null +++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/action/CaptureLayersAction.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 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.hierarchyviewer.ui.action; + +import com.android.hierarchyviewer.ui.Workspace; + +import javax.swing.KeyStroke; +import java.awt.event.KeyEvent; +import java.awt.event.ActionEvent; +import java.awt.Toolkit; + +public class CaptureLayersAction extends BackgroundAction { + public static final String ACTION_NAME = "captureLayers"; + private Workspace mWorkspace; + + public CaptureLayersAction(Workspace workspace) { + putValue(NAME, "Capture PSD"); + putValue(SHORT_DESCRIPTION, "Capture PSD"); + putValue(LONG_DESCRIPTION, "Capture current window into a Photoshop PSD file"); + putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_P, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + this.mWorkspace = workspace; + } + + public void actionPerformed(ActionEvent e) { + executeBackgroundTask(mWorkspace.captureLayers()); + } +} diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFile.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFile.java new file mode 100644 index 0000000..3768e41 --- /dev/null +++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFile.java @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2010 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.hierarchyviewer.ui.util; + +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +/** + * Writes PSD file. + * + * Supports only 8 bits, RGB images with 4 channels. + */ +public class PsdFile { + private final Header mHeader; + private final ColorMode mColorMode; + private final ImageResources mImageResources; + private final LayersMasksInfo mLayersMasksInfo; + private final LayersInfo mLayersInfo; + + private final BufferedImage mMergedImage; + private final Graphics2D mGraphics; + + public PsdFile(int width, int height) { + mHeader = new Header(width, height); + mColorMode = new ColorMode(); + mImageResources = new ImageResources(); + mLayersMasksInfo = new LayersMasksInfo(); + mLayersInfo = new LayersInfo(); + + mMergedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + mGraphics = mMergedImage.createGraphics(); + } + + public void addLayer(String name, BufferedImage image, Point offset) { + addLayer(name, image, offset, true); + } + + public void addLayer(String name, BufferedImage image, Point offset, boolean visible) { + mLayersInfo.addLayer(name, image, offset, visible); + if (visible) mGraphics.drawImage(image, null, offset.x, offset.y); + } + + public void write(OutputStream stream) { + mLayersMasksInfo.setLayersInfo(mLayersInfo); + + DataOutputStream out = new DataOutputStream(new BufferedOutputStream(stream)); + try { + mHeader.write(out); + out.flush(); + + mColorMode.write(out); + mImageResources.write(out); + mLayersMasksInfo.write(out); + mLayersInfo.write(out); + out.flush(); + + mLayersInfo.writeImageData(out); + out.flush(); + + writeImage(mMergedImage, out, false); + out.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static void writeImage(BufferedImage image, DataOutputStream out, boolean split) + throws IOException { + + if (!split) out.writeShort(0); + + int width = image.getWidth(); + int height = image.getHeight(); + + final int length = width * height; + int[] pixels = new int[length]; + + image.getData().getDataElements(0, 0, width, height, pixels); + + byte[] a = new byte[length]; + byte[] r = new byte[length]; + byte[] g = new byte[length]; + byte[] b = new byte[length]; + + for (int i = 0; i < length; i++) { + final int pixel = pixels[i]; + a[i] = (byte) ((pixel >> 24) & 0xFF); + r[i] = (byte) ((pixel >> 16) & 0xFF); + g[i] = (byte) ((pixel >> 8) & 0xFF); + b[i] = (byte) (pixel & 0xFF); + } + + if (split) out.writeShort(0); + if (split) out.write(a); + if (split) out.writeShort(0); + out.write(r); + if (split) out.writeShort(0); + out.write(g); + if (split) out.writeShort(0); + out.write(b); + if (!split) out.write(a); + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class Header { + static final short MODE_BITMAP = 0; + static final short MODE_GRAYSCALE = 1; + static final short MODE_INDEXED = 2; + static final short MODE_RGB = 3; + static final short MODE_CMYK = 4; + static final short MODE_MULTI_CHANNEL = 7; + static final short MODE_DUOTONE = 8; + static final short MODE_LAB = 9; + + final byte[] mSignature = "8BPS".getBytes(); + final short mVersion = 1; + final byte[] mReserved = new byte[6]; + final short mChannelCount = 4; + final int mHeight; + final int mWidth; + final short mDepth = 8; + final short mMode = MODE_RGB; + + Header(int width, int height) { + mWidth = width; + mHeight = height; + } + + void write(DataOutputStream out) throws IOException { + out.write(mSignature); + out.writeShort(mVersion); + out.write(mReserved); + out.writeShort(mChannelCount); + out.writeInt(mHeight); + out.writeInt(mWidth); + out.writeShort(mDepth); + out.writeShort(mMode); + } + } + + // Unused at the moment + @SuppressWarnings({"UnusedDeclaration"}) + static class ColorMode { + final int mLength = 0; + + void write(DataOutputStream out) throws IOException { + out.writeInt(mLength); + } + } + + // Unused at the moment + @SuppressWarnings({"UnusedDeclaration"}) + static class ImageResources { + static final short RESOURCE_RESOLUTION_INFO = 0x03ED; + + int mLength = 0; + + final byte[] mSignature = "8BIM".getBytes(); + final short mResourceId = RESOURCE_RESOLUTION_INFO; + + final short mPad = 0; + + final int mDataLength = 16; + + final short mHorizontalDisplayUnit = 0x48; // 72 dpi + final int mHorizontalResolution = 1; + final short mWidthDisplayUnit = 1; + + final short mVerticalDisplayUnit = 0x48; // 72 dpi + final int mVerticalResolution = 1; + final short mHeightDisplayUnit = 1; + + ImageResources() { + mLength = mSignature.length; + mLength += 2; + mLength += 2; + mLength += 4; + mLength += 8; + mLength += 8; + } + + void write(DataOutputStream out) throws IOException { + out.writeInt(mLength); + out.write(mSignature); + out.writeShort(mResourceId); + out.writeShort(mPad); + out.writeInt(mDataLength); + out.writeShort(mHorizontalDisplayUnit); + out.writeInt(mHorizontalResolution); + out.writeShort(mWidthDisplayUnit); + out.writeShort(mVerticalDisplayUnit); + out.writeInt(mVerticalResolution); + out.writeShort(mHeightDisplayUnit); + } + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class LayersMasksInfo { + int mMiscLength; + int mLayerInfoLength; + + void setLayersInfo(LayersInfo layersInfo) { + mLayerInfoLength = layersInfo.getLength(); + // Round to the next multiple of 2 + if ((mLayerInfoLength & 0x1) == 0x1) mLayerInfoLength++; + mMiscLength = mLayerInfoLength + 8; + } + + void write(DataOutputStream out) throws IOException { + out.writeInt(mMiscLength); + out.writeInt(mLayerInfoLength); + } + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class LayersInfo { + final List mLayers = new ArrayList(); + + void addLayer(String name, BufferedImage image, Point offset, boolean visible) { + mLayers.add(new Layer(name, image, offset, visible)); + } + + int getLength() { + int length = 2; + for (Layer layer : mLayers) { + length += layer.getLength(); + } + return length; + } + + void write(DataOutputStream out) throws IOException { + out.writeShort((short) -mLayers.size()); + for (Layer layer : mLayers) { + layer.write(out); + } + } + + void writeImageData(DataOutputStream out) throws IOException { + for (Layer layer : mLayers) { + layer.writeImageData(out); + } + // Global layer mask info length + out.writeInt(0); + } + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class Layer { + static final byte OPACITY_TRANSPARENT = 0x0; + static final byte OPACITY_OPAQUE = (byte) 0xFF; + + static final byte CLIPPING_BASE = 0x0; + static final byte CLIPPING_NON_BASE = 0x1; + + static final byte FLAG_TRANSPARENCY_PROTECTED = 0x1; + static final byte FLAG_INVISIBLE = 0x2; + + final int mTop; + final int mLeft; + final int mBottom; + final int mRight; + + final short mChannelCount = 4; + final Channel[] mChannelInfo = new Channel[mChannelCount]; + + final byte[] mBlendSignature = "8BIM".getBytes(); + final byte[] mBlendMode = "norm".getBytes(); + + final byte mOpacity = OPACITY_OPAQUE; + final byte mClipping = CLIPPING_BASE; + byte mFlags = 0x0; + final byte mFiller = 0x0; + + int mExtraSize = 4 + 4; + + final int mMaskDataLength = 0; + final int mBlendRangeDataLength = 0; + + final byte[] mName; + + final byte[] mLayerExtraSignature = "8BIM".getBytes(); + final byte[] mLayerExtraKey = "luni".getBytes(); + int mLayerExtraLength; + final String mOriginalName; + + private BufferedImage mImage; + + Layer(String name, BufferedImage image, Point offset, boolean visible) { + final int height = image.getHeight(); + final int width = image.getWidth(); + final int length = width * height; + + mChannelInfo[0] = new Channel(Channel.ID_ALPHA, length); + mChannelInfo[1] = new Channel(Channel.ID_RED, length); + mChannelInfo[2] = new Channel(Channel.ID_GREEN, length); + mChannelInfo[3] = new Channel(Channel.ID_BLUE, length); + + mTop = offset.y; + mLeft = offset.x; + mBottom = offset.y + height; + mRight = offset.x + width; + + mOriginalName = name; + byte[] data = name.getBytes(); + + try { + mLayerExtraLength = 4 + mOriginalName.getBytes("UTF-16").length; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + final byte[] nameData = new byte[data.length + 1]; + nameData[0] = (byte) (data.length & 0xFF); + System.arraycopy(data, 0, nameData, 1, data.length); + + // This could be done in the same pass as above + if (nameData.length % 4 != 0) { + data = new byte[nameData.length + 4 - (nameData.length % 4)]; + System.arraycopy(nameData, 0, data, 0, nameData.length); + mName = data; + } else { + mName = nameData; + } + mExtraSize += mName.length; + mExtraSize += mLayerExtraLength + 4 + mLayerExtraKey.length + + mLayerExtraSignature.length; + + mImage = image; + + if (!visible) { + mFlags |= FLAG_INVISIBLE; + } + } + + int getLength() { + int length = 4 * 4 + 2; + + for (Channel channel : mChannelInfo) { + length += channel.getLength(); + } + + length += mBlendSignature.length; + length += mBlendMode.length; + length += 4; + length += 4; + length += mExtraSize; + + return length; + } + + void write(DataOutputStream out) throws IOException { + out.writeInt(mTop); + out.writeInt(mLeft); + out.writeInt(mBottom); + out.writeInt(mRight); + + out.writeShort(mChannelCount); + for (Channel channel : mChannelInfo) { + channel.write(out); + } + + out.write(mBlendSignature); + out.write(mBlendMode); + + out.write(mOpacity); + out.write(mClipping); + out.write(mFlags); + out.write(mFiller); + + out.writeInt(mExtraSize); + out.writeInt(mMaskDataLength); + + out.writeInt(mBlendRangeDataLength); + + out.write(mName); + + out.write(mLayerExtraSignature); + out.write(mLayerExtraKey); + out.writeInt(mLayerExtraLength); + out.writeInt(mOriginalName.length() + 1); + out.write(mOriginalName.getBytes("UTF-16")); + } + + void writeImageData(DataOutputStream out) throws IOException { + writeImage(mImage, out, true); + } + } + + @SuppressWarnings({"UnusedDeclaration"}) + static class Channel { + static final short ID_RED = 0; + static final short ID_GREEN = 1; + static final short ID_BLUE = 2; + static final short ID_ALPHA = -1; + static final short ID_LAYER_MASK = -2; + + final short mId; + final int mDataLength; + + Channel(short id, int dataLength) { + mId = id; + mDataLength = dataLength + 2; + } + + int getLength() { + return 2 + 4 + mDataLength; + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(mId); + out.writeInt(mDataLength); + } + } +} diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFileFilter.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFileFilter.java new file mode 100644 index 0000000..6a7ce5b --- /dev/null +++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/util/PsdFileFilter.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.hierarchyviewer.ui.util; + +import javax.swing.filechooser.FileFilter; +import java.io.File; + +public class PsdFileFilter extends FileFilter { + @Override + public boolean accept(File f) { + return f.isDirectory() || f.getName().toLowerCase().endsWith(".psd"); + } + + @Override + public String getDescription() { + return "Photoshop Document (*.psd)"; + } +} \ No newline at end of file -- cgit v1.1