Repository: importre/android-selector-intellij-plugin Branch: master Commit: 16569b022508 Files: 9 Total size: 28.9 KB Directory structure: gitextract_0tlwig1c/ ├── .gitignore ├── META-INF/ │ └── plugin.xml ├── license ├── readme.md └── src/ └── importre/ └── intellij/ └── android/ └── selector/ ├── action/ │ └── AndroidSelector.java ├── color/ │ ├── ColorIcon.java │ └── ColorItemRenderer.java └── form/ ├── AndroidSelectorDialog.form └── AndroidSelectorDialog.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: META-INF/plugin.xml ================================================ importre.intellij.android.selector Android Selector 0.2.0 importre Generate selectors for background drawable
You can use `colorButtonNormal` simply,
but make easily touch feedback of normal `View`s as well as `Button`s with this plugin.

1. Set your colors(in `res/values/colors.xml`).
2. Select `New -> Android Selector(or Ctrl/Cmd + N)` on your `res` directory.
3. Select filename, color, pressed and pressed-v21 respectively.

- ripple drawable is generated in drawable-v21 directory.
- normal drawable is generated in drawable directory.

Dependency


- com.android.support:appcompat-v7:22.+ ]]>
- Changing icon ]]> com.intellij.modules.lang
================================================ FILE: license ================================================ The MIT License (MIT) Copyright (c) 2015 Jaewe Heo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: readme.md ================================================ [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-android--selector--intellij--plugin-green.svg?style=flat)](https://android-arsenal.com/details/1/2342) # android-selector-intellij-plugin ![icon](src/icons/icon@2x.png) :art: Generate selectors for background drawable. You can use `colorButtonNormal` simply, but make easily touch feedback of normal `View`s as well as `Button`s with this plugin. ## Installation 1. open Android Studio(or IntelliJ) 2. Preferences :arrow_right: Plugins :arrow_right: Browse Repositories 3. Search "Android Selector" 4. Click "Install Plugin" button ## Usage - Set your colors(in `res/values/colors.xml`). ```xml #519FE5 #388AC6 #FFFFFF ``` - Select `New -> Android Selector(or Ctrl/Cmd + N)` on your `res` directory. ![screenshot1](images/screenshot1.png) - Select filename, color, pressed and pressed-v21 respectively. ![screenshot2](images/screenshot2.png) > ripple drawable is generated in drawable-v21 directory. > normal drawable is generated in drawable directory. - Use the drawable. ```xml ``` ## Demo | Lollipop > | Lollipop <= | |---------------|----------------| | ![demo1][d1] | ![demo2][d2] | ## Dependency - com.android.support:appcompat-v7:22.+ ## License MIT © [Jaewe Heo][importre] [importre]: http://import.re [d1]: images/demo1.png [d2]: images/demo2.png ================================================ FILE: src/importre/intellij/android/selector/action/AndroidSelector.java ================================================ package importre.intellij.android.selector.action; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import importre.intellij.android.selector.form.AndroidSelectorDialog; public class AndroidSelector extends AnAction { @Override public void update(AnActionEvent e) { super.update(e); VirtualFile dir = e.getData(LangDataKeys.VIRTUAL_FILE); if (dir != null && dir.isDirectory()) { String text = dir.getName(); e.getPresentation().setVisible("res".equals(text)); } } @Override public void actionPerformed(AnActionEvent e) { final VirtualFile dir = e.getData(LangDataKeys.VIRTUAL_FILE); if (dir == null) { return; } Project project = e.getProject(); AndroidSelectorDialog dialog = new AndroidSelectorDialog(project, dir); dialog.show(); } } ================================================ FILE: src/importre/intellij/android/selector/color/ColorIcon.java ================================================ package importre.intellij.android.selector.color; import com.intellij.ui.JBColor; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.geom.Rectangle2D; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ColorIcon implements Icon { private final String color; public ColorIcon(String color) { if (color == null) { this.color = null; } else { this.color = color.startsWith("#") ? color.substring(1) : null; } } public int getIconHeight() { return 16; } public int getIconWidth() { return 16; } public void paintIcon(Component c, Graphics g, int x, int y) { JBColor color = getColor(); if (color == null) { g.setColor(JBColor.WHITE); g.fillRect(1, 1, getIconWidth(), getIconHeight()); g.setColor(JBColor.DARK_GRAY); if (g instanceof Graphics2D) { RenderingHints.Key key = RenderingHints.KEY_TEXT_ANTIALIASING; Object value = RenderingHints.VALUE_TEXT_ANTIALIAS_ON; ((Graphics2D) g).setRenderingHint(key, value); } String q = "?"; FontMetrics fm = g.getFontMetrics(); Rectangle2D r = fm.getStringBounds(q, g); x = (int) ((getIconWidth() - (int) r.getWidth()) * 0.7f); y = (getIconHeight() - (int) r.getHeight()) / 2 + fm.getAscent(); g.drawString(q, x, y); } else { g.setColor(color); g.fillRect(1, 1, getIconWidth(), getIconHeight()); } g.setColor(JBColor.DARK_GRAY); g.drawRect(1, 1, getIconWidth(), getIconHeight()); } @Nullable private JBColor getColor() { String regex = "((?:[0-9a-fA-F]{2})?)" + "([0-9a-fA-F]{2})" + "([0-9a-fA-F]{2})" + "([0-9a-fA-F]{2})"; Pattern p = Pattern.compile(regex); try { if (color == null) { return null; } Matcher m = p.matcher(color); if (m.find()) { int r = Integer.parseInt(m.group(2), 16); int g = Integer.parseInt(m.group(3), 16); int b = Integer.parseInt(m.group(4), 16); if (m.group(1).isEmpty()) { return new JBColor( new Color(r, g, b), new Color(r, g, b)); } else { int a = Integer.parseInt(m.group(1), 16); return new JBColor( new Color(r, g, b, a), new Color(r, g, b, a)); } } } catch (Exception ignore) { } return null; } } ================================================ FILE: src/importre/intellij/android/selector/color/ColorItemRenderer.java ================================================ package importre.intellij.android.selector.color; import javax.swing.*; import java.awt.*; public class ColorItemRenderer implements ListCellRenderer { DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer(); @Override public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Icon icon; String name; JLabel renderer = (JLabel) defaultRenderer .getListCellRendererComponent( list, value, index, isSelected, cellHasFocus); String values[] = (String[]) value; name = values[1]; icon = new ColorIcon(values[0]); renderer.setIcon(icon); renderer.setText(name); return renderer; } } ================================================ FILE: src/importre/intellij/android/selector/form/AndroidSelectorDialog.form ================================================
================================================ FILE: src/importre/intellij/android/selector/form/AndroidSelectorDialog.java ================================================ package importre.intellij.android.selector.form; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import importre.intellij.android.selector.color.ColorItemRenderer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.swing.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import java.io.*; import java.util.*; public class AndroidSelectorDialog extends DialogWrapper { private static final String INDENT_SPACE = "{http://xml.apache.org/xslt}indent-amount"; private static final String drawableDir = "drawable"; private static final String drawableV21Dir = "drawable-v21"; private static final String valuesColorsXml = "values/colors.xml"; private static final String localProps = "local.properties"; private static final String platformsRes = "%s/platforms/%s/data/res/values"; private static final String nsUri = "http://www.w3.org/2000/xmlns/"; private static final String androidUri = "http://schemas.android.com/apk/res/android"; private final VirtualFile dir; private final Project project; private JPanel contentPane; private JTextField filenameText; private JComboBox colorCombo; private JComboBox pressedCombo; private JComboBox pressedV21Combo; public AndroidSelectorDialog(@Nullable Project project, VirtualFile dir) { super(project); this.project = project; this.dir = dir; setTitle("Android Selector"); setResizable(false); init(); } @Override public void show() { try { if (initColors(dir)) { super.show(); } } catch (Exception e) { e.printStackTrace(); } } private boolean initColors(VirtualFile dir) { VirtualFile colorsXml = dir.findFileByRelativePath(valuesColorsXml); if (colorsXml != null && colorsXml.exists()) { HashMap cmap = parseColorsXml(colorsXml); HashMap andCmap = parseAndroidColorsXml(); if (cmap.isEmpty()) { String title = "Error"; String msg = "Cannot find colors in colors.xml"; showMessageDialog(title, msg); return false; } String regex = "^@(android:)?color/(.+$)"; ArrayList elements = new ArrayList(); for (String name : cmap.keySet()) { String color = cmap.get(name); while (color != null && color.matches(regex)) { if (color.startsWith("@color/")) { String key = color.replace("@color/", ""); color = cmap.get(key); } else if (color.startsWith("@android:color/")) { String key = color.replace("@android:color/", ""); color = andCmap.get(key); } else { // not reachable... } } if (color != null) { elements.add(new String[]{color, name}); } } ColorItemRenderer renderer = new ColorItemRenderer(); colorCombo.setRenderer(renderer); pressedCombo.setRenderer(renderer); pressedV21Combo.setRenderer(renderer); for (Object element : elements) { colorCombo.addItem(element); pressedCombo.addItem(element); pressedV21Combo.addItem(element); } return !elements.isEmpty(); } String title = "Error"; String msg = String.format("Cannot find %s", valuesColorsXml); showMessageDialog(title, msg); return false; } @NotNull private HashMap parseColorsXml(VirtualFile colorsXml) { HashMap map = new LinkedHashMap(); try { NodeList colors = getColorNodes(colorsXml.getInputStream()); makeColorMap(colors, map); } catch (Exception e) { e.printStackTrace(); } return map; } private void makeColorMap(NodeList colors, HashMap map) { for (int i = 0; i < colors.getLength(); i++) { Element node = (Element) colors.item(i); String nodeName = node.getNodeName(); if ("color".equals(nodeName) || "item".equals(nodeName)) { String name = node.getAttribute("name"); String color = node.getTextContent(); if (name != null && color != null && !map.containsKey(name)) { map.put(name, color); } } } } private NodeList getColorNodes(InputStream stream) throws Exception { XPath xPath = XPathFactory.newInstance().newXPath(); String expression = "//item[@type=\"color\"]|//color"; XPathExpression compile = xPath.compile(expression); DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = f.newDocumentBuilder(); Document doc = builder.parse(stream); return (NodeList) compile.evaluate(doc, XPathConstants.NODESET); } @NotNull private HashMap parseAndroidColorsXml() { HashMap map = new HashMap(); if (project == null) return map; VirtualFile baseDir = project.getBaseDir(); VirtualFile prop = baseDir.findFileByRelativePath(localProps); if (prop == null) return map; Properties properties = new Properties(); try { properties.load(prop.getInputStream()); } catch (IOException e) { e.printStackTrace(); } String sdkDir = properties.getProperty("sdk.dir"); File file = new File(sdkDir + File.separator + "platforms"); if (!file.isDirectory()) return map; ArrayList platforms = new ArrayList(); Collections.addAll(platforms, file.list()); Collections.reverse(platforms); for (int i = 0, size = platforms.size(); i < size; i++) { String platform = platforms.get(i); if (platform.matches("^android-\\d+$")) continue; if (i > 3) break; String path = String.format(platformsRes, sdkDir, platform); File[] files = new File(path).listFiles(); if (files == null) continue; for (File f : files) { if (f.getName().matches("colors.*\\.xml")) { try { FileInputStream stream = new FileInputStream(f); NodeList colors = getColorNodes(stream); makeColorMap(colors, map); } catch (Exception e) { e.printStackTrace(); } } } } return map; } @Nullable @Override protected JComponent createCenterPanel() { return contentPane; } private String getColorName(JComboBox combo) { Object colorItem = combo.getSelectedItem(); try { if (colorItem instanceof Object[]) { return "@color/" + ((Object[]) (colorItem))[1]; } } catch (Exception e) { e.printStackTrace(); } return ""; } @Override protected void doOKAction() { String f = filenameText.getText(); final String filename = (f.endsWith(".xml") ? f : f + ".xml").trim(); final String color = getColorName(colorCombo); final String pressed = getColorName(pressedCombo); final String pressedV21 = getColorName(pressedV21Combo); if (!valid(filename, color, pressed, pressedV21)) { String title = "Invalidation"; String msg = "color, pressed, pressedV21 must start with `@color/`"; showMessageDialog(title, msg); return; } if (exists(filename)) { String title = "Cannot create files"; String msg = String.format(Locale.US, "`%s` already exists", filename); showMessageDialog(title, msg); return; } Application app = ApplicationManager.getApplication(); app.runWriteAction(new Runnable() { @Override public void run() { try { createDrawable(filename, color, pressed); createDrawableV21(filename, color, pressedV21); } catch (Exception e) { e.printStackTrace(); } } }); super.doOKAction(); } private boolean valid(String filename, String color, String pressed, String pressedV21) { if (filename.isEmpty() || ".xml".equals(filename)) return false; String regex = "^@color/.+"; return color.matches(regex) || pressed.matches(regex) || pressedV21.matches(regex); } private boolean exists(String filename) { String[] dirs = new String[]{drawableDir, drawableV21Dir}; for (String d : dirs) { VirtualFile f = dir.findChild(d); if (f != null && f.isDirectory()) { VirtualFile dest = f.findChild(filename); if (dest != null && dest.exists()) { return true; } } } return false; } private void createDrawable(String filename, String color, String pressed) throws Exception { VirtualFile child = dir.findChild(drawableDir); if (child == null) { child = dir.createChildDirectory(null, drawableDir); } VirtualFile newXmlFile = child.findChild(filename); if (newXmlFile != null && newXmlFile.exists()) { newXmlFile.delete(null); } newXmlFile = child.createChildData(null, filename); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.newDocument(); Element root = doc.createElement("selector"); root.setAttributeNS(nsUri, "xmlns:android", androidUri); doc.appendChild(root); Element item = doc.createElement("item"); item.setAttribute("android:drawable", "@drawable/abc_list_selector_disabled_holo_light"); item.setAttribute("android:state_enabled", "false"); item.setAttribute("android:state_focused", "true"); item.setAttribute("android:state_pressed", "true"); root.appendChild(item); item = doc.createElement("item"); item.setAttribute("android:drawable", "@drawable/abc_list_selector_disabled_holo_light"); item.setAttribute("android:state_enabled", "false"); item.setAttribute("android:state_focused", "true"); root.appendChild(item); item = doc.createElement("item"); item.setAttribute("android:drawable", pressed); item.setAttribute("android:state_focused", "true"); item.setAttribute("android:state_pressed", "true"); root.appendChild(item); item = doc.createElement("item"); item.setAttribute("android:drawable", pressed); item.setAttribute("android:state_focused", "false"); item.setAttribute("android:state_pressed", "true"); root.appendChild(item); item = doc.createElement("item"); item.setAttribute("android:drawable", color); root.appendChild(item); OutputStream os = newXmlFile.getOutputStream(null); PrintWriter out = new PrintWriter(os); StringWriter writer = new StringWriter(); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(INDENT_SPACE, "4"); transformer.transform(new DOMSource(doc), new StreamResult(writer)); out.println(writer.getBuffer().toString()); out.close(); } private void createDrawableV21(String filename, String color, String pressed) throws Exception { VirtualFile child = dir.findChild(drawableV21Dir); if (child == null) { child = dir.createChildDirectory(null, drawableV21Dir); } VirtualFile newXmlFile = child.findChild(filename); if (newXmlFile != null && newXmlFile.exists()) { newXmlFile.delete(null); } newXmlFile = child.createChildData(null, filename); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.newDocument(); Element root = doc.createElement("ripple"); root.setAttributeNS(nsUri, "xmlns:android", androidUri); root.setAttribute("android:color", pressed); doc.appendChild(root); Element item = doc.createElement("item"); item.setAttribute("android:drawable", color); root.appendChild(item); OutputStream os = newXmlFile.getOutputStream(null); PrintWriter out = new PrintWriter(os); StringWriter writer = new StringWriter(); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(INDENT_SPACE, "4"); transformer.transform(new DOMSource(doc), new StreamResult(writer)); out.println(writer.getBuffer().toString()); out.close(); } private void showMessageDialog(String title, String message) { Messages.showMessageDialog( project, message, title, Messages.getErrorIcon()); } }