shenmegui 0.3.6 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 20a88724018ae39b5bc0d78f2ad4a309ddfee718
4
- data.tar.gz: cd76c8753e91659e878c535a4801e732982a3f18
3
+ metadata.gz: fd26f4f5ed85bdf4a68277307384e178cbcc8a98
4
+ data.tar.gz: ddc3ee9f6d6d8adea90049f8edb5eb919d88518b
5
5
  SHA512:
6
- metadata.gz: 585ace9236ccf147c24be691b7bcb99e74885b020e4ff3524ce3149e02b7f15e594ddb4bd8f145cf82e53032eda7b899f3d21d16edf787036fa5762c8e209d1f
7
- data.tar.gz: 6f657c4e06167509f3c94277aabd1de9768354f0d8fb47a6233bd0bba0741ed27c9cccac9f2f2a4c35e201082fb431b14fbea0a0c35618939bf37d82967332ac
6
+ metadata.gz: 970a1abb56193a2bd09a33ff0b9d7fb2cd03bfbe8d013b186783d4e586c9a40599ff8aaf41be8678aa7807952731d569794045eddb2b1ed589f5fd5b4459ef0f
7
+ data.tar.gz: cf06ffb82a85f09c917949cae8b1ab1612b33e71f61ddc60502f1a3397d43d9fe65c97cd1c8b8b9da53016d799a17bcbd3cba0ad41b5c4126d7e231e75ddf295
@@ -5,19 +5,7 @@ module ShenmeGUI
5
5
  class Base
6
6
  attr_accessor :id, :properties, :events, :children, :parent
7
7
 
8
- def add_hook(obj)
9
- case obj
10
- when String
11
- HookedString.new(obj, self)
12
- when Array
13
- HookedArray.new(obj, self)
14
- when Hash
15
- HookedHash.new(obj, self)
16
- else
17
- obj
18
- end
19
- end
20
-
8
+ # 读取时直接从@properties读取,写入时则调用update_properties这个统一的接口
21
9
  def self.property(*arr)
22
10
  arr.each do |x|
23
11
  define_method(x) do
@@ -39,26 +27,29 @@ module ShenmeGUI
39
27
 
40
28
  end
41
29
 
30
+ # 注意@default_properties都是未上钩子的属性,因为钩子本身需要控件的self,而此时控件还未实例化
42
31
  def self.default(params)
43
- @default_properties = params
32
+ @default_properties ||= {}
33
+ @default_properties.merge!(params)
44
34
  end
45
35
 
46
36
  def self.default_properties
47
37
  @default_properties
48
38
  end
49
39
 
40
+ # 注册事件及获取事件的lambda,事件lambda存储在events属性里,最后的self是为了支持链式调用
50
41
  available_events = %w{click input dblclick mouseover mouseout blur focus mousedown mouseup change select}.collect(&:to_sym)
51
42
  available_events.each do |x|
52
43
  define_method("on#{x}") do |&block|
53
- return events[x] if block.nil?
54
- events[x] = lambda &block
44
+ return @events[x] if block.nil?
45
+ @events[x] = lambda &block
55
46
  self
56
47
  end
57
48
  end
58
49
 
59
50
  def sync
60
51
  data = @properties
61
- validate @properties
52
+ before_sync
62
53
  msg = "sync:#{@id}->#{data.to_json}"
63
54
  ShenmeGUI.socket.send(msg)
64
55
  end
@@ -70,17 +61,19 @@ module ShenmeGUI
70
61
 
71
62
  def update_properties(data)
72
63
  data = Hash[data.keys.collect(&:to_sym).zip(data.values.collect{|x| add_hook(x)})]
73
- @properties.update(data)
64
+ @properties.merge!(data)
74
65
  end
75
66
 
76
67
  def sync_events
77
68
  data = @events.keys
69
+ #return if data.empty?
78
70
  msg = "add_event:#{@id}->#{data.to_json}"
79
71
  ShenmeGUI.socket.send(msg)
80
72
  end
81
73
 
