simple_gui_creator 0.1.0

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/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...