shenmegui 0.3.6 → 0.4

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