| 1 | |
|---|
| 2 | |
|---|
| 3 | begin |
|---|
| 4 | require 'rubygems' |
|---|
| 5 | rescue LoadError |
|---|
| 6 | end |
|---|
| 7 | require 'osx/cocoa' |
|---|
| 8 | require 'osx/hotkey' |
|---|
| 9 | require 'drb/drb' |
|---|
| 10 | require 'yaml' |
|---|
| 11 | require 'logger' |
|---|
| 12 | require 'thread' |
|---|
| 13 | require 'pathname' |
|---|
| 14 | require 'uri' |
|---|
| 15 | |
|---|
| 16 | require 'OutIO' |
|---|
| 17 | require 'MainView' |
|---|
| 18 | |
|---|
| 19 | include OSX |
|---|
| 20 | |
|---|
| 21 | class AppController < NSObject |
|---|
| 22 | ib_outlets :menu |
|---|
| 23 | ib_outlets :winConfig |
|---|
| 24 | ib_outlets :configController |
|---|
| 25 | |
|---|
| 26 | ib_outlets :menuToggleHide, :menuToggleTimeout, :menuToggleIgnoreMouseEvent, :menuToggleStealthMode |
|---|
| 27 | |
|---|
| 28 | attr_reader :window |
|---|
| 29 | |
|---|
| 30 | def about(sender) |
|---|
| 31 | path = Pathname.new NSBundle.mainBundle.resourcePath.to_s |
|---|
| 32 | OSX::NSApp.orderFrontStandardAboutPanelWithOptions({ |
|---|
| 33 | # 'Credits' => nil, |
|---|
| 34 | 'Copyright' => 'Creative-Commons by cho45(さとう)', |
|---|
| 35 | 'Version' => File.read(path + 'VERSION'), |
|---|
| 36 | }) |
|---|
| 37 | end |
|---|
| 38 | |
|---|
| 39 | def toggleHide(sender) |
|---|
| 40 | if sender.state == NSOnState |
|---|
| 41 | sender.setState NSOffState |
|---|
| 42 | #@window.deminiaturize |
|---|
| 43 | @window.orderFront(nil) |
|---|
| 44 | @view.fadein |
|---|
| 45 | else |
|---|
| 46 | sender.setState NSOnState |
|---|
| 47 | @window.orderOut(nil) |
|---|
| 48 | end |
|---|
| 49 | end |
|---|
| 50 | |
|---|
| 51 | def toggleStealthMode(sender) |
|---|
| 52 | if sender.state == NSOnState |
|---|
| 53 | sender.setState NSOffState |
|---|
| 54 | @stealth = false |
|---|
| 55 | else |
|---|
| 56 | sender.setState NSOnState |
|---|
| 57 | @stealth = true |
|---|
| 58 | end |
|---|
| 59 | end |
|---|
| 60 | |
|---|
| 61 | def toggleIgnoreMouseEvent(sender) |
|---|
| 62 | if sender.state == NSOnState |
|---|
| 63 | sender.setState NSOffState |
|---|
| 64 | @window.setIgnoresMouseEvents(false) |
|---|
| 65 | @logger.info _("Now This Window is Clickable") |
|---|
| 66 | else |
|---|
| 67 | sender.setState NSOnState |
|---|
| 68 | @window.setIgnoresMouseEvents(true) |
|---|
| 69 | @logger.info _("Ignore Mouse Events") |
|---|
| 70 | end |
|---|
| 71 | end |
|---|
| 72 | |
|---|
| 73 | def toggleTimeout(sender) |
|---|
| 74 | # 消さないようにする |
|---|
| 75 | if sender.state == NSOnState |
|---|
| 76 | sender.setState NSOffState |
|---|
| 77 | @timeout = false |
|---|
| 78 | @view.fadeout |
|---|
| 79 | else |
|---|
| 80 | sender.setState NSOnState |
|---|
| 81 | @th.kill if @th |
|---|
| 82 | @timeout = true |
|---|
| 83 | @view.fadein |
|---|
| 84 | end |
|---|
| 85 | end |
|---|
| 86 | |
|---|
| 87 | def quit(sender) |
|---|
| 88 | OSX::NSApp.stop(nil) |
|---|
| 89 | end |
|---|
| 90 | |
|---|
| 91 | # status icon clicked |
|---|
| 92 | def itemclicked(sender) |
|---|
| 93 | @sitem.popUpStatusItemMenu @menu |
|---|
| 94 | true |
|---|
| 95 | end |
|---|
| 96 | |
|---|
| 97 | def mouseEntered(event) |
|---|
| 98 | if @stealth |
|---|
| 99 | @view.fadeout |
|---|
| 100 | @mousein = true |
|---|
| 101 | end |
|---|
| 102 | end |
|---|
| 103 | |
|---|
| 104 | def mouseExited(event) |
|---|
| 105 | if @stealth |
|---|
| 106 | @view.fadein |
|---|
| 107 | @mousein = false |
|---|
| 108 | end |
|---|
| 109 | end |
|---|
| 110 | |
|---|
| 111 | def awakeFromNib |
|---|
| 112 | @configController.setDelegate(self) |
|---|
| 113 | @configController.load_config |
|---|
| 114 | @config = @configController.config |
|---|
| 115 | |
|---|
| 116 | @stealth = @config[:stealth] |
|---|
| 117 | |
|---|
| 118 | # メニューの初期設定 |
|---|
| 119 | @menuToggleIgnoreMouseEvent.setState @config[:ignore_mouse] ? NSOnState : NSOffState |
|---|
| 120 | @menuToggleTimeout.setState @config[:dont_fadeout] ? NSOnState : NSOffState |
|---|
| 121 | @menuToggleStealthMode.setState @config[:stealth] ? NSOnState : NSOffState |
|---|
| 122 | |
|---|
| 123 | |
|---|
| 124 | # ステータスアイコンを取得 |
|---|
| 125 | icon, acon = %w(status status_alt).map { |name| |
|---|
| 126 | path = NSBundle.mainBundle.pathForResource_ofType(name, "png") |
|---|
| 127 | NSImage.alloc.initByReferencingFile(path) |
|---|
| 128 | } |
|---|
| 129 | |
|---|
| 130 | # ステータスアイコンを登録 |
|---|
| 131 | @sitem = NSStatusBar.systemStatusBar.statusItemWithLength -1 |
|---|
| 132 | @sitem.setTarget self |
|---|
| 133 | @sitem.setAction "itemclicked_" |
|---|
| 134 | @sitem.setToolTip "OpenMenu" |
|---|
| 135 | @sitem.setImage icon |
|---|
| 136 | @sitem.setAlternateImage acon |
|---|
| 137 | @sitem.setHighlightMode true |
|---|
| 138 | |
|---|
| 139 | # メインウィンドウの初期化 |
|---|
| 140 | height = (@config[:lines] + 1) * @config[:font_size] * @config[:line_height] |
|---|
| 141 | rect = [0, 0, @config[:width], height] |
|---|
| 142 | |
|---|
| 143 | @window = NSWindow.alloc.objc_send(:initWithContentRect, rect, |
|---|
| 144 | :styleMask, NSBorderlessWindowMask, |
|---|
| 145 | :backing, NSBackingStoreBuffered, |
|---|
| 146 | :defer, 0) |
|---|
| 147 | @window.opaque = false |
|---|
| 148 | @window.hasShadow = false |
|---|
| 149 | @window.setLevel(1000) # NSScreenSaverWindowLevel |
|---|
| 150 | @window.setMovableByWindowBackground(true) |
|---|
| 151 | @window.setIgnoresMouseEvents(@config[:ignore_mouse]) |
|---|
| 152 | |
|---|
| 153 | |
|---|
| 154 | @view = MainView.alloc.initWithFrame(rect) |
|---|
| 155 | @view.init |
|---|
| 156 | set_view_props |
|---|
| 157 | |
|---|
| 158 | @window.setContentView(@view) |
|---|
| 159 | # addTrackingRect は window に view が関連づいてからでなければならない |
|---|
| 160 | @view.objc_send(:addTrackingRect, rect, :owner, self, :userData, 'track', :assumeInside, true) |
|---|
| 161 | |
|---|
| 162 | screenFrame = NSScreen.mainScreen.frame |
|---|
| 163 | @window.setFrameOrigin([screenFrame.size.width - @config[:width], 5]) |
|---|
| 164 | |
|---|
| 165 | # いでよ |
|---|
| 166 | @window.makeKeyAndOrderFront(nil) |
|---|
| 167 | @window.orderFrontRegardless |
|---|
| 168 | |
|---|
| 169 | # 再描画リクエストスレッド |
|---|
| 170 | Thread.start do |
|---|
| 171 | th = nil |
|---|
| 172 | loop do |
|---|
| 173 | if @redraw |
|---|
| 174 | @view.fadein unless @mousein |
|---|
| 175 | @view.setNeedsDisplay(true) |
|---|
| 176 | th.kill if th |
|---|
| 177 | unless @timeout |
|---|
| 178 | th = Thread.start do |
|---|
| 179 | sleep @config[:fadeout_time] |
|---|
| 180 | @view.fadeout |
|---|
| 181 | end |
|---|
| 182 | end |
|---|
| 183 | @redraw = false |
|---|
| 184 | end |
|---|
| 185 | sleep 0.5 |
|---|
| 186 | end |
|---|
| 187 | end |
|---|
| 188 | |
|---|
| 189 | # DRb 開始 |
|---|
| 190 | @front = Proc.new do |str| |
|---|
| 191 | str = str.to_s |
|---|
| 192 | @view.out << str |
|---|
| 193 | @redraw = true |
|---|
| 194 | str.length |
|---|
| 195 | end |
|---|
| 196 | DRb.start_service(@config[:uri], @front) |
|---|
| 197 | |
|---|
| 198 | # ホットキー追加 |
|---|
| 199 | set_hotkeys |
|---|
| 200 | |
|---|
| 201 | # ロガーの設定 |
|---|
| 202 | logdev = OutIO.new(@front) |
|---|
| 203 | @logger = Logger.new(logdev) |
|---|
| 204 | @logger.datetime_format = '%H:%M:%S ' |
|---|
| 205 | @configController.logger = @logger |
|---|
| 206 | |
|---|
| 207 | @logger.info _('Initialize Completed') |
|---|
| 208 | @logger.info "DRb URI: #{DRb.uri}" |
|---|
| 209 | end |
|---|
| 210 | |
|---|
| 211 | def set_hotkeys |
|---|
| 212 | @config[:keys].each do |k,v| |
|---|
| 213 | next unless v |
|---|
| 214 | case k |
|---|
| 215 | when :toggle_hide |
|---|
| 216 | k = :toggleHide |
|---|
| 217 | when :toggle_timeout |
|---|
| 218 | k = :toggleTimeout |
|---|
| 219 | when :ignore_mouse_event |
|---|
| 220 | k = :toggleIgnoreMouseEvent |
|---|
| 221 | end |
|---|
| 222 | begin |
|---|
| 223 | OSX::NSApp.register_hotkey(v) do |
|---|
| 224 | k = k.to_s |
|---|
| 225 | send(k, instance_variable_get("@menu#{k[0].chr.upcase}#{k[1..-1]}")) |
|---|
| 226 | end |
|---|
| 227 | rescue ArgumentError => e |
|---|
| 228 | @logger.info "Key #{v} set on #{v} is invalid.. Ignored." |
|---|
| 229 | end |
|---|
| 230 | end |
|---|
| 231 | end |
|---|
| 232 | |
|---|
| 233 | def set_view_props |
|---|
| 234 | @view.config = @config |
|---|
| 235 | @view.attr = make_attributes |
|---|
| 236 | end |
|---|
| 237 | |
|---|
| 238 | def make_attributes |
|---|
| 239 | shadow = NSShadow.alloc.init |
|---|
| 240 | shadow.setShadowColor(NSColor.blackColor) |
|---|
| 241 | shadow.setShadowBlurRadius(2) |
|---|
| 242 | shadow.setShadowOffset([0, 0]) |
|---|
| 243 | NSMutableDictionary.alloc.initWithDictionary({ |
|---|
| 244 | OSX::NSForegroundColorAttributeName => NSColor.whiteColor, |
|---|
| 245 | OSX::NSFontAttributeName => @configController.font, |
|---|
| 246 | OSX::NSShadowAttributeName => shadow, |
|---|
| 247 | }) |
|---|
| 248 | end |
|---|
| 249 | end |
|---|
| 250 | |
|---|
| 251 | |
|---|
| 252 | |
|---|