root/lang/ruby/Amalgam/AppController.rb @ 1193

Revision 1193, 9.7 kB (checked in by cho45, 6 years ago)

lang/ruby/Amalgam/AppController.rb,
lang/ruby/Amalgam/Amalgam.xcodeproj/cho45.mode1,
lang/ruby/Amalgam/Amalgam.xcodeproj/cho45.pbxuser:

起動時におちないように

Line 
1require 'osx/cocoa'
2include OSX
3
4require "pathname"
5
6class TransparentView < NSView
7        def drawRect(rect)
8                super_drawRect(rect)
9                NSColor.clearColor.set
10                NSRectFill(bounds)
11        end
12end
13
14class AmalgamWindow < NSWindow
15        def canBecomeKeyWindow
16                true
17        end
18
19        def canBecomeMainWindow
20                true
21        end
22
23        def sendEvent(e)
24                if e.oc_type == NSKeyDown
25                        return if delegate.process_keyevent(e)
26                end
27                super_sendEvent(e)
28        end
29end
30
31class History < Array
32        attr_accessor :pointer
33
34        def pointer
35                @pointer ||= 0
36        end
37
38        def prev(str)
39                if self.pointer == 0
40                        self.unshift str
41                end
42                self.pointer += 1
43                @pointer = length - 1 if @pointer >= length
44                self[@pointer]
45        end
46
47        def next
48                self.pointer -= 1
49                @pointer = 0 if @pointer < 0
50                ret = self[@pointer]
51                if self.pointer == 0
52                        self.shift
53                end
54                ret
55        end
56
57        def add(hist)
58                self.unshift hist
59                self.replace(self.first(20))
60        end
61
62        def reset
63                while self.pointer > 0
64                        self.next
65                end
66        end
67end
68
69class Candidate
70        attr_accessor :name, :desc, :icon, :proc
71
72        def initialize(name, desc, icon=nil, &block)
73                @desc = desc.to_s
74                @name = name.to_s
75                @proc = block
76                case icon
77                when Symbol
78                        @icon = NSWorkspace.sharedWorkspace.iconForFileType(icon.to_s)
79                when Pathname
80                        @icon = NSWorkspace.sharedWorkspace.iconForFile(icon.to_s)
81                when NilClass
82                        @icon = NSWorkspace.sharedWorkspace.iconForFileType("unknown")
83                when NSData
84                        @icon = NSImage.alloc.initWithData(str)
85                when NSImage
86                        @icon = icon
87                else
88                        str = NSData.objc_send :dataWithBytes, icon.to_s, :length
89                        @icon = NSImage.alloc.initWithData(str)
90                end
91        end
92
93        def call(*args)
94                if @proc.arity.abs > 0
95                        @proc.call(self, *args)
96                end
97        end
98end
99
100class Command < Candidate
101end
102
103require "yaml"
104require "rubygems"
105require "module/pluggable"
106require "plugin_base"
107class AmalgamConfig
108
109        PATH = Pathname.new("#{ENV["HOME"]}/.amalgam")
110
111        pluggable :plugins,         :base_class  => AmalgamPlugin,
112                                    :search_path => (PATH + "plugins").to_s
113        pluggable :default_plugins, :base_class  => AmalgamPlugin,
114                                    :search_path => (NSBundle.mainBundle.resourcePath + "/plugins").to_s
115
116        Default = {
117                :hotkey => "Command+`",
118        }.freeze
119
120        def initialize(controller)
121                @controller = controller
122                begin
123                        @config = Default.merge(YAML.load((PATH+"config.yaml").read))
124                rescue Errno::ENOENT
125                        @config = Default
126                end
127                plugins.init(controller)
128                default_plugins.init(controller)
129        end
130
131        def [](key)
132                @config[key]
133        end
134
135        def initrc
136                (PATH + "initrc.rb").read
137        rescue Errno::ENOENT
138                ""
139        end
140
141        def reload
142                reloaded = []
143                [self.plugins, self.default_plugins].each do |pl|
144                        pl.reload.first.each do |c|
145                                log c
146                                reloaded << [c, pl[c]]
147                                pl[c].init(@controller)
148                        end
149                end
150                reloaded
151        end
152
153        def inspect
154                "#<%s:%#08x>" % [self.class.to_s, self.object_id]
155        end
156end
157
158class AppController < NSObject
159        ib_outlet :view
160        ib_outlet :input
161        ib_outlet :results
162
163        attr_reader :candidates, :sublaunchers
164
165        def candidates
166                if @sublaunchers.empty?
167                        @candidates.inject([]) {|r,(k,v)| r.concat(v) }
168                else
169                        @sublaunchers.last.candidates
170                end
171        end
172
173        def search_text
174                @input.stringValue
175        end
176
177        def search_text=(val)
178                @input.stringValue = val
179        end
180
181        # Tableview
182        def numberOfRowsInTableView(table)
183                @now.first(100).length
184        end
185
186        def tableView_objectValueForTableColumn_row(table, column, row)
187                case column.identifier.to_s.to_sym
188                when :icon
189                        @now[row].icon ||
190                                NSWorkspace.sharedWorkspace.iconForFileType('unknown')
191                when :name
192                        "#{@now[row].name}\n\t#{@now[row].desc}"
193                end
194        end
195
196        def tableView_setObjectValue_forTableColumn_row(table, value, column, row)
197        end
198
199        def tableView_willDisplayCell_forTableColumn_row(table, cell, column, row)
200                case column.identifier.to_s.to_sym
201                when :icon
202                when :name
203                        cell.allowsEditingTextAttributes = true
204                        str = NSMutableAttributedString.alloc.init
205                        str.appendAttributedString(NSAttributedString.alloc.initWithString_attributes(@now[row].name + "\n", {
206                                NSForegroundColorAttributeName => NSColor.blackColor
207                        }))
208                        str.appendAttributedString(NSAttributedString.alloc.initWithString_attributes(@now[row].desc, {
209                                NSForegroundColorAttributeName => NSColor.lightGrayColor,
210                                NSFontAttributeName            => NSFont.systemFontOfSize(10)
211                        }))
212                        cell.attributedStringValue = str
213                end
214        end
215
216        def run_current_selection(*)
217                if @now[@results.selectedRow]
218                        begin
219                                @now[@results.selectedRow].call
220                        rescue Exception => e
221                                log e
222                                $stderr.puts e.backtrace
223                                alert("Error", e.inspect)
224                        end
225                end
226        end
227
228        def controlTextDidChange(anot)
229                filtering @input.stringValue
230                @results.selectRowIndexes_byExtendingSelection(NSIndexSet.alloc.initWithIndex(0), false)
231                @results.scrollRowToVisible(0)
232        end
233
234        def windowDidResignKey(anot)
235                #@window.orderOut(nil)
236        end
237
238        def windowDidResignMain(anot)
239                NSApp.hide(nil)
240        end
241
242        def windowDidBecomeMain(anot)
243                @window.makeFirstResponder(@input)
244        end
245
246        def windowDidBecomeKey(anot)
247                @window.makeFirstResponder(@input)
248                @sublaunchers.clear
249                self.clear
250                @history.reset
251                @results.selectRowIndexes_byExtendingSelection(NSIndexSet.alloc.initWithIndex(0), false)
252        end
253
254        def applicationWillFinishLaunching(n)
255                #set_status("Finish Launching", false)
256                #@combo.cell.placeholderString = "Finish Launching"
257        end
258
259        def awakeFromNib
260                @sublaunchers = []
261                @input.stringValue
262                @history = History.new
263                log "Loading Config"
264                @config  = AmalgamConfig.new(self)
265
266                rect = @view.frame
267
268                @window = AmalgamWindow.alloc.objc_send(
269                        :initWithContentRect, rect,
270                        :styleMask, NSBorderlessWindowMask,
271                        :backing, NSBackingStoreBuffered,
272                        :defer, 0
273                )
274                #@window.setLevel(1000) # NSScreenSaverWindowLevel
275
276                @window.opaque = true
277                @window.setContentView(@view)
278                @window.delegate = self
279
280                screenFrame = NSScreen.mainScreen.frame
281                @window.setFrameOrigin([screenFrame.size.width / 2, screenFrame.size.height / 2])
282
283                raise "HotKey is not defined" if @config[:hotkey].nil?
284                NSApp.register_hotkey(@config[:hotkey]) do
285                        if NSApp.isActive?
286                                NSApp.hide(nil)
287                        else
288                                NSApp.activateIgnoringOtherApps(true)
289                                @window.makeMainWindow
290                                @window.orderFrontRegardless
291                                @window.makeKeyAndOrderFront(@window)
292                        end
293                end
294
295
296                build_candidates()
297                @results.dataSource = self
298
299                NSApp.activateIgnoringOtherApps(true)
300                @window.makeFirstResponder(@input)
301
302                eval(@config.initrc, binding)
303        end
304
305        def filtering(str)
306                str = str.to_s
307
308                if sublaunchers.empty? ||
309                   !sublaunchers.last.respond_to?(:filter)
310
311                        r = /(#{str.split(//).map {|c| Regexp.escape(c) }.join(").*?(")})/i
312                        @now = candidates.select {|i|
313                                if i.kind_of? Command
314                                        /^#{Regexp.escape(i.name)}(\s.*)?\z/ === str
315                                else
316                                        r === i.name
317                                end
318                        }.sort_by {|i|
319                                # 文字が前のほうに集っているほど高ランクになるように
320                                m = r.match(i.name)
321                                if m
322                                        (0...m.size).map {|j| m.begin(j) }.inject {|s,j| s + j } * 1000 + i.name.length
323                                else
324                                        # Command 引数ありのとき
325                                        i.name.length
326                                end
327                        }
328                else
329                        @now = sublaunchers.last.filter(candidates, str)
330                end
331                @results.reloadData
332        end
333
334        def nextCandidate(sender)
335                if @history.pointer > 0
336                        @input.stringValue = @history.next
337                else
338                        if @results.selectedRow <= @now.size
339                                @results.selectRowIndexes_byExtendingSelection(NSIndexSet.alloc.initWithIndex(@results.selectedRow+1), false)
340                                @results.scrollRowToVisible(@results.selectedRow)
341                        end
342                end
343                filtering @input.stringValue
344        end
345
346        def prevCandidate(sender)
347                if @results.selectedRow > 0
348                        @results.selectRowIndexes_byExtendingSelection(NSIndexSet.alloc.initWithIndex(@results.selectedRow-1), false)
349                        @results.scrollRowToVisible(@results.selectedRow)
350                else
351                        unless @history.empty?
352                                @input.stringValue = @history.prev(@input.stringValue)
353                        end
354                end
355                filtering @input.stringValue
356        end
357
358        def process_keyevent(e)
359                key = key_string(e)
360                #log key
361                case key
362                when "C-n"
363                        nextCandidate nil
364                when "C-p"
365                        prevCandidate nil
366                when "\r"
367                        @history.reset
368                        if @input.stringValue.strip.empty?
369                                if @sublaunchers.empty?
370                                        NSApp.hide(nil)
371                                else
372                                        n = @sublaunchers.size
373                                        run_current_selection
374                                        NSApp.hide(nil) if @sublaunchers.size <= n
375                                end
376                        else
377                                @history.add @input.stringValue
378                                run_current_selection
379                                NSApp.hide(nil) if @sublaunchers.empty?
380                        end
381                when "\t"
382                        return true
383                when "S-\t"
384                        return true
385                when "C-u"
386                        @input.stringValue = ""
387                        filtering ""
388                when "C-b"
389                        @sublaunchers.pop
390                        filtering ""
391                when "C-c"
392                        NSApp.hide(nil)
393                end
394
395                false
396        end
397
398        def key_string(e)
399                key = ""
400                m = e.modifierFlags
401                key << "S-" if m & NSShiftKeyMask > 0
402                key << "C-" if m & NSControlKeyMask > 0
403                key << "M-" if m & NSAlternateKeyMask > 0
404                key << "G-" if m & NSCommandKeyMask > 0 # TODO
405                key << e.charactersIgnoringModifiers.to_s
406                key
407        end
408
409        def restart(*)
410                log "restart"
411                path = NSBundle.mainBundle.executablePath.to_s
412                fork do
413                        sleep 2
414                        system(path)
415                end
416                NSApp.terminate(nil)
417        end
418
419        def build_candidates
420                @candidates = {}
421                @sublaunchers.clear
422#
423#               @candidates << Candidate.new("Exit", "Exit Amalgam", :sh) do
424#                       if OSX.NSRunAlertPanel("Sure?", "exit ok?", "Exit", "Cancel", nil) == NSAlertDefaultReturn
425#                               NSApp.terminate(nil)
426#                       end
427#               end
428
429                begin
430                        @config.default_plugins.candidates.each do |k,v|
431                                @candidates[k] = v
432                        end
433                        @config.plugins.candidates.each do |k,v|
434                                @candidates[k] = v
435                        end
436                rescue => e
437                        log e
438                        log e.backtrace.join("\n\t")
439                end
440
441                @now = candidates
442                log "Search done"
443        end
444
445        def clear
446                @input.stringValue = ""
447                filtering ""
448        end
449
450        def reload_plugins
451                ret = []
452                @sublaunchers.clear
453                @config.reload.each do |k,v|
454                        ret << k
455                        @candidates[k] = v.candidates
456                end
457                ret
458        end
459
460        # match するプラグインを強制リロード
461        def r(match)
462                @sublaunchers.clear
463                ret = []
464                [@config.plugins, @config.default_plugins].each do |pl|
465                        pl.each do |k,v|
466                                next unless match === k
467                                ret << k
468                                @candidates[k] = v.candidates
469                        end
470                end
471                ret
472        end
473
474
475end
476
477
Note: See TracBrowser for help on using the browser.