shortcut 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +7 -0
- data/lib/jar/JNativeHook.jar +0 -0
- data/lib/shortcut/errors.rb +4 -0
- data/lib/shortcut/point.rb +13 -0
- data/lib/shortcut/screen.rb +57 -0
- data/lib/shortcut/version.rb +3 -0
- data/lib/shortcut/window.rb +157 -0
- data/lib/shortcut.rb +1 -0
- data/shortcut.gemspec +26 -0
- data/spec/lib/shortcut_point_spec.rb +5 -0
- data/spec/lib/shortcut_window_spec.rb +395 -0
- data/spec/spec_helper.rb +17 -0
- metadata +146 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
jruby
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Décio Ferreira
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Shortcut
|
2
|
+
|
3
|
+
Make your own Bot.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'shortcut'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install shortcut
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Ubuntu 12.04 with Unity-2D
|
22
|
+
|
23
|
+
Ubuntu has a bug related to how unity-2d draws itself on screen.
|
24
|
+
To fix this issue run the following command before you run shortcut:
|
25
|
+
|
26
|
+
$ gconftool --set --type bool /apps/metacity/general/compositing_manager false
|
27
|
+
|
28
|
+
And then log out/in to apply the changes. To restore the default configuration run:
|
29
|
+
|
30
|
+
$ gconftool --set --type bool /apps/metacity/general/compositing_manager true
|
31
|
+
|
32
|
+
And again log out/in to apply the changes.
|
33
|
+
|
34
|
+
See more information about this [bug](https://bugs.launchpad.net/unity-2d/+bug/1081674).
|
35
|
+
|
36
|
+
## Development
|
37
|
+
|
38
|
+
$ ruby --ng-server &
|
39
|
+
$ ruby --ng -S rspec spec
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
1. Fork it
|
44
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
45
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
46
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
47
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
Binary file
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
import java.awt.Rectangle
|
4
|
+
import java.awt.Toolkit
|
5
|
+
|
6
|
+
import javax.imageio.ImageIO
|
7
|
+
|
8
|
+
module Shortcut
|
9
|
+
class Screen
|
10
|
+
def initialize(opts = {})
|
11
|
+
dim = Toolkit.get_default_toolkit.get_screen_size
|
12
|
+
opts = {:x => 0, :y => 0, :width => dim.get_width, :height => dim.get_height}.merge(opts)
|
13
|
+
rectangle = Rectangle.new(opts[:x], opts[:y], opts[:width], opts[:height])
|
14
|
+
|
15
|
+
@robot = Robot.new
|
16
|
+
@image = @robot.create_screen_capture(rectangle)
|
17
|
+
end
|
18
|
+
|
19
|
+
def save_image(pathname)
|
20
|
+
file = java::io::File.new(pathname)
|
21
|
+
ImageIO.write(@image, "PNG", file)
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_color(x, y)
|
25
|
+
@image.getRGB(x, y)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_color_hex(x, y)
|
29
|
+
color = get_color(x, y)
|
30
|
+
sprintf("%02X%02X%02X", (color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF)
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_template(template)
|
34
|
+
for x in 0...(@image.getWidth - template.getWidth)
|
35
|
+
for y in 0...(@image.getHeight - template.getHeight)
|
36
|
+
if loop_through_template(template, x, y)
|
37
|
+
return Point.new(x, y)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
raise "Couldn't find the game board!\nIs the game on the screen?"
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def loop_through_template(template, x, y)
|
48
|
+
for i in 0...template.getWidth
|
49
|
+
for j in 0...template.getHeight
|
50
|
+
return false if get_color(x+i, y+j) - template.getRGB(i, j) != 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'java'
|
2
|
+
require 'jar/JNativeHook.jar'
|
3
|
+
|
4
|
+
import java.awt.Color
|
5
|
+
import java.awt.event.ActionListener
|
6
|
+
import java.awt.event.InputEvent
|
7
|
+
import java.awt.event.WindowListener
|
8
|
+
import java.awt.Robot
|
9
|
+
|
10
|
+
import java.io.ByteArrayInputStream
|
11
|
+
|
12
|
+
import javax.swing.JButton
|
13
|
+
import javax.swing.JFrame
|
14
|
+
import javax.swing.JScrollPane
|
15
|
+
import javax.swing.JTextArea
|
16
|
+
|
17
|
+
import javax.imageio.ImageIO
|
18
|
+
|
19
|
+
import org.jnativehook.GlobalScreen
|
20
|
+
import org.jnativehook.keyboard.NativeKeyListener
|
21
|
+
import org.jnativehook.NativeHookException
|
22
|
+
|
23
|
+
# https://github.com/jruby/jruby/wiki/Persistence
|
24
|
+
Color.__persistent__ = true
|
25
|
+
GlobalScreen.__persistent__ = true
|
26
|
+
JScrollPane.__persistent__ = true
|
27
|
+
JTextArea.__persistent__ = true
|
28
|
+
|
29
|
+
module Shortcut
|
30
|
+
class Window < JFrame
|
31
|
+
include ActionListener
|
32
|
+
include NativeKeyListener
|
33
|
+
include WindowListener
|
34
|
+
|
35
|
+
def create_button(text, action, options = {})
|
36
|
+
options = {enabled: true}.merge(options)
|
37
|
+
JButton.new(text).tap do |btn|
|
38
|
+
btn.setActionCommand(action)
|
39
|
+
btn.addActionListener(self)
|
40
|
+
btn.setEnabled(options[:enabled])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def robot
|
45
|
+
@robot ||= Robot.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def click(x, y)
|
49
|
+
robot.mouseMove(x, y)
|
50
|
+
robot.mousePress(InputEvent::BUTTON1_MASK)
|
51
|
+
robot.mouseRelease(InputEvent::BUTTON1_MASK)
|
52
|
+
end
|
53
|
+
|
54
|
+
def feedback_text_area(start_text = '')
|
55
|
+
@feedback_text_area ||= JTextArea.new.tap do |text_area|
|
56
|
+
text_area.setEditable(false)
|
57
|
+
text_area.setText(start_text)
|
58
|
+
text_area.setBackground(Color::WHITE)
|
59
|
+
text_area.setForeground(Color::BLACK)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def feedback_scroll_panel(text_area, dimension)
|
64
|
+
JScrollPane.new(text_area).tap do |scroll_panel|
|
65
|
+
scroll_panel.setPreferredSize(dimension)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def display_info(text)
|
70
|
+
feedback_text_area.append("\n#{text}")
|
71
|
+
feedback_text_area.setCaretPosition(feedback_text_area.getLineStartOffset(feedback_text_area.getLineCount() - 1))
|
72
|
+
end
|
73
|
+
|
74
|
+
def load_image(path)
|
75
|
+
buffer_array = ByteArrayInputStream.new(File.read(path).to_java_bytes)
|
76
|
+
image_input_stream = ImageIO.createImageInputStream(buffer_array)
|
77
|
+
ImageIO.read(image_input_stream)
|
78
|
+
end
|
79
|
+
|
80
|
+
def operative_system
|
81
|
+
java.lang.System.getProperty("os.name").downcase
|
82
|
+
end
|
83
|
+
|
84
|
+
def run
|
85
|
+
setTitle(respond_to?(:default_title) ? default_title : 'Shortcut App')
|
86
|
+
|
87
|
+
always_on_top if respond_to?(:always_on_top)
|
88
|
+
GlobalScreen.getInstance.addNativeKeyListener(self)
|
89
|
+
addWindowListener(self)
|
90
|
+
setVisible true
|
91
|
+
end
|
92
|
+
|
93
|
+
class << self
|
94
|
+
def title(text)
|
95
|
+
send :define_method, :default_title do
|
96
|
+
text
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def always_on_top(bool_value)
|
101
|
+
send :define_method, :always_on_top do
|
102
|
+
setAlwaysOnTop(bool_value)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def key_pressed(code, action = nil, &block)
|
107
|
+
send :define_method, "key_#{code}_pressed".to_sym do
|
108
|
+
if action
|
109
|
+
method_name = "action_#{action}_handler".to_sym
|
110
|
+
raise UndefinedActionError.new("undefined action `#{action}'") unless respond_to?(method_name)
|
111
|
+
self.send(method_name)
|
112
|
+
else
|
113
|
+
self.instance_eval(&block)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def action(name, &block)
|
119
|
+
send :define_method, "action_#{name}_handler".to_sym do
|
120
|
+
self.instance_eval(&block)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
[:windowClosing, :windowIconified,
|
126
|
+
:windowDeiconified, :windowActivated, :windowDeactivated].each do |name|
|
127
|
+
define_method(name) { |e| }
|
128
|
+
end
|
129
|
+
|
130
|
+
def windowOpened(e)
|
131
|
+
requestFocusInWindow
|
132
|
+
GlobalScreen.registerNativeHook
|
133
|
+
rescue NativeHookException => e
|
134
|
+
display_info(e.toString)
|
135
|
+
end
|
136
|
+
|
137
|
+
def windowClosed(e)
|
138
|
+
GlobalScreen.unregisterNativeHook
|
139
|
+
java.lang.System.runFinalization
|
140
|
+
java.lang.System.exit(0)
|
141
|
+
end
|
142
|
+
|
143
|
+
[:nativeKeyReleased, :nativeKeyTyped].each do |name|
|
144
|
+
define_method(name) { |e| }
|
145
|
+
end
|
146
|
+
|
147
|
+
def nativeKeyPressed(event)
|
148
|
+
method_name = "key_#{event.getKeyCode}_pressed".to_sym
|
149
|
+
send(method_name) if respond_to?(method_name)
|
150
|
+
end
|
151
|
+
|
152
|
+
def actionPerformed(event)
|
153
|
+
method_name = "action_#{event.getActionCommand}_handler".to_sym
|
154
|
+
send(method_name) if respond_to?(method_name)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/lib/shortcut.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.dirname(__FILE__) + '/shortcut/*.rb'].each { |file| require file }
|
data/shortcut.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'shortcut/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "shortcut"
|
8
|
+
spec.version = Shortcut::VERSION
|
9
|
+
spec.authors = ["Décio Ferreira"]
|
10
|
+
spec.email = ["decioferreira@decioferreira.com"]
|
11
|
+
spec.description = %q{Make your own Bot}
|
12
|
+
spec.summary = %q{Make your own Bot}
|
13
|
+
spec.homepage = "https://github.com/decioferreira/shortcut"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_development_dependency "warbler"
|
25
|
+
spec.add_development_dependency "guard-rspec"
|
26
|
+
end
|
@@ -0,0 +1,395 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "shortcut/errors"
|
3
|
+
require "shortcut/window"
|
4
|
+
require "ostruct"
|
5
|
+
|
6
|
+
import java.awt.Dimension
|
7
|
+
import java.awt.event.KeyEvent
|
8
|
+
|
9
|
+
# https://github.com/jruby/jruby/wiki/Persistence
|
10
|
+
Dimension.__persistent__ = true
|
11
|
+
|
12
|
+
# Title property
|
13
|
+
class TitleClass < Shortcut::Window; title 'Test title'; end
|
14
|
+
|
15
|
+
# AlwaysOnTop property
|
16
|
+
class AlwaysOnTopTrueClass < Shortcut::Window; always_on_top true; end
|
17
|
+
class AlwaysOnTopFalseClass < Shortcut::Window; always_on_top false; end
|
18
|
+
class AlwaysOnTopNoneClass < Shortcut::Window; end
|
19
|
+
|
20
|
+
# NativeKeyListener
|
21
|
+
class ListenerExampleClass < Shortcut::Window
|
22
|
+
attr_accessor :called_actions, :key_pressed_events
|
23
|
+
|
24
|
+
action 'some_action' do
|
25
|
+
self.called_actions ||= []
|
26
|
+
self.called_actions << 'some_action'
|
27
|
+
end
|
28
|
+
|
29
|
+
action 'action_q_key' do
|
30
|
+
self.called_actions ||= []
|
31
|
+
self.called_actions << 'action_q_key'
|
32
|
+
end
|
33
|
+
|
34
|
+
key_pressed KeyEvent::VK_Q, :action_q_key
|
35
|
+
key_pressed KeyEvent::VK_W, :undefined_action
|
36
|
+
|
37
|
+
key_pressed KeyEvent::VK_ESCAPE do
|
38
|
+
self.key_pressed_events ||= []
|
39
|
+
self.key_pressed_events << KeyEvent::VK_ESCAPE
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe Shortcut::Window do
|
44
|
+
let(:window) { Shortcut::Window.new }
|
45
|
+
|
46
|
+
it 'inherits from JFrame' do
|
47
|
+
window.should be_a(JFrame)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'includes NativeKeyListener' do
|
51
|
+
window.should be_a(NativeKeyListener)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'includes WindowListener' do
|
55
|
+
window.should be_a(WindowListener)
|
56
|
+
end
|
57
|
+
|
58
|
+
describe :create_button do
|
59
|
+
let(:jbutton) { double('jbutton').as_null_object }
|
60
|
+
|
61
|
+
it 'returns an instance of JButton' do
|
62
|
+
window.create_button('text', 'action').should be_a(JButton)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'creates a button with text' do
|
66
|
+
JButton.should_receive(:new).with('text').and_return(jbutton)
|
67
|
+
window.create_button('text', anything())
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'creates a button with action' do
|
71
|
+
JButton.should_receive(:new).and_return(jbutton)
|
72
|
+
jbutton.should_receive(:setActionCommand).with('action')
|
73
|
+
window.create_button(anything(), 'action')
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'returns the created button' do
|
77
|
+
JButton.should_receive(:new).and_return(jbutton)
|
78
|
+
window.create_button(anything(), anything()).should eq(jbutton)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'enables the button by default' do
|
82
|
+
JButton.should_receive(:new).and_return(jbutton)
|
83
|
+
jbutton.should_receive(:setEnabled).with(true)
|
84
|
+
window.create_button(anything(), anything())
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'accepts an enabled option to disable the button' do
|
88
|
+
JButton.should_receive(:new).and_return(jbutton)
|
89
|
+
jbutton.should_receive(:setEnabled).with(false)
|
90
|
+
window.create_button(anything(), anything(), {enabled: false})
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'adds self as action listener' do
|
94
|
+
JButton.should_receive(:new).and_return(jbutton)
|
95
|
+
jbutton.should_receive(:addActionListener).with(window)
|
96
|
+
window.create_button(anything(), anything())
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe :robot do
|
101
|
+
let(:robot) { double('robot') }
|
102
|
+
|
103
|
+
it 'returns an instance of Robot' do
|
104
|
+
window.robot.should be_a(Robot)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'creates and returns a Robot' do
|
108
|
+
Robot.should_receive(:new).and_return(robot)
|
109
|
+
window.robot.should eq(robot)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'caches result' do
|
113
|
+
Robot.should_receive(:new).once.and_return(robot)
|
114
|
+
window.robot.should eq(robot)
|
115
|
+
window.robot.should eq(robot)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe :click do
|
120
|
+
let(:robot) { double('robot') }
|
121
|
+
|
122
|
+
before(:each) do
|
123
|
+
window.stub(:robot) { robot }
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'moves mouse to correct position, presses and releases left mouse button, all in the correct order' do
|
127
|
+
robot.should_receive(:mouseMove).with(1, 2).ordered
|
128
|
+
robot.should_receive(:mousePress).with(InputEvent::BUTTON1_MASK).ordered
|
129
|
+
robot.should_receive(:mouseRelease).with(InputEvent::BUTTON1_MASK).ordered
|
130
|
+
window.click(1, 2)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe :feedback_text_area do
|
135
|
+
let(:text_area) { double('text area').as_null_object }
|
136
|
+
|
137
|
+
it 'returns an instance of JTextArea' do
|
138
|
+
window.feedback_text_area.should be_a(JTextArea)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'sets the text' do
|
142
|
+
JTextArea.should_receive(:new).and_return(text_area)
|
143
|
+
text_area.should_receive(:setText).with('Text')
|
144
|
+
window.feedback_text_area('Text')
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'disables the returned area' do
|
148
|
+
JTextArea.should_receive(:new).and_return(text_area)
|
149
|
+
text_area.should_receive(:setEditable).with(false)
|
150
|
+
window.feedback_text_area
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'sets the background white' do
|
154
|
+
JTextArea.should_receive(:new).and_return(text_area)
|
155
|
+
text_area.should_receive(:setBackground).with(Color::WHITE)
|
156
|
+
window.feedback_text_area
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'sets the foreground black' do
|
160
|
+
JTextArea.should_receive(:new).and_return(text_area)
|
161
|
+
text_area.should_receive(:setForeground).with(Color::BLACK)
|
162
|
+
window.feedback_text_area
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'caches result' do
|
166
|
+
JTextArea.should_receive(:new).once.and_return(text_area)
|
167
|
+
window.feedback_text_area.should eq(text_area)
|
168
|
+
window.feedback_text_area.should eq(text_area)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe :feedback_scroll_panel do
|
173
|
+
let(:text_area) { JTextArea.new }
|
174
|
+
let(:dimension) { Dimension.new }
|
175
|
+
|
176
|
+
it 'returns an instance of JScrollPane' do
|
177
|
+
window.feedback_scroll_panel(text_area, dimension).should be_a(JScrollPane)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'creates the scroll panel with the text_area argument' do
|
181
|
+
scroll_panel = JScrollPane.new
|
182
|
+
JScrollPane.should_receive(:new).with(text_area).and_return(scroll_panel)
|
183
|
+
window.feedback_scroll_panel(text_area, dimension)
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'sets preferred size' do
|
187
|
+
scroll_panel = JScrollPane.new
|
188
|
+
JScrollPane.should_receive(:new).with(text_area).and_return(scroll_panel)
|
189
|
+
scroll_panel.should_receive(:setPreferredSize).with(dimension)
|
190
|
+
window.feedback_scroll_panel(text_area, dimension)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe :display_info do
|
195
|
+
it 'append message to feedback text area' do
|
196
|
+
window.display_info('A')
|
197
|
+
window.display_info('B')
|
198
|
+
window.feedback_text_area.getText.should eq("\nA\nB")
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'scrolls down' do
|
202
|
+
window.display_info('A')
|
203
|
+
window.feedback_text_area.getCaretPosition.should eq(1)
|
204
|
+
window.display_info('B')
|
205
|
+
window.feedback_text_area.getCaretPosition.should eq(3)
|
206
|
+
window.display_info('C')
|
207
|
+
window.feedback_text_area.getCaretPosition.should eq(5)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe :load_image do
|
212
|
+
let(:reader) { double('reader').as_null_object }
|
213
|
+
let(:java_bytes_reader) { double('java bytes reader') }
|
214
|
+
let(:input_stream) { double('input stream') }
|
215
|
+
let(:image_input_stream) { double('image input stream') }
|
216
|
+
let(:image) { double('image') }
|
217
|
+
|
218
|
+
it 'loads an image' do
|
219
|
+
File.should_receive(:read).with('path').and_return(reader)
|
220
|
+
reader.should_receive(:to_java_bytes).and_return(java_bytes_reader)
|
221
|
+
ByteArrayInputStream.should_receive(:new).with(java_bytes_reader).and_return(input_stream)
|
222
|
+
ImageIO.should_receive(:createImageInputStream).with(input_stream).and_return(image_input_stream)
|
223
|
+
ImageIO.should_receive(:read).with(image_input_stream).and_return(image)
|
224
|
+
|
225
|
+
window.load_image('path').should eq(image)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe :operative_system do
|
230
|
+
it 'returns lowercased os.name property' do
|
231
|
+
java.lang.System.should_receive(:getProperty).with("os.name").and_return('Linux')
|
232
|
+
window.operative_system.should eq('linux')
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe :run do
|
237
|
+
before(:each) do
|
238
|
+
Shortcut::Window.any_instance.stub(:setVisible)
|
239
|
+
end
|
240
|
+
|
241
|
+
describe :title do
|
242
|
+
it 'sets the JFrame title' do
|
243
|
+
title = TitleClass.new
|
244
|
+
title.run
|
245
|
+
title.getTitle.should eq('Test title')
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'sets a JFrame default title of "Shortcut App"' do
|
249
|
+
window.run
|
250
|
+
window.getTitle.should eq('Shortcut App')
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
describe 'always on top property' do
|
255
|
+
it 'sets property to true' do
|
256
|
+
on_top_true = AlwaysOnTopTrueClass.new
|
257
|
+
on_top_true.should_receive(:setAlwaysOnTop).with(true)
|
258
|
+
on_top_true.run
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'sets property to false' do
|
262
|
+
on_top_false = AlwaysOnTopFalseClass.new
|
263
|
+
on_top_false.should_receive(:setAlwaysOnTop).with(false)
|
264
|
+
on_top_false.run
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'does not set property' do
|
268
|
+
on_top_none = AlwaysOnTopNoneClass.new
|
269
|
+
on_top_none.should_not_receive(:setAlwaysOnTop)
|
270
|
+
on_top_none.run
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'adds a window listener' do
|
275
|
+
window.should_receive(:addWindowListener).with(window)
|
276
|
+
window.run
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'adds a native key listener' do
|
280
|
+
GlobalScreen.getInstance.should_receive(:addNativeKeyListener).with(window)
|
281
|
+
window.run
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'sets visibility to true' do
|
285
|
+
window.should_receive(:setVisible).with(true)
|
286
|
+
window.run
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe 'window listeners' do
|
291
|
+
[:windowOpened, :windowClosed, :windowClosing, :windowIconified,
|
292
|
+
:windowDeiconified, :windowActivated, :windowDeactivated].each do |event|
|
293
|
+
it { window.should respond_to(event).with(1).argument }
|
294
|
+
end
|
295
|
+
|
296
|
+
describe :windowOpened do
|
297
|
+
it 'requests focus in window' do
|
298
|
+
window.should_receive(:requestFocusInWindow)
|
299
|
+
window.windowOpened(anything())
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'registers native hook' do
|
303
|
+
GlobalScreen.should_receive(:registerNativeHook)
|
304
|
+
window.windowOpened(anything())
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'handles exception when registering native hook' do
|
308
|
+
GlobalScreen.should_receive(:registerNativeHook).and_raise(NativeHookException.new('error message!'))
|
309
|
+
window.should_receive(:display_info).with('org.jnativehook.NativeHookException: error message!')
|
310
|
+
window.windowOpened(anything())
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe :windowClosed do
|
315
|
+
before(:each) do
|
316
|
+
java.lang.System.stub(:exit)
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'unregisters native hook' do
|
320
|
+
GlobalScreen.should_receive(:unregisterNativeHook)
|
321
|
+
window.windowClosed(anything())
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'runs system finalization' do
|
325
|
+
java.lang.System.should_receive(:runFinalization)
|
326
|
+
window.windowClosed(anything())
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'exit system with value 0' do
|
330
|
+
java.lang.System.should_receive(:exit).with(0)
|
331
|
+
window.windowClosed(anything())
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
describe 'native key listeners' do
|
337
|
+
[:nativeKeyReleased, :nativeKeyTyped, :nativeKeyPressed].each do |event|
|
338
|
+
it { window.should respond_to(event).with(1).argument }
|
339
|
+
end
|
340
|
+
|
341
|
+
describe :nativeKeyPressed do
|
342
|
+
it 'executes block from key_pressed function' do
|
343
|
+
key_event = OpenStruct.new(getKeyCode: KeyEvent::VK_ESCAPE)
|
344
|
+
|
345
|
+
listener_example = ListenerExampleClass.new
|
346
|
+
listener_example.nativeKeyPressed(key_event)
|
347
|
+
|
348
|
+
listener_example.key_pressed_events.should eq([KeyEvent::VK_ESCAPE])
|
349
|
+
end
|
350
|
+
|
351
|
+
it 'does not raise exception when handler not defined' do
|
352
|
+
key_event = OpenStruct.new(getKeyCode: KeyEvent::VK_0)
|
353
|
+
|
354
|
+
listener_example = ListenerExampleClass.new
|
355
|
+
expect { listener_example.nativeKeyPressed(key_event) }.not_to raise_exception
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'works with an action' do
|
359
|
+
key_event = OpenStruct.new(getKeyCode: KeyEvent::VK_Q)
|
360
|
+
|
361
|
+
listener_example = ListenerExampleClass.new
|
362
|
+
listener_example.nativeKeyPressed(key_event)
|
363
|
+
|
364
|
+
listener_example.called_actions.should eq(['action_q_key'])
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'works with an action' do
|
368
|
+
key_event = OpenStruct.new(getKeyCode: KeyEvent::VK_W)
|
369
|
+
|
370
|
+
listener_example = ListenerExampleClass.new
|
371
|
+
expect { listener_example.nativeKeyPressed(key_event) }.to raise_exception(Shortcut::UndefinedActionError, "undefined action `undefined_action'")
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
describe :actionPerformed do
|
377
|
+
it { window.should respond_to(:actionPerformed).with(1).argument }
|
378
|
+
|
379
|
+
it 'executes block from action function' do
|
380
|
+
action_event = OpenStruct.new(getActionCommand: 'some_action')
|
381
|
+
|
382
|
+
listener_example = ListenerExampleClass.new
|
383
|
+
listener_example.actionPerformed(action_event)
|
384
|
+
|
385
|
+
listener_example.called_actions.should eq(['some_action'])
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'does not raise exception when handler not defined' do
|
389
|
+
action_event = OpenStruct.new(getActionCommand: 'undefined_action')
|
390
|
+
|
391
|
+
listener_example = ListenerExampleClass.new
|
392
|
+
expect { listener_example.actionPerformed(action_event) }.not_to raise_exception
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shortcut
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Décio Ferreira
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.3'
|
21
|
+
none: false
|
22
|
+
requirement: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
none: false
|
28
|
+
prerelease: false
|
29
|
+
type: :development
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
version_requirements: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: !binary |-
|
37
|
+
MA==
|
38
|
+
none: false
|
39
|
+
requirement: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: !binary |-
|
44
|
+
MA==
|
45
|
+
none: false
|
46
|
+
prerelease: false
|
47
|
+
type: :development
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: warbler
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: !binary |-
|
55
|
+
MA==
|
56
|
+
none: false
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: !binary |-
|
62
|
+
MA==
|
63
|
+
none: false
|
64
|
+
prerelease: false
|
65
|
+
type: :development
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: guard-rspec
|
68
|
+
version_requirements: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: !binary |-
|
73
|
+
MA==
|
74
|
+
none: false
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: !binary |-
|
80
|
+
MA==
|
81
|
+
none: false
|
82
|
+
prerelease: false
|
83
|
+
type: :development
|
84
|
+
description: Make your own Bot
|
85
|
+
email:
|
86
|
+
- decioferreira@decioferreira.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- ".ruby-version"
|
94
|
+
- Gemfile
|
95
|
+
- Guardfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- lib/jar/JNativeHook.jar
|
100
|
+
- lib/shortcut.rb
|
101
|
+
- lib/shortcut/errors.rb
|
102
|
+
- lib/shortcut/point.rb
|
103
|
+
- lib/shortcut/screen.rb
|
104
|
+
- lib/shortcut/version.rb
|
105
|
+
- lib/shortcut/window.rb
|
106
|
+
- shortcut.gemspec
|
107
|
+
- spec/lib/shortcut_point_spec.rb
|
108
|
+
- spec/lib/shortcut_window_spec.rb
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
homepage: https://github.com/decioferreira/shortcut
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
segments:
|
122
|
+
- 0
|
123
|
+
version: !binary |-
|
124
|
+
MA==
|
125
|
+
hash: 2
|
126
|
+
none: false
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
segments:
|
132
|
+
- 0
|
133
|
+
version: !binary |-
|
134
|
+
MA==
|
135
|
+
hash: 2
|
136
|
+
none: false
|
137
|
+
requirements: []
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 1.8.24
|
140
|
+
signing_key:
|
141
|
+
specification_version: 3
|
142
|
+
summary: Make your own Bot
|
143
|
+
test_files:
|
144
|
+
- spec/lib/shortcut_point_spec.rb
|
145
|
+
- spec/lib/shortcut_window_spec.rb
|
146
|
+
- spec/spec_helper.rb
|