simple_gui_creator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,70 @@
1
+ This gem is meant to make GUI development in Ruby easy, and even fun.
2
+
3
+ You can specify "easy window layout" in ASCII text, example, if you have a layout like this:
4
+
5
+ ---------- A Title ---------------------------
6
+ | [a button:button1] [button text:button2] |
7
+ | "some text2:text1" |
8
+ ----------------------------------------------
9
+
10
+ It will create a window that has buttons and text "like that," with reasonable spacing.
11
+
12
+ Here's how:
13
+ >> frame = ParseTemplate::JFramer.new # or optionally subclass this instead
14
+ >> frame.parse_setup_filename 'some_filename'
15
+
16
+ You can program behavior, like this:
17
+
18
+ >> frame.elements['button1'].on_clicked {
19
+ SimpleGui.show_blocking_message_dialog "you clicked button1!"
20
+ }
21
+
22
+ This has the effect of separating your view from your controller, in this case, because you can store the layouts in
23
+ an entirely separate file, or embedded in the code. "Normal humans" can edit the design layout files, for instance,
24
+ and the separation allows for easy peace of mind.
25
+
26
+ It also has helper methods for common GUI tasks, like the above show_blocking_message_dialog:
27
+
28
+ SimpleGui.new_nonexisting_filechooser_and_go # select file or filename for a "new file" (not yet existing)
29
+ SimpleGui.new_existing_dir_chooser_and_go # select pre-existing directory
30
+ SimpleGui.show_in_explorer(filename) # reveals file in Explorer for windows, Finder for OS X
31
+ text_from_user = SimpleGui.get_user_input "Input your name:" # these raise an exception if the user cancels the dialog,
32
+
33
+ A select-button prompt dialog:
34
+
35
+ if(SimpleGui.show_select_buttons_prompt("message title", :yes => 'text for the yes button', :no => 'text for the no button', :cancel => 'text for the cancel button') == :yes)
36
+ # they chose the "yes" equivalent button...
37
+ end
38
+
39
+ etc. ...
40
+
41
+ It provies a few helper methods to the ParseTemplate::JFramer (and JFrame) class, like:
42
+
43
+ #bring_to_front
44
+ #minimize
45
+ #restore
46
+ #after_closed { ... }
47
+ #after_minimized { ... }
48
+
49
+ It has helpers to control/playback audio, like mp3's or wave's, starting/stopping asynchronously (see the files in the 'lib/ruby-easy-gui-creator' directory.
50
+ It has helpers to set/get system clipboard contents.
51
+ It has helpers to control/query the mouse (I use this, but don't know why anybody else ever would want to LOL).
52
+ It has helpers to query the current system for its DVD drives, be notified when disks are inserted/changed, etc.
53
+
54
+ Feedback/feature requests welcome.
55
+ http://groups.google.com/group/roger-projects, roger-projects@googlegroups.com
56
+ I'd even be happy to wrap other gui frameworks (currently jruby/swing only) with the same helper functions, if anybody wanted it.
57
+
58
+ Enjoy!
59
+
60
+ To install/use:
61
+
62
+ $ gem install simple-ruby-gui-creator
63
+
64
+ >> require 'simple_gui'
65
+ >> ... [see above]
66
+
67
+ == Known problems ==
68
+
69
+ Only allows single element types per line currently, like "all text" or "all buttons", but I'm working on it :)
70
+ Only Jruby today, which can make loading time painful (hint: use splash screen).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'sane'
2
+ require 'jeweler2'
3
+ Jeweler::Tasks.new do |gem|
4
+ gem.name = "simple_gui_creator"
5
+ gem.summary = %Q{Framework to ease in creation of ruby GUI apps. Makes designing windows a snap.}
6
+ gem.description = gem.summary # %Q{TODO: longer description of your gem}
7
+ gem.email = "rogerdpack@gmail.com"
8
+ gem.homepage = "http://github.com/rdp/simple-ruby-gui-creator"
9
+ gem.authors = ["rogerdpack"]
10
+ gem.add_dependency 'sane'
11
+ end
12
+
13
+ Jeweler::RubygemsDotOrgTasks.new
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ on_minimized fix
2
+
3
+ ask_should_allow_on_minimize {
4
+ }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+ # assumes has rubygems loaded...since we need the sane gem
3
+ require 'os'
4
+ if !OS.jruby?
5
+ $stderr.puts 'jruby only for now, you can request a change to this, exiting...'
6
+ exit 1
7
+ end
8
+ require File.dirname(__FILE__) + "/../lib/simple_gui_creator.rb"
9
+
10
+ class TestWindow < ParseTemplate::JFramer
11
+
12
+ def initialize
13
+ super
14
+ string = <<-EOL
15
+ ---------- Simple Ruby Gui Creator Test Window ---------------------------
16
+ | "Edit this, then..." |
17
+ | [Test it out! :create_button] |
18
+ | [ :text_area_to_use, width=70chars, height=500, font=fixed_width] |
19
+ | [ ] |
20
+ | [ ] |
21
+ | [ ] |
22
+ | [ ] |
23
+ | [ ] |
24
+ | [ ] |
25
+ | [ ] |
26
+ | |
27
+ | [Show code snippet :create_snippet] |
28
+ --------------------------------------------------------------------------
29
+ EOL
30
+ Kernel.print string
31
+ parse_setup_string string
32
+ elements[:text_area_to_use].text=string
33
+ elements[:create_button].on_clicked {
34
+ ParseTemplate::JFramer.new.parse_setup_string elements[:text_area_to_use].text
35
+ }
36
+ elements[:create_snippet].on_clicked {
37
+ frame = ParseTemplate::JFramer.new.parse_setup_string elements[:text_area_to_use].text
38
+ frame.close
39
+
40
+ element_code = ""
41
+ frame.elements.each{|e|
42
+ if e[1].is_a? Java::JavaxSwing::JButton
43
+ element_code += " elements[:#{e[0]}].on_clicked { puts 'clicked #{e[0]}' }\n"
44
+ end
45
+ }
46
+
47
+ code=<<-EOL
48
+
49
+ require 'simple-ruby-gui-creator.rb'
50
+
51
+ class MyWindow < ParseTemplate::JFramer
52
+ def initialize
53
+ super
54
+ parse_setup_filename 'my_window.template' # create this file first
55
+
56
+ #{element_code}
57
+ end
58
+
59
+ end
60
+ MyWindow.new
61
+ EOL
62
+ frame2 = ParseTemplate::JFramer.new.parse_setup_string <<-EOL
63
+ "Here's your snippet!"
64
+ [:code,width=500,height=400]
65
+ [ ]
66
+ [ ]
67
+ [Save Code Snippet:snippet_save]
68
+ "Here's your template:"
69
+ [:template_text,width=500,height=400,font=fixed_width]
70
+ [ ]
71
+ [Save Template:template_save]
72
+ EOL
73
+ frame2.elements[:code].text = code
74
+ frame2.elements[:snippet_save].on_clicked { save_file 'simple-gui-demo.rb', code}
75
+ template = elements[:text_area_to_use].text
76
+ frame2.elements[:template_text].text = template
77
+ frame2.elements[:template_save].on_clicked { save_file 'template.simple-gui-demo', template}
78
+ }
79
+ end
80
+
81
+ def save_file default_filename, text
82
+ location = SimpleGuiCreator.new_nonexisting_or_existing_filechooser_and_go "Choose where to save:", nil, default_filename
83
+ File.write(location, text)
84
+ end
85
+
86
+ end
87
+
88
+ TestWindow.new
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'sane'
3
+ require __DIR__ + '/../lib/simple-ruby-gui-creator.rb'
4
+
5
+ a = ParseTemplate::JFramer.new
6
+ a.parse_setup_string <<EOL
7
+
8
+ | [a button:button_name,width=100,height=100,abs_x=50,abs_y=50] [a third button]
9
+ | [another button:button_name2] [another button:button_name4]|
10
+ | [another button:button_name3] |
11
+
12
+ EOL
data/ext/jl1.0.1.jar ADDED
Binary file
@@ -0,0 +1,162 @@
1
+ =begin
2
+ Copyright 2010, Roger Pack
3
+ This file is part of Sensible Cinema.
4
+
5
+ Sensible Cinema is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ Sensible Cinema is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with Sensible Cinema. If not, see <http://www.gnu.org/licenses/>.
17
+ =end
18
+
19
+ require 'os'
20
+ require 'ostruct'
21
+ require 'thread'
22
+
23
+ class DriveInfo
24
+
25
+ def self.md5sum_disk(dir)
26
+ if OS.mac?
27
+ exe = "#{__DIR__}/../../vendor/mac_dvdid/bin/dvdid"
28
+ else
29
+ exe = "#{__DIR__}/../../vendor/dvdid.exe"
30
+ end
31
+ raise exe + '--exe doesnt exist?' unless File.exist? exe
32
+ command = "#{exe} \"#{dir}\""
33
+ output = `#{command}` # can take like 2.2s to spin up the disk...
34
+ raise 'dvdid command failed?' + command unless $?.exitstatus == 0
35
+ output.strip
36
+ end
37
+
38
+ @@drive_cache = nil
39
+ @@drive_cache_mutex = Mutex.new
40
+ @@drive_changed_notifies = []
41
+ def self.create_looping_drive_cacher
42
+ # has to be in its own thread or wmi will choke...
43
+ looped_at_least_once = false
44
+ if @caching_thread
45
+ looped_at_least_once = true
46
+ else
47
+ @caching_thread = Thread.new {
48
+ # use a dir glob to avoid having to use wmi too frequently (or accessing disks too often, which we might still be doing accidentally anyway)
49
+ if OS.doze?
50
+ old_drive_glob = '{' + DriveInfo.get_dvd_drives_even_if_no_disc_present.map{|dr| dr.MountPoint[0..0]}.join(',') + '}:/.' # must be in the thread for wmi
51
+ else
52
+ old_drive_glob = '/Volumes/*'
53
+ end
54
+ previously_known_about_discs = nil
55
+ loop {
56
+ should_update = false
57
+ @@drive_cache_mutex.synchronize { # in case the first update takes too long, basically, so they miss it LODO still a tiny race condition in here...might be useless...
58
+ looped_at_least_once = true # let the spawning waiting thread exit quickly
59
+ cur_disks = Dir[old_drive_glob]
60
+ if cur_disks != previously_known_about_discs
61
+ p 'updating disk lists...'
62
+ should_update = true
63
+ @@drive_cache = get_all_drives_as_ostructs_internal
64
+ previously_known_about_discs = cur_disks
65
+ end
66
+ }
67
+ notify_all_drive_blocks_that_change_has_occured if should_update
68
+ sleep 0.5
69
+ }
70
+ }
71
+ end
72
+ # maintain some thread startup sanity :P
73
+ while(!looped_at_least_once)
74
+ sleep 0.01
75
+ end
76
+ true
77
+ end
78
+
79
+ @@updating_mutex = Mutex.new # theoretically 2 threads could call this at once...so synchronize it...
80
+ def self.notify_all_drive_blocks_that_change_has_occured
81
+ @@updating_mutex.synchronize {
82
+ @@drive_changed_notifies.each{|block| block.call}
83
+ }
84
+ end
85
+
86
+ def self.add_notify_on_changed_disks &block
87
+ raise unless block
88
+ @@drive_changed_notifies << block
89
+ should_call = false
90
+ @@drive_cache_mutex.synchronize { should_call = true if @@drive_cache }
91
+ block.call if should_call # should be called at least once, right, on init?
92
+ true
93
+ end
94
+
95
+ def self.get_dvd_drives_as_openstruct
96
+ disks = get_all_drives_as_ostructs
97
+ disks.select{|d| d.Description =~ /CD-ROM/ && File.exist?(d.Name + "/VIDEO_TS")}
98
+ end
99
+
100
+
101
+ def self.get_drive_with_most_space_with_slash
102
+ disks = get_all_drives_as_ostructs
103
+ most_space = disks.sort_by{|d| d.FreeSpace}[-1]
104
+ most_space.MountPoint + "/"
105
+ end
106
+
107
+ def self.get_all_drives_as_ostructs # gets all drives not just DVD drives...
108
+ @@drive_cache_mutex.synchronize {
109
+ if @caching_thread
110
+ @@drive_cache
111
+ else
112
+ get_all_drives_as_ostructs_internal # first time through for the startup thread goes here too
113
+ end
114
+ }
115
+ end
116
+
117
+ private
118
+
119
+ def self.get_dvd_drives_even_if_no_disc_present # private since it uses internal
120
+ raise unless OS.doze? # no idea how to do this in mac :P
121
+ disks = get_all_drives_as_ostructs_internal
122
+ disks.select{|d| d.Description =~ /CD-ROM/}
123
+ end
124
+
125
+ # DevicePoint is like "where to point mplayer at this succer"
126
+ def self.get_all_drives_as_ostructs_internal
127
+ if OS.mac?
128
+ require 'plist'
129
+ Dir['/Volumes/*'].map{|dir|
130
+ parsed = Plist.parse_xml(`diskutil info -plist "#{dir}"`)
131
+ d2 = OpenStruct.new
132
+ d2.VolumeName = parsed["VolumeName"]
133
+ d2.Name = dir # DevNode?
134
+ d2.FreeSpace = parsed["FreeSpace"].to_i
135
+ d2.Description = parsed['OpticalDeviceType']
136
+ d2.MountPoint = parsed['MountPoint']
137
+ if d2.MountPoint == '/'
138
+ # try to guess a more writable default location...this works I guess?
139
+ d2.MountPoint = File.expand_path '~'
140
+ end
141
+ d2.DevicePoint = parsed['DeviceNode'].sub('disk', 'rdisk') # I've heard using rdisk is better/faster...
142
+ d2
143
+ }
144
+ else
145
+ require 'ruby-wmi'
146
+ disks = WMI::Win32_LogicalDisk.find(:all)
147
+ disks.map{|d| d2 = OpenStruct.new
148
+ d2.Description = d.Description
149
+ d2.VolumeName = d.VolumeName
150
+ d2.Name = d.Name
151
+ d2.FreeSpace = d.FreeSpace.to_i
152
+ d2.MountPoint = d.Name[0..2] # like f:\
153
+ d2.DevicePoint = d2.MountPoint
154
+ d2
155
+ }
156
+ end
157
+ end
158
+ end
159
+
160
+ if $0 == __FILE__
161
+ p DriveInfo.get_dvd_drives_as_openstruct
162
+ end
@@ -0,0 +1,149 @@
1
+ =begin
2
+ Copyright 2010, Roger Pack
3
+ This file is part of Sensible Cinema.
4
+
5
+ Sensible Cinema is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ Sensible Cinema is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with Sensible Cinema. If not, see <http://www.gnu.org/licenses/>.
17
+ =end
18
+ require 'rubygems'
19
+ require 'ffi'
20
+ require 'java'
21
+
22
+ module MouseControl
23
+ extend FFI::Library
24
+ MouseInfo = java.awt.MouseInfo
25
+
26
+ ffi_lib 'user32'
27
+ ffi_convention :stdcall
28
+
29
+ MOUSEEVENTF_MOVE = 1
30
+ INPUT_MOUSE = 0
31
+ MOUSEEVENTF_ABSOLUTE = 0x8000
32
+ MOUSEEVENTF_LEFTDOWN = 0x0002
33
+ MOUSEEVENTF_LEFTUP = 0x0004
34
+
35
+
36
+ class MouseInput < FFI::Struct
37
+ layout :dx, :long,
38
+ :dy, :long,
39
+ :mouse_data, :ulong,
40
+ :flags, :ulong,
41
+ :time, :ulong,
42
+ :extra, :ulong
43
+ end
44
+
45
+ class InputEvent < FFI::Union
46
+ layout :mi, MouseInput
47
+ end
48
+
49
+ class Input < FFI::Struct
50
+ layout :type, :ulong,
51
+ :evt, InputEvent
52
+ end
53
+
54
+ # UINT SendInput(UINT nInputs, LPINPUT pInputs, int cbSize);
55
+ attach_function :SendInput, [ :uint, :pointer, :int ], :uint
56
+
57
+ # poller...
58
+ attach_function :GetAsyncKeyState, [:int], :uint
59
+
60
+ class << self
61
+
62
+ def jitter_forever_in_own_thread
63
+
64
+ old_x, old_y = get_mouse_location
65
+ Thread.new {
66
+ loop {
67
+ move_y = 8 # just enough for VLC when full screened...
68
+ cur_x, cur_y = get_mouse_location
69
+ if(cur_x == old_x && cur_y == old_y)
70
+ @total_movements += 1
71
+ # blit it up
72
+ move_mouse_relative(0, move_y)
73
+ move_mouse_relative(0, move_y * -1)
74
+ # let it move it back
75
+ sleep 0.05
76
+ old_x, old_y = get_mouse_location
77
+ sleep 0.75
78
+ else
79
+ # user has been moving the mouse around, so we don't need to, to not annoy them
80
+ old_x, old_y = get_mouse_location
81
+ sleep 3
82
+ end
83
+ }
84
+ }
85
+
86
+ end
87
+
88
+ def move_mouse_relative dx, dy
89
+ myinput = MouseControl::Input.new
90
+ myinput[:type] = MouseControl::INPUT_MOUSE
91
+ in_evt = myinput[:evt][:mi]
92
+ in_evt[:mouse_data] = 0 # null it out
93
+ in_evt[:flags] = MouseControl::MOUSEEVENTF_MOVE
94
+ in_evt[:time] = 0
95
+ in_evt[:extra] = 0
96
+ in_evt[:dx] = dx
97
+ in_evt[:dy] = dy
98
+ SendInput(1, myinput, MouseControl::Input.size)
99
+ end
100
+
101
+ def single_click_left_mouse_button
102
+ left_mouse_down!
103
+ left_mouse_up!
104
+ p "CLICKED LEFT MOUSE BUTTON"
105
+ end
106
+
107
+ def left_mouse_down!
108
+ send_left_mouse_button MOUSEEVENTF_LEFTDOWN
109
+ end
110
+
111
+ def left_mouse_up!
112
+ send_left_mouse_button MOUSEEVENTF_LEFTUP
113
+ end
114
+
115
+ VK_LBUTTON = 0x01 # mouse left button for GetAsyncKeyState (seeing if mouse down currently or not)
116
+
117
+ def left_mouse_button_state
118
+ GetAsyncKeyState(VK_LBUTTON) # ignore a first response, which also tells us if it has changed at all since last call
119
+ if GetAsyncKeyState(VK_LBUTTON) == 0 # zero means up
120
+ :up
121
+ else
122
+ :down
123
+ end
124
+ end
125
+
126
+ # [x, y]
127
+ def get_mouse_location
128
+ loc = MouseInfo.getPointerInfo.getLocation # pure java!
129
+ [loc.x, loc.y]
130
+ end
131
+
132
+ attr_accessor :total_movements
133
+
134
+ private
135
+
136
+ def send_left_mouse_button action_type
137
+ myinput = MouseControl::Input.new
138
+ myinput[:type] = MouseControl::INPUT_MOUSE
139
+ in_evt = myinput[:evt][:mi]
140
+ in_evt[:flags] = action_type
141
+ SendInput(1, myinput, MouseControl::Input.size)
142
+ end
143
+
144
+
145
+ end
146
+
147
+ end
148
+
149
+ MouseControl.total_movements=0 # ruby is a bit freaky with these...