82
74
  def initialize(params={})
83
- @properties = self.class.default_properties ? self.class.default_properties.dup : {}
75
+ @properties = {}
76
+ update_properties(self.class.default_properties.dup) if self.class.default_properties
84
77
  update_properties(params)
85
78
  @id = ShenmeGUI.elements.size
86
79
  ShenmeGUI.elements << self
@@ -97,11 +90,26 @@ module ShenmeGUI
97
90
  template.result(binding)
98
91
  end
99
92
 
100
- def validate(params)
93
+ def before_sync
101
94
  end
102
95
 
103
- property :width, :height, :font, :background, :margin, :border
96
+ property :width, :height, :font, :margin
104
97
 
98
+ private
99
+ #这里存在重复给某字符串加钩子的情况,真是有够难抓的BUG
100
+ def add_hook(obj)
101
+ #p obj.class
102
+ case obj
103
+ when String
104
+ HookedString.new(obj, self, :sync)
105
+ when Array
106
+ HookedArray.new(obj, self, :sync)
107
+ when Hash
108
+ HookedHash.new(obj, self, :sync)
109
+ else
110
+ obj
111
+ end
112
+ end
105
113
  end
106
114
 
107
115
  class Body < Base
@@ -129,11 +137,6 @@ module ShenmeGUI
129
137
  class Textline < Base
130
138
  property :text, :selection
131
139
  shortcut :text
132
-
133
- def text=(v)
134
- update_properties({text: v, selection: [v.size]*2 })
135
- sync
136
- end
137
140
  end
138
141
 
139
142
  class Textarea < Base
@@ -144,11 +147,6 @@ module ShenmeGUI
144
147
  def <<(t)
145
148
  text << "\n#{t}"
146
149
  end
147
-
148
- def text=(v)
149
- update_properties({text: v, selection: [v.size]*2 })
150
- sync
151
- end
152
150
  end
153
151
 
154
152
  class Stack < Base
@@ -172,9 +170,9 @@ module ShenmeGUI
172
170
  shortcut :percent
173
171
  default :width=>'100%'
174
172
 
175
- def validate(params)
176
- params[:percent] = 0 if params[:percent] < 0
177
- params[:percent] = 100 if params[:percent] > 100
173
+ def before_sync
174
+ @properties[:percent] = 0 if @properties[:percent] < 0
175
+ @properties[:percent] = 100 if @properties[:percent] > 100
178
176
  end
179
177
  end
180
178
 
@@ -194,20 +192,33 @@ module ShenmeGUI
194
192
  end
195
193
 
196
194
  class Table < Base
197
- property :data
195
+ #attr_accessor :row_names_enum
196
+ property :data, :max_column_width, :column_names, :row_names, :row_names_enum
198
197
  shortcut :data
199
198
  default :width=>'100%', :height=>'150px'
200
199
 
200
+ def before_sync
201
+ row_names_enum = @properties[:row_names_enum]
202
+ if row_names_enum
203
+ @properties[:row_names] = []
204
+ row_names_enum.rewind
205
+ @properties[:data].size.times do
206
+ @properties[:row_names] << row_names_enum.next
207
+ end
208
+ end
209
+ end
210
+
201
211
  def << row
202
212
  data << row
203
213
  end
204
214
  end
205
215
 
206
216
  controls = constants.reject{|x| x==:Base}
217
+ control_module = self
207
218
  controls.each do |x|
208
219
  ShenmeGUI.singleton_class.instance_eval do
209
220
  define_method "#{x}".downcase do |*arr, &block|
210
- el = const_get("ShenmeGUI::Control::#{x}").new(*arr)
221
+ el = control_module.const_get(x).new(*arr)
211
222
  @temp_stack.last.children << el unless @temp_stack.empty?
212
223
  el.parent = @temp_stack.last unless @temp_stack.empty?
213
224
  @temp_stack << el
@@ -5,11 +5,21 @@ module ShenmeGUI
5
5
  @elements = []
6
6
  @temp_stack = []
7
7
 
