还记得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自定义扩展点方式完成扩展开发,代码如下:
到此为止插件功能开发及与之匹配的SDK开发均已完成,还不快去找自己喜欢的矢量图标。
最后,陈述一些看法,ideaide做为现阶段比较流行的IDE,在国内基本均是已使用,配置为主,但其实作为工具类这一基础软件,在IDEA IDE社区版的发布,代码开放这一给力的条件下,做为开发人员应更深入了解其本身的运行原理,学习其先进理念,为基础软件开发积累经验,本人当前从事Eclipse插件开发,其中使用的EMF相关技术亦可与IDEAIDE插件开发进行结合,希望有机会和大家一起分享。谢谢
相关代码:https://github.com/OuYuBin/VectorIcons