From ce0863694786e40ddb806224680873806e3b1d70 Mon Sep 17 00:00:00 2001 From: Brian Hsu Date: Mon, 16 Jul 2012 13:35:18 +0800 Subject: [PATCH] Extract Image loading to another util object. --- src/main/scala/irc/IRCBot.scala | 82 +++++++++++++++++----------- src/main/scala/irc/IRCMessage.scala | 34 +++++++++++- src/main/scala/ui/MainWindow.scala | 4 +- src/main/scala/utils/Avatar.scala | 16 +----- src/main/scala/utils/Emotes.scala | 32 +++++------ src/main/scala/utils/ImageUtil.scala | 51 +++++++++++++++++ src/main/scala/utils/Util.scala | 24 ++++---- 7 files changed, 162 insertions(+), 81 deletions(-) create mode 100644 src/main/scala/utils/ImageUtil.scala diff --git a/src/main/scala/irc/IRCBot.scala b/src/main/scala/irc/IRCBot.scala index 51bfa57..f9ba1e4 100644 --- a/src/main/scala/irc/IRCBot.scala +++ b/src/main/scala/irc/IRCBot.scala @@ -8,11 +8,25 @@ import org.pircbotx.hooks.events._ import scala.collection.JavaConversions._ import I18N.i18n._ +/** + * IRC 機器人 + * + * @param hostname IRC Server + * @param port IRC Server Port + * @param nickname 要使用的 IRC 暱稱 + * @param password IRC Server 密碼 + * @param channel 要加入的 IRC 頻道 + * @param callback 機器人收到 IRC 來的訊息時的 Callback + * @param onLog 當 IRCBot 進行 log 時的 Callback + * @param onError 當錯誤發生時的 Callback + * @param showJoin 是否顯示加入聊天室訊息 + * @param showLeave 是否顯示離開聊天室訊息 + */ class IRCBot(hostname: String, port: Int, nickname: String, password: Option[String], channel: String, - callback: IRCMessage => Any = IRCBot.doNothing, - onLog: String => Any = IRCBot.doNothing, - onError: Exception => Any = IRCBot.doNothing, + callback: IRCMessage => Any, + onLog: String => Any, + onError: Exception => Any, showJoin: Boolean = false, showLeave: Boolean = false) extends PircBotX { @@ -54,7 +68,8 @@ class IRCBot(hostname: String, port: Int, nickname: String, val sender = event.getUser.getNick showJoin match { - case true => callback(SystemMessage(tr("[SYS] %s has joined") format(sender))) + case true => + callback(SystemMessage(tr("[SYS] %s has joined") format(sender))) case false if (sender == nickname) => callback(SystemMessage(tr("[SYS] %s has joined") format(sender))) case _ => @@ -75,6 +90,9 @@ class IRCBot(hostname: String, port: Int, nickname: String, onLog(line) } + /** + * 連線至 IRC Server + */ private def connect() { password match { @@ -83,38 +101,45 @@ class IRCBot(hostname: String, port: Int, nickname: String, } } + /** + * 停止 IRC 機器人 + */ def stop() { - val thread = new Thread() - { - override def run() - { - if (IRCBot.this.isConnected) { - IRCBot.this.disconnect() - } + runByThread { + if (IRCBot.this.isConnected) { + IRCBot.this.disconnect() } } - - thread.start() } + /** + * 啟動 IRC 機器人 + */ def start() + { + runByThread { + try { + IRCBot.this.setAutoNickChange(true) + IRCBot.this.setVerbose(true) + IRCBot.this.setName(nickname) + IRCBot.this.setEncoding("UTF-8") + IRCBot.this.connect() + IRCBot.this.getListenerManager.addListener(Callbacks) + IRCBot.this.joinChannel(channel) + } catch { + case e: Exception => onError(e) + } + } + } + + def runByThread(action: => Any) { val thread = new Thread() { override def run() { - try { - IRCBot.this.setAutoNickChange(true) - IRCBot.this.setVerbose(true) - IRCBot.this.setName(nickname) - IRCBot.this.setEncoding("UTF-8") - IRCBot.this.connect() - IRCBot.this.getListenerManager.addListener(Callbacks) - IRCBot.this.joinChannel(channel) - } catch { - case e: Exception => onError(e) - } + action } } @@ -124,12 +149,3 @@ class IRCBot(hostname: String, port: Int, nickname: String, } -object IRCBot -{ - def doNothing(message: String) {} - def doNothing(message: IRCMessage) {} - def doNothing(exception: Exception) { - println("From IRCBot.doNothing:") - exception.printStackTrace() - } -} diff --git a/src/main/scala/irc/IRCMessage.scala b/src/main/scala/irc/IRCMessage.scala index 93fdaa7..fcd11ab 100644 --- a/src/main/scala/irc/IRCMessage.scala +++ b/src/main/scala/irc/IRCMessage.scala @@ -2,9 +2,7 @@ package org.bone.ircballoon import I18N.i18n._ -sealed trait IRCMessage { - val message: String -} +sealed trait IRCMessage trait HasUser { @@ -13,11 +11,22 @@ trait HasUser import Avatar.displayAvatar import Avatar.onlyAvatar + /** + * 視狀況取代使用者名稱取代為 Twitch 的暱稱 + * + */ def convertedNickname = Avatar.usingTwitchNickname match { case true => Avatar.getTwitchNicknameCache(nickname).getOrElse(nickname) case false => nickname } + /** + * 加入 Avatar 代碼 + * + * 如果有開啟 Avatar 功能,而且該使用者有 Avatar,就加 + * 入 Avatar 控制碼 [nickname],UI 看到這個控制碼的時 + * 候會取代為使用者的 Avatar。 + */ def userDisplay = Avatar(nickname) match { case Some(image) if displayAvatar && onlyAvatar => "[%s]" format(nickname) case Some(image) if displayAvatar => "[%s] %s" format(nickname, convertedNickname) @@ -25,6 +34,13 @@ trait HasUser } } +/** + * IRC 聊天訊息 + * + * @param nickname 使用者暱稱 + * @param isOp 使用者是否為 IRC 頻道管理員 + * @param message 訊息 + */ case class ChatMessage(nickname: String, isOp: Boolean, message: String) extends IRCMessage with HasUser { @@ -34,12 +50,24 @@ case class ChatMessage(nickname: String, isOp: Boolean, } } +/** + * IRC 動作訊息 + * + * @param nickname 使用者暱稱 + * @param isOp 使用者是否為 IRC 頻道管理員 + * @param message 使用者的動作 + */ case class ActionMessage(nickname: String, isOp: Boolean, message: String) extends IRCMessage with HasUser { override def toString = tr("[Action] %s %s") format(userDisplay, message) } +/** + * IRC 系統訊息 + * + * @param message 系統訊息 + */ case class SystemMessage(message: String) extends IRCMessage { override def toString = message diff --git a/src/main/scala/ui/MainWindow.scala b/src/main/scala/ui/MainWindow.scala index 1412686..a1bef85 100644 --- a/src/main/scala/ui/MainWindow.scala +++ b/src/main/scala/ui/MainWindow.scala @@ -11,7 +11,9 @@ import org.eclipse.swt.custom.ScrolledComposite import org.eclipse.swt._ import I18N.i18n._ - +/** + * 主視窗 + */ object MainWindow extends SWTHelper { Display.setAppName("IRCBalloon") diff --git a/src/main/scala/utils/Avatar.scala b/src/main/scala/utils/Avatar.scala index d837e32..91dae8a 100644 --- a/src/main/scala/utils/Avatar.scala +++ b/src/main/scala/utils/Avatar.scala @@ -2,6 +2,7 @@ package org.bone.ircballoon import org.eclipse.swt.widgets._ import org.eclipse.swt.graphics._ +import ImageUtil._ object Avatar { @@ -52,12 +53,6 @@ object Avatar def getTwitchAvatar(nickname: String): Option[Image] = { def isDefault(url: String) = url.contains("404_user") - def getImage(url: String) = { - val inputStream = new URL(url).openStream - val image = new Image(Display.getDefault, inputStream) - inputStream.close() - Some(image) - } try { val profileURL = "http://api.justin.tv/api/user/show/" + nickname + ".xml" @@ -66,7 +61,7 @@ object Avatar (twitchUserXML \\ "image_url_tiny").map(_.text).filterNot(isDefault) val avatarImage = imageURLTiny match { - case imageURL :: Nil => getImage(imageURL) + case imageURL :: Nil => loadFromURL(imageURL) case _ => None } @@ -89,14 +84,9 @@ object Avatar } - def getImageFile(filePath: String) = - { - new Image(Display.getDefault, filePath) - } - def addAvatar(nickname: String, imagePath: String) { - customAvatars += (nickname -> (imagePath, getImageFile(imagePath))) + customAvatars += (nickname -> (imagePath, loadFromFile(imagePath).get)) } def removeAvatar(nickname: String) diff --git a/src/main/scala/utils/Emotes.scala b/src/main/scala/utils/Emotes.scala index 72bcb11..f96ddfd 100644 --- a/src/main/scala/utils/Emotes.scala +++ b/src/main/scala/utils/Emotes.scala @@ -3,6 +3,8 @@ package org.bone.ircballoon import org.eclipse.swt.widgets._ import org.eclipse.swt.graphics._ +import ImageUtil._ + object Emotes { var useDefault: Boolean = true @@ -15,7 +17,10 @@ object Emotes def addEmote(emotes: EmoteIcon) { customEmotes = customEmotes.updated(emotes.targetText, emotes.imagePath) - customIcons = customIcons.updated(emotes.targetText, getImageFile(emotes.imagePath)) + customIcons = customIcons.updated( + emotes.targetText, + loadFromFile(emotes.imagePath).get + ) } def removeEmote(targetText: String) @@ -25,15 +30,6 @@ object Emotes customIcons -= targetText } - def getImageFile(filePath: String) = - { - new Image(Display.getDefault, filePath) - } - - def getImage(path: String) = { - new Image(Display.getDefault, getClass().getResourceAsStream(path)) - } - def getEmotes: Map[String, Image] = { useDefault match { @@ -43,13 +39,13 @@ object Emotes } val default = Map( - ":)" -> getImage("/emotes/face-smile.png"), - ":D" -> getImage("/emotes/face-laugh.png"), - ":o" -> getImage("/emotes/face-surprise.png"), - ":(" -> getImage("/emotes/face-sad.png"), - ":p" -> getImage("/emotes/face-raspberry.png"), - "8)" -> getImage("/emotes/face-cool.png"), - ":X" -> getImage("/emotes/face-angry.png"), - ";)" -> getImage("/emotes/face-wink.png") + ":)" -> loadFromResource("/emotes/face-smile.png").get, + ":D" -> loadFromResource("/emotes/face-laugh.png").get, + ":o" -> loadFromResource("/emotes/face-surprise.png").get, + ":(" -> loadFromResource("/emotes/face-sad.png").get, + ":p" -> loadFromResource("/emotes/face-raspberry.png").get, + "8)" -> loadFromResource("/emotes/face-cool.png").get, + ":X" -> loadFromResource("/emotes/face-angry.png").get, + ";)" -> loadFromResource("/emotes/face-wink.png").get ) } diff --git a/src/main/scala/utils/ImageUtil.scala b/src/main/scala/utils/ImageUtil.scala new file mode 100644 index 0000000..6e28179 --- /dev/null +++ b/src/main/scala/utils/ImageUtil.scala @@ -0,0 +1,51 @@ +package org.bone.ircballoon + +import org.eclipse.swt.SWT +import org.eclipse.swt.widgets.Display +import org.eclipse.swt.graphics.Image +import org.eclipse.swt.graphics.GC + +import scala.util.control.Exception._ + +object ImageUtil +{ + import java.net.URL + + lazy val display = Display.getDefault + + def loadFromResource(path: String) = allCatch.opt { + new Image(display, getClass().getResourceAsStream(path)) + } + + def loadFromFile(filePath: String) = allCatch.opt { + new Image(display, filePath) + } + + def loadFromURL(url: String) = allCatch.opt { + val inputStream = new URL(url).openStream + val image = new Image(Display.getDefault, inputStream) + inputStream.close() + image + } + + def resize(image: Image, size: (Int, Int)) = + { + val (width, height) = size + val scaled = new Image(display, width, height) + val gc = new GC(scaled) + + gc.setAntialias(SWT.ON) + gc.setInterpolation(SWT.HIGH) + gc.drawImage( + image, 0, 0, + image.getBounds().width, image.getBounds().height, + 0, 0, width, height + ) + + gc.dispose() + image.dispose() + + scaled + } +} + diff --git a/src/main/scala/utils/Util.scala b/src/main/scala/utils/Util.scala index e807ffb..5f89ebd 100644 --- a/src/main/scala/utils/Util.scala +++ b/src/main/scala/utils/Util.scala @@ -1,14 +1,13 @@ package org.bone.ircballoon import org.eclipse.swt._ -import org.eclipse.swt.widgets.{List => SWTList, _} -import org.eclipse.swt.layout._ -import org.eclipse.swt.events._ +import org.eclipse.swt.widgets.Display import org.eclipse.swt.graphics._ -import org.eclipse.swt.custom.StyledText import org.xnap.commons.i18n.I18nFactory + import java.util.Locale +import scala.util.control.Exception._ object I18N { @@ -18,13 +17,15 @@ object I18N object MyIcon { - val appIcon = new Image(Display.getDefault, getClass().getResourceAsStream("/appIcon.png")) - val ircOP = new Image(Display.getDefault, getClass().getResourceAsStream("/opIcon.png")) - val preference = new Image(Display.getDefault, getClass().getResourceAsStream("/preference.png")) + import ImageUtil.loadFromResource + + val appIcon = loadFromResource("/appIcon.png").get + val ircOP = loadFromResource("/opIcon.png").get + val preference = loadFromResource("/preference.png").get - val close = new Image(Display.getDefault, getClass().getResourceAsStream("/close.png")) - val add = new Image(Display.getDefault, getClass().getResourceAsStream("/add.png")) - val remove = new Image(Display.getDefault, getClass().getResourceAsStream("/remove.png")) + val close = loadFromResource("/close.png").get + val add = loadFromResource("/add.png").get + val remove = loadFromResource("/remove.png").get } object MyColor @@ -32,7 +33,6 @@ object MyColor lazy val Black = new Color(Display.getDefault, 0, 0, 0) lazy val White = new Color(Display.getDefault, 255, 255, 255) lazy val Blue = new Color(Display.getDefault, 100, 100, 255) - } object MyFont @@ -66,7 +66,5 @@ object MessageSample val repeat = (size / samples.length) + 1 Random.shuffle(List.fill(repeat)(samples).flatten).take(size) } - - }