8
+ # FakeSocket把send的内容都记录下来,在WebSocket连接建立时会将这些消息读出,转发到ws上
9
+ class FakeSocket
10
+ attr_reader :messages
11
+ def initialize
12
+ @messages = []
13
+ end
14
+ def send(msg)
15
+ @messages << msg
16
+ end
17
+ end
18
+
8
19
  class << self
9
- attr_accessor :socket
10
- attr_reader :this, :elements
20
+ attr_reader :this, :elements, :socket
11
21
 
12
- def handle(msg)
22
+ def handle_message(msg)
13
23
  match_data = msg.match(/(.+?):(\d+)(?:->)?({.+?})?/)
14
24
  command = match_data[1].to_sym
15
25
  id = match_data[2].to_i
@@ -27,15 +37,21 @@ module ShenmeGUI
27
37
  end
28
38
  target
29
39
  end
40
+ private :handle_message
30
41
 
31
42
  def app(params={}, &block)
43
+ raise "ShenmeGUI app has been initialized" if @initialized
44
+ @initialized = true
45
+ @fake_socket = FakeSocket.new
46
+ @socket = @fake_socket
32
47
  body(params, &block)
33
48
  #找一个空闲的端口,不太好看
34
49
  temp_server = TCPServer.open('localhost', 0)
35
50
  @port = temp_server.addr[1]
36
51
  temp_server.close
52
+ @title = params[:title] || 'Application'
37
53
  app_dir = File.expand_path($PROGRAM_NAME).match(/(.+)\/.+/)[1]
38
- File.open("#{app_dir}/index.html", 'w'){ |f| f.write @elements[0].render(port: @port) }
54
+ File.open("#{app_dir}/index.html", 'w'){ |f| f.write @elements[0].render(port: @port, title: @title) }
39
55
  nil
40
56
  end
41
57
 
@@ -70,41 +86,55 @@ module ShenmeGUI
70
86
  end
71
87
  end
72
88
 
89
+ def open_browser
90
+ app_dir = File.expand_path($PROGRAM_NAME).match(/(.+)\/.+/)[1]
91
+ index_path = "#{app_dir}/index.html"
92
+ if Gem.win_platform?
93
+ `start #{index_path}`
94
+ elsif Gem.platforms[1].os == 'linux'
95
+ `xdg-open #{index_path}`
96
+ elsif Gem.platforms[1].os == 'darwin'
97
+ `open #{index_path}`
98
+ end
99
+ rescue
100
+ end
101
+
73
102
  def start!
74
- ws_thread = Thread.new do
75
- EM.run do
76
- EM::WebSocket.run(:host => "0.0.0.0", :port => @port) do |ws|
77
- ws.onopen do
78
- puts "WebSocket connection open"
79
- @elements.each do |e|
80
- e.sync_events
81
- e.sync
103
+ EM.run do
104
+ EM::WebSocket.run(:host => "0.0.0.0", :port => @port) do |ws|
105
+ ws.onopen do
106
+ puts "WebSocket connection open"
107
+ # 同时只能有一个连接,而正常连接关闭的时候会把@socket指向FakeSocket,如果建立连接的时候发现@socket是WebSocket,便把连接关掉
108
+ @socket.close if @socket.respond_to? :close
109
+ @socket = ws
110
+
111
+ class << ws
112
+ alias :original_send :send
113
+ def send(msg)
114
+ puts "Sent: #{msg}"
115
+ original_send(msg)
82
116
  end
83
117
  end
84
118
 
85
- ws.onclose { puts "Connection closed" }
86
-
87
- ws.onmessage do |msg|
88
- puts "Recieved message: #{msg}"
89
- handle msg
119
+ @elements.each do |e|
120
+ e.sync_events
121
+ e.sync
90
122
  end
123
+ @socket.send(@fake_socket.messages.shift) until @fake_socket.messages.empty?
124
+ end
91
125
 
92
- @socket = ws
126
+ ws.onclose do
127
+ puts "Connection closed"
128
+ @socket = @fake_socket
93
129
  end
130
+
131
+ ws.onmessage do |msg|
132
+ puts "Recieved: #{msg}"
133
+ handle_message msg
134
+ end
135
+
94
136
  end
95
137
  end
96
- begin
97
- app_dir = File.expand_path($PROGRAM_NAME).match(/(.+)\/.+/)[1]
98
- index_path = "#{app_dir}/index.html"
99
- if Gem.win_platform?
100
- `start file:///#{index_path}`
101
- elsif Gem.platforms[1].os == 'linux'
102
- `xdg-open file:///#{index_path}`
103
- end
104
- rescue
105
- end
106
-
107
- ws_thread.join
108
138
  rescue Interrupt
109
139
  puts 'bye~'
110
140
  end
@@ -29,17 +29,17 @@ module ShenmeGUI
29
29
  int lpfnHook,
30
30
  LPCSTR lpTemplateName
31
31
  EOS
32
- extern 'BOOL GetOpenFileName(struct OPENFILENAME*)'
33
- extern 'BOOL GetSaveFileName(struct OPENFILENAME*)'
32
+ extern 'BOOL GetOpenFileNameW(struct OPENFILENAME*)'
33
+ extern 'BOOL GetSaveFileNameW(struct OPENFILENAME*)'
34
34
  extern 'HWND GetForegroundWindow()'
35
35
 
36
36
  def self.construct_OPENFILENAME(params={})
37
- filter = params[:filter] ? "#{params[:filter]}\0#{params[:filter]}\0\0" : 0
37
+ filter = params[:filter] ? "#{params[:filter]}\0#{params[:filter]}\0\0".encode('UTF-16LE') : 0
38
38
  title = params[:title] || 0
39
39
  flags = 0x00880000
40
40
  flags += 0x00000200 if params[:multi_select]
41
41
  flags += 0x10000000 if params[:show_hidden]
42
- initial_path = params[:initial_path] ? params[:initial_path] : 0
42
+ initial_path = params[:initial_path] ? params[:initial_path].encode('UTF-16LE') : 0
43
43
 
44
44
  path = "\0" * 1024
45
45
  ofn = Struct_OPENFILENAME.malloc
@@ -69,19 +69,20 @@ module ShenmeGUI
69
69
 
70
70
  def self.get_open_file_name(params={})
71
71
  ofn, path = construct_OPENFILENAME(params)
72
- GetOpenFileName(ofn)
72
+ GetOpenFileNameW(ofn)
73
73
  Fiddle.free(ofn.to_i)
74
- path = path.split("\0")
74
+ path = path.force_encoding('UTF-16LE').encode('UTF-8').split("\0")
75
75
  path = path[1..-1].collect{|x| "#{path[0]}\\#{x}"} if path.size > 1
76
- path.collect{|x| x.force_encoding('GBK').encode('UTF-8')}
77
76
  path.size > 1 ? path : path[0]
78
77
  end
79
78
 
80
79
  def self.get_save_file_name(params={})
80
+ #禁止保存文件多选
81
+ params[:multi_select] = false
81
82
  ofn, path = construct_OPENFILENAME(params)
82
- GetSaveFileName(ofn)
83
+ GetSaveFileNameW(ofn)
83
84
  Fiddle.free(ofn.to_i)
84
- path.force_encoding('GBK').encode('UTF-8')
85
+ path.force_encoding('UTF-16LE').encode('UTF-8').split("\0")
85
86
  end
86
87
 
87
88
  #avoid error under linux
@@ -99,4 +100,4 @@ module ShenmeGUI
99
100
 
100
101
  end
101
102
 
102
- #ShenmeGUI::FileDialog.get_open_file_name(initial_path: 'C:\\')
103
+ #puts ShenmeGUI::FileDialog.get_open_file_name(initial_path: 'D:\\迅雷下载', multi_select: true, filter: '*.中文')
@@ -1,3 +1,4 @@
1
+ # 用在控件的属性上,给Array String Hash加上钩子,监视其对自身的操作,一旦内容有改变立刻请求同步控件属性
1
2
  module ShenmeGUI
2
3
  @unhook_methods = {
3
4
  array: %i{<< []= clear collect! compact! concat delete delete_at delete_if fill flatten! replace insert keep_if map map! pop push reject! replace rotate! select! shift shuffle! slice! sort! sort_by! uniq! unshift},
@@ -7,8 +8,9 @@ module ShenmeGUI
7
8
  @unhook_methods.each do |k, v|
8
9
  const_set("Hooked#{k.to_s.capitalize}", Class.new(const_get(k.to_s.capitalize)))
9
10
  const_get("Hooked#{k.to_s.capitalize}").class_eval do
10
- def initialize(obj, owner)
11
+ def initialize(obj, owner, callback)
11
12
  @owner = owner
13
+ @callback = callback
12
14
  super(obj)
13
15
  end
14
16
 
@@ -16,7 +18,7 @@ module ShenmeGUI
16
18
  methods.each do |k, v|
17
19
  define_method(k) do |*arr, &block|
18
20
  result = v.bind(self).call(*arr, &block)
19
- @owner.sync
21
+ @owner.send(@callback)
20
22
  result
21
23
  end
22
24
  end
@@ -7,12 +7,17 @@ var websocket =
7
7
  console.log("Connected.");
8
8
  };
9
9
  websocket.onmessage = function(evt){
10
- console.log(evt.data);
10
+ console.log("received: " + evt.data);
11
11
  handleMessage(evt.data);
12
12
  };
13
13
  websocket.onclose = function(evt){
14
14
  console.log("Closed.");
15
15
  };
16
+ websocket.originalSend = websocket.send;
17
+ websocket.send = function(msg){
18
+ console.log("sent: " + msg);
19
+ websocket.originalSend(msg);
20
+ }
16
21
  return websocket;
17
22
  })();
18
23
 
@@ -97,7 +102,7 @@ function getId(obj){
97
102
 
98
103
  var syncHandlers = {
99
104
  base: (function(target, data){
100
- var properties = ['width', 'height', 'font', 'margin', 'background', 'border'];
105
+ var properties = ['width', 'height', 'font', 'margin'];
101
106
  for(var i=0;i<properties.length;i++){
102
107
  if(data[properties[i]]!=undefined) target.style[properties[i]] = data[properties[i]];
103
108
  }
@@ -108,27 +113,19 @@ var syncHandlers = {
108
113
  }),
109
114
 
110
115
  form: (function(target, data){
111
- target.getElementsByClassName('title')[0].getElementsByTagName('span')[0].innerText = data.title;
116
+ target.getElementsByClassName('title')[0].getElementsByTagName('span')[0].textContent = data.title;
112
117
  }),
113
118
 
114
119
  button: (function(target, data){
115
- target.innerText = data.text;
120
+ target.textContent = data.text;
116
121
  }),
117
122
 
118
123
  textline: (function(target, data){
119
124
  target.value = data.text;
120
- if(data.selection){
121
- target.selectionStart = data.selection[0];
122
- target.selectionEnd = data.selection[1];
123
- }
124
125
  }),
125
126
 
126
127
  textarea: (function(target, data){
127
128
  target.value = data.text;
128
- if(data.selection){
129
- target.selectionStart = data.selection[0];
130
- target.selectionEnd = data.selection[1];
131
- }
132
129
  }),
133
130
 
134
131
  image: (function(target, data){
@@ -156,7 +153,7 @@ var syncHandlers = {
156
153
  var label = document.createElement('label');
157
154
  input.type = 'checkbox';
158
155
  input.value = data.options[i];
159
- label.innerText = data.options[i];
156
+ label.textContent = data.options[i];
160
157
  option.appendChild(input);
161
158
  option.appendChild(label);
162
159
  target.appendChild(option);
@@ -182,7 +179,7 @@ var syncHandlers = {
182
179
  input.type="radio";
183
180
  input.value = data.options[i];
184
181
  input.name = "radio";
185
- label.innerText = data.options[i];
182
+ label.textContent = data.options[i];
186
183
  option.appendChild(input);
187
184
  option.appendChild(label);
188
185
  target.appendChild(option);
@@ -198,25 +195,45 @@ var syncHandlers = {
198
195
  for(var i=0;i<data.options.length;i++){
199
196
  var option = document.createElement('option');
200
197
  option.value = data.options[i];
201
- option.innerText = data.options[i];
198
+ option.textContent = data.options[i];
202
199
  target.appendChild(option);
203
200
  }
204
201
  }),
205
202
 
206
203
  label: (function(target, data){
207
- target.innerText = data.text;
204
+ target.textContent = data.text;
208
205
  }),
209
206
 
210
207
  table: (function(target, data){
211
208
  for(var i=0;i<target.children.length;) target.removeChild(target.children[i]);
212
209
  var tableData = data.data;
213
- var columnSize = tableData[0].length;
214
210
  var table = document.createElement('table');
211
+ var columnNames = data.column_names;
212
+ var rowNames = data.row_names;
213
+ if (columnNames) {
214
+ var tr = document.createElement('tr');
215
+ if (rowNames) {
216
+ var th = document.createElement('th');
217
+ th.textContent = '';
218
+ tr.appendChild(th);
219
+ }
220
+ for(var i=0; i<columnNames.length; i++ ){
221
+ var th = document.createElement('th');
222
+ th.textContent = columnNames[i] || '';
223
+ tr.appendChild(th);
224
+ }
225
+ table.appendChild(tr);
226
+ }
215
227
  for(var i=0; i<tableData.length; i++){
216
228
  var tr = document.createElement('tr');
217
- for(var j=0; j<columnSize; j++){
229
+ if(rowNames){
230
+ var th = document.createElement('th');
231
+ th.textContent = rowNames[i] || '';
232
+ tr.appendChild(th);
233
+ }
234
+ for(var j=0; j<tableData[i].length; j++){
218
235
  var td = document.createElement('td');
219
- td.innerText = tableData[i][j];
236
+ td.textContent = tableData[i][j];
220
237
  tr.appendChild(td);
221
238
  }
222
239
  table.appendChild(tr);
@@ -131,7 +131,8 @@ input[type="radio"]:checked + label:before {
131
131
 
132
132
  .table {
133
133
  background-color: #808080;
134
- overflow: auto; }
134
+ overflow: auto;
135
+ padding: 0 0 10px 0; }
135
136
 
136
137
  table {
137
138
  border-collapse: collapse;
@@ -140,6 +141,23 @@ table {
140
141
  td {
141
142
  vertical-align: baseline;
142
143
  padding: 3px 15px 3px 3px;
143
- border: 1px solid #d4d0c8; }
144
+ border: 1px solid #bfbfbf;
145
+ overflow: auto; }
146
+
147
+ th {
148
+ background-color: #bfbfbf;
149
+ padding: 3px 0 3px 9px;
150
+ border: 1px solid #808080;
151
+ border-left-color: #fff;
152
+ box-sizing: border-box; }
153
+
154
+ tr th:last-child {
155
+ border-right-color: #bfbfbf; }
156
+
157
+ table tr:first-child th {
158
+ border-top-color: #fff; }
159
+
160
+ table tr:last-child th {
161
+ border-bottom-color: #bfbfbf; }
144
162
 
145
163
  /*# sourceMappingURL=style.css.map */
@@ -2,7 +2,7 @@
2
2
  <html lang="zh">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <title>Application</title>
5
+ <title><%= material[:title] %></title>
6
6
  <style>
7
7
  <%= material[:style] %>
8
8
  </style>
@@ -1 +1 @@
1
- <div id="item-<%= id %>" class="label" data-type="label"><%= text %></div>
1
+ <div id="item-<%= id %>" class="label" data-type="label"></div>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shenmegui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: '0.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - CicholGricenchos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-02 00:00:00.000000000 Z
11
+ date: 2015-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-websocket