还记得2013年半夜观看Google
I/O大会,一堆眼花缭乱的数字广告后,主持人提到这次I/O的主角是开发者,接下来给大家演示一系列开发工具和服务时,心想:可能无非就是一些向开发者开放的Google的API,这些对于一个从事Eclipse插件开发者来讲,确实遥远。果不其然,Maps
API,Google Now API,悉数登场,接下来Google Play Games的游戏服务登场,其服务下的游戏和主机平台下的游戏,甚至与掌机平台下的游戏比简直就是小儿科,且演示多人实时游戏时,现场也没成功,实在是让人有洗洗睡了的冲动,就在这时,另一位主持人上场,Android
Studio的图片突然出现在大屏幕上,还未来的及反应,黑色主题的开发环境映入眼帘,项目结构树,包含大段的代码编辑器,难道是Eclipse ADT Plugin,再仔细看,噢,IntelliJ
IDEA,此时主持人的一连串操作,现场已是一篇欢呼,酷酷的应用可视化开发,短短的4分钟时间,已经让我心潮澎湃,一直以来想细致了解Idea
ide插件开发的冲动,顺时被激发了,因为从9.0开源的时候,那份第一时间下载的源码一直没有细致的去了解过,因为一直忙于Eclipse插件的开发,所以4年之后的今天,再次拾起当年写的一个实例插件,再去找当前的中文插件开发论坛,大多都成了使用论坛,唯一的一份中文资料也还是多年前的,再次来到官方wiki,IntelliJIDEA
Plugin Development主题下的内容足已让人感叹,中文社区的idea ide插件的开发者是多么的稀少,显然你可以用Intellij旗下的各类产品写出高质量的java,js,python,ruby,甚至objective-c代码产品,但开发一个成熟的IntelliJ
IDEA插件对我来说却是一件很着迷的事情。
那么IntelliJ IDEA插件开发又是如何实现的呢,IDEA
IDE和Eclipse插件开发开发又有什么不同呢?Eclipse插件开发中已经开发好的插件代码是否可以复用呢?等等问题放在面前需要去探索发现,但相关技术信息获取则已官方WIKI及相关论坛板块为主,而国内开发者又多已使用工具为主,但对工具本身实现却极少关注,但随着9.0的开源,其实代码开放已为广大开发者提供了最好的技术细节。本文就已自身学习实践通过解读源码来分享一些相关技术信息,以便为今后进一步完善插件打好坚实的基础,可以回顾一下官方的一篇Alexey
Efimov编写的IntelliJ IDEA Plugin Development,其中有提到插件是如何工作的一节中关于IntelliJ
IDEA component模型的描述,原文中这样写到:IntelliJIDEA包含多种组件模型,这些组件模型都是基于PicoContainer的,大家应该应该对PicoContainer容器并不陌生,但插件又是如何基于PicoContainer来工作的呢?还是从最佳途径源代码入手来进行理解。
但本文对插件又是如何基于PicoContainer来完成管理和加载不做详细描述,仅仅在应用层面完成一个改变IntelliJ
IDEA界面图标样式的小插件
,为自己打造一个个性化的IDE开发工具,我们都知道IDEAide是纯正的java
swing项目,swing作为传统的跨平台客户端用户界面组件,可定制行
强,用户可通过编写代码就可定制诸如边框,颜色,背景,透明度等属性,完成高度自定义的视觉效果,这次我们利用swing这一特点定制IntelliJ
IDEA
界面图标样式完成一个个性化的小插件,而不仅仅停留在开发工具使用和配置这一肤浅的层面。先看下效果图:
显示图标全部采用字体矢量图标,达到高质量显示效果
图标是如何被渲染绘制在工具栏或者菜单上呢,随着9.0版本的IDEA
IDE社区版的OpenAPI发布,代码开放已为广大开发者提供了最好的技术细节,
通过查看代码,IDEA IDE的图标资源均是通过com.intellij.icons.AllIcons进行统一管理的,图标资源均为png格式,
那么我们能不能用矢量图标来进行显示呢?Swing组件是否支持矢量图标的渲染绘制?答案是肯定的,那么实现思路就可以确定了。
1.根据AllIcons类中关于图标对应的对象均为javax.swing.Icon接口对象,那么构件一个我们自己实现了Icon接口的构件矢量图标对象,
通过覆写ImageIcon中的paintIcon方法来完成我们自己的图标对象是一件很简单的事情,具体写法如下:
class FontIcon(font: Font, iconFont: IIconFont) extends ImageIcon { setSize(14) var size: Integer = _ var iconWidth: Integer = _ var iconHeight: Integer = _ var deriveFont: Font = _ def setSize(size: Integer) = { if (size > 0) { this.size = size deriveFont = font.deriveFont(Font.PLAIN, size.floatValue()) val bufferImage: BufferedImage = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB) val graphics2d: Graphics2D = GraphicsEnvironment.getLocalGraphicsEnvironment.createGraphics(bufferImage) graphics2d.setFont(deriveFont) this.iconWidth = graphics2d.getFontMetrics.charWidth(iconFont.getCode()) this.iconHeight = graphics2d.getFontMetrics.getHeight graphics2d.dispose() } } override def paintIcon(c: Component, g: Graphics, x: Int, y: Int): Unit = { val bufferedImage = new BufferedImage(getIconWidth, getIconHeight, BufferedImage.TYPE_INT_ARGB) val graphics2d = bufferedImage.getGraphics.asInstanceOf[Graphics2D] graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB) graphics2d.setFont(deriveFont) if (!UIUtil.isUnderDarcula) graphics2d.setColor(Color.DARK_GRAY) else graphics2d.setColor(Color.LIGHT_GRAY) graphics2d.drawString(String.valueOf(iconFont.getCode), 0, graphics2d.getFontMetrics.getAscent) graphics2d.dispose(); g.drawImage(bufferedImage, x, y, null) } override def getIconHeight: Int = { return iconHeight } override def getIconWidth: Int = { return iconWidth } }
2.通过编写ApplicationComponent完成对AllIcons中所有的图标对象filed通过反射API完成对象变更,变更结果则是将AllIcons中所有的Icon
对象均指向我们自型构建的矢量图标对象,那么引用这些图标的Swing组件也会呈现我们的自己图标了,代码如下:
class FontIconComponent extends ApplicationComponent { val CACHED_IMAGE_ICON: String = "$CachedImageIcon" val MY_URL: String = "myUrl" val MY_REAL_ICON: String = "myRealIcon" val ALL_ICONS: String = "com.intellij.icons.AllIcons" var iconFontList: java.util.List[IIconFonts] = new java.util.ArrayList[IIconFonts]() override def initComponent(): Unit = { registerFonts val ideaIconClass: Class[_] = Class.forName(ALL_ICONS) val classes: Array[Class[_]] = ideaIconClass.getDeclaredClasses classes.foreach(clazz => fixIcon(clazz)) } def registerFonts: Unit = { val iconFontBeans = Extensions.getExtensions(IconFontEP.ICON_EP_NAME) iconFontBeans.foreach(iconFontBean => initFont(iconFontBean)) } def initFont(iconFontEP: IconFontEP) = { try { val ttf = iconFontEP.getTtf val stream = iconFontEP.getFontClass.getResourceAsStream(ttf) val font = Font.createFont(Font.TRUETYPE_FONT, stream) if (GraphicsEnvironment.getLocalGraphicsEnvironment.registerFont(font)) { val iconFonts = iconFontEP.getIConFonts iconFonts.setFont(font) iconFontList.add(iconFonts) } stream.close() } catch { case ex: Exception => ex.printStackTrace() } } def fixIcon(clazz: Class[_]): Unit = { val fields: Array[Field] = clazz.getDeclaredFields fields.foreach(field => fixIcon(field)) val classes: Array[Class[_]] = clazz.getDeclaredClasses classes.foreach(clazz => fixIcon(clazz)) } def fixIcon(field: Field) = { try { if (Modifier.isStatic(field.getModifiers)) { //--静态属性信息可通过null参数获取对象实例,非静态属性需要通过传入类对象获取实例 val icon = field.get(null) val iconClass = icon.getClass if (iconClass.getName.endsWith(CACHED_IMAGE_ICON)) { val fieldName = field.getName val iconFontArray = iconFontList.toArray iconFontArray.foreach(iconFonts => if (iconFonts.asInstanceOf[IIconFonts].getIConFont(fieldName) != null) patchFields(icon, iconFonts.asInstanceOf[IIconFonts].getFont, iconFonts.asInstanceOf[IIconFonts].getIConFont(fieldName))) } } } catch { case e: Exception => e.printStackTrace() } } def patchFields(obj: Object, font: Font, iconFont: IIconFont) = { try { val clazz = obj.getClass val realIconField = clazz.getDeclaredField(MY_REAL_ICON) val darkField = clazz.getDeclaredField("dark") val fontAwesomeIcon = new FontIcon(font, iconFont) realIconField.setAccessible(true) realIconField.set(obj, fontAwesomeIcon) if (UIUtil.isUnderDarcula) { darkField.setAccessible(true) darkField.set(obj, true) } } catch { case ex: Exception => ex.printStackTrace() } } override def disposeComponent(): Unit = { } override def getComponentName: String = { return "FontIconComponent" }
哈哈,这样我们就完成一个个性化小插件的主要逻辑的开发。是不是很简单。
到这里是不是就结束了呢?其实并没有,从插件的功能上我们确实已完成我们初期的愿景,但对于一个合格开发人员来讲,不光要实现功能,应该为其他开发人员提供友好的接口(SDK),能够通过SDK让更多的开发人员投身当中,不断丰富功能,回到当前插件当中,由于插件中的图标本是按个人喜好来完成定义的,而且更改的图标并不是全部,那么如何通过SDK让更多的开发人员完成图标定义或者充实其他未修改图标呢?我们可以通过IntelliJ
IDEA自定义扩展点方式完成扩展开发,代码如下:
<extensionPoints> <extensionPoint name="iconFont" beanClass="cn.robin.vectorIconPack.api.IconFontEP"></extensionPoint> </extensionPoints> <extensions defaultExtensionNs="com.intellij"> <!-- Add your extensions here --> </extensions> <extensions defaultExtensionNs="cn.robin.vectorIconPack"> <iconFont id="iconFont" implementClass="cn.robin.vectorIconPack.iconFont.IconFonts" ttf="iconfont.ttf"></iconFont> <iconFont id="fontAwesome" implementClass="cn.robin.vectorIconPack.awesomeFont.AwesomeFonts" ttf="fontawesome-webfont.ttf"></iconFont> </extensions>
通过自己编写的SDK完成对自定义扩展点的使用。
到此为止插件功能开发及与之匹配的SDK开发均已完成,还不快去找自己喜欢的矢量图标。
最后,陈述一些看法,ideaide做为现阶段比较流行的IDE,在国内基本均是已使用,配置为主,但其实作为工具类这一基础软件,在IDEA
IDE社区版的发布,代码开放这一给力的条件下,做为开发人员应更深入了解其本身的运行原理,学习其先进理念,为基础软件开发积累经验,本人当前从事Eclipse插件开发,其中使用的EMF相关技术亦可与IDEAIDE插件开发进行结合,希望有机会和大家一起分享。谢谢
相关代码:https://github.com/OuYuBin/VectorIcons