scout 5.6.7 → 5.6.8.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/CHANGELOG.markdown +4 -0
  2. data/lib/scout/version.rb +1 -1
  3. data/vendor/json_pure/.gitignore +12 -0
  4. data/vendor/json_pure/.travis.yml +20 -0
  5. data/vendor/json_pure/CHANGES +120 -0
  6. data/vendor/json_pure/COPYING-json-jruby +57 -0
  7. data/vendor/json_pure/Gemfile +11 -0
  8. data/vendor/json_pure/README-json-jruby.markdown +33 -0
  9. data/vendor/json_pure/{README → README.rdoc} +7 -7
  10. data/vendor/json_pure/Rakefile +305 -185
  11. data/vendor/json_pure/VERSION +1 -1
  12. data/vendor/json_pure/diagrams/.keep +0 -0
  13. data/vendor/json_pure/ext/json/ext/fbuffer/fbuffer.h +181 -0
  14. data/vendor/json_pure/ext/json/ext/generator/depend +1 -0
  15. data/vendor/json_pure/ext/json/ext/generator/extconf.rb +4 -6
  16. data/vendor/json_pure/ext/json/ext/generator/generator.c +463 -369
  17. data/vendor/json_pure/ext/json/ext/generator/generator.h +44 -66
  18. data/vendor/json_pure/ext/json/ext/parser/depend +1 -0
  19. data/vendor/json_pure/ext/json/ext/parser/extconf.rb +3 -5
  20. data/vendor/json_pure/ext/json/ext/parser/parser.c +580 -311
  21. data/vendor/json_pure/ext/json/ext/parser/parser.h +14 -8
  22. data/vendor/json_pure/ext/json/ext/parser/parser.rl +242 -107
  23. data/vendor/json_pure/install.rb +8 -11
  24. data/vendor/json_pure/java/src/json/ext/ByteListTranscoder.java +167 -0
  25. data/vendor/json_pure/java/src/json/ext/Generator.java +444 -0
  26. data/vendor/json_pure/java/src/json/ext/GeneratorMethods.java +232 -0
  27. data/vendor/json_pure/java/src/json/ext/GeneratorService.java +43 -0
  28. data/vendor/json_pure/java/src/json/ext/GeneratorState.java +543 -0
  29. data/vendor/json_pure/java/src/json/ext/OptionsReader.java +114 -0
  30. data/vendor/json_pure/java/src/json/ext/Parser.java +2644 -0
  31. data/vendor/json_pure/java/src/json/ext/Parser.rl +968 -0
  32. data/vendor/json_pure/java/src/json/ext/ParserService.java +35 -0
  33. data/vendor/json_pure/java/src/json/ext/RuntimeInfo.java +121 -0
  34. data/vendor/json_pure/java/src/json/ext/StringDecoder.java +167 -0
  35. data/vendor/json_pure/java/src/json/ext/StringEncoder.java +106 -0
  36. data/vendor/json_pure/java/src/json/ext/Utils.java +89 -0
  37. data/vendor/json_pure/json-java.gemspec +23 -0
  38. data/vendor/json_pure/json.gemspec +37 -0
  39. data/vendor/json_pure/json_pure.gemspec +39 -0
  40. data/vendor/json_pure/lib/json.rb +52 -0
  41. data/vendor/json_pure/lib/json/add/bigdecimal.rb +28 -0
  42. data/vendor/json_pure/lib/json/add/complex.rb +22 -0
  43. data/vendor/json_pure/lib/json/add/core.rb +9 -146
  44. data/vendor/json_pure/lib/json/add/date.rb +34 -0
  45. data/vendor/json_pure/lib/json/add/date_time.rb +50 -0
  46. data/vendor/json_pure/lib/json/add/exception.rb +31 -0
  47. data/vendor/json_pure/lib/json/add/ostruct.rb +31 -0
  48. data/vendor/json_pure/lib/json/add/range.rb +29 -0
  49. data/vendor/json_pure/lib/json/add/rational.rb +22 -0
  50. data/vendor/json_pure/lib/json/add/regexp.rb +30 -0
  51. data/vendor/json_pure/lib/json/add/struct.rb +30 -0
  52. data/vendor/json_pure/lib/json/add/symbol.rb +25 -0
  53. data/vendor/json_pure/lib/json/add/time.rb +38 -0
  54. data/vendor/json_pure/lib/json/common.rb +157 -67
  55. data/vendor/json_pure/lib/json/ext.rb +8 -2
  56. data/vendor/json_pure/lib/json/ext/.keep +0 -0
  57. data/vendor/json_pure/lib/json/generic_object.rb +70 -0
  58. data/vendor/json_pure/lib/json/pure.rb +8 -64
  59. data/vendor/json_pure/lib/json/pure/generator.rb +183 -113
  60. data/vendor/json_pure/lib/json/pure/parser.rb +118 -66
  61. data/vendor/json_pure/lib/json/version.rb +1 -1
  62. data/vendor/json_pure/tests/fixtures/fail18.json +1 -1
  63. data/vendor/json_pure/tests/setup_variant.rb +11 -0
  64. data/vendor/json_pure/tests/test_json.rb +233 -28
  65. data/vendor/json_pure/tests/test_json_addition.rb +68 -34
  66. data/vendor/json_pure/tests/test_json_encoding.rb +11 -14
  67. data/vendor/json_pure/tests/test_json_fixtures.rb +11 -10
  68. data/vendor/json_pure/tests/test_json_generate.rb +207 -7
  69. data/vendor/json_pure/tests/test_json_generic_object.rb +75 -0
  70. data/vendor/json_pure/tests/test_json_string_matching.rb +39 -0
  71. data/vendor/json_pure/tests/test_json_unicode.rb +3 -7
  72. data/vendor/json_pure/tools/fuzz.rb +1 -1
  73. data/vendor/json_pure/tools/server.rb +1 -0
  74. metadata +87 -94
  75. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkComparison.log +0 -52
  76. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast-autocorrelation.dat +0 -1000
  77. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast.dat +0 -1001
  78. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty-autocorrelation.dat +0 -900
  79. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty.dat +0 -901
  80. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe-autocorrelation.dat +0 -1000
  81. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe.dat +0 -1001
  82. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt.log +0 -261
  83. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast-autocorrelation.dat +0 -1000
  84. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast.dat +0 -1001
  85. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty-autocorrelation.dat +0 -1000
  86. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty.dat +0 -1001
  87. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe-autocorrelation.dat +0 -1000
  88. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe.dat +0 -1001
  89. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure.log +0 -262
  90. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator-autocorrelation.dat +0 -1000
  91. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator.dat +0 -1001
  92. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails.log +0 -82
  93. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkComparison.log +0 -34
  94. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser-autocorrelation.dat +0 -900
  95. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser.dat +0 -901
  96. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt.log +0 -81
  97. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser-autocorrelation.dat +0 -1000
  98. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser.dat +0 -1001
  99. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure.log +0 -82
  100. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser-autocorrelation.dat +0 -1000
  101. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser.dat +0 -1001
  102. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails.log +0 -82
  103. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser-autocorrelation.dat +0 -1000
  104. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser.dat +0 -1001
  105. data/vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML.log +0 -82
  106. data/vendor/json_pure/benchmarks/generator2_benchmark.rb +0 -222
  107. data/vendor/json_pure/benchmarks/generator_benchmark.rb +0 -224
  108. data/vendor/json_pure/benchmarks/ohai.json +0 -1216
  109. data/vendor/json_pure/benchmarks/ohai.ruby +0 -1
  110. data/vendor/json_pure/benchmarks/parser2_benchmark.rb +0 -251
  111. data/vendor/json_pure/benchmarks/parser_benchmark.rb +0 -259
  112. data/vendor/json_pure/bin/edit_json.rb +0 -9
  113. data/vendor/json_pure/bin/prettify_json.rb +0 -75
  114. data/vendor/json_pure/lib/json/Array.xpm +0 -21
  115. data/vendor/json_pure/lib/json/FalseClass.xpm +0 -21
  116. data/vendor/json_pure/lib/json/Hash.xpm +0 -21
  117. data/vendor/json_pure/lib/json/Key.xpm +0 -73
  118. data/vendor/json_pure/lib/json/NilClass.xpm +0 -21
  119. data/vendor/json_pure/lib/json/Numeric.xpm +0 -28
  120. data/vendor/json_pure/lib/json/String.xpm +0 -96
  121. data/vendor/json_pure/lib/json/TrueClass.xpm +0 -21
  122. data/vendor/json_pure/lib/json/add/rails.rb +0 -58
  123. data/vendor/json_pure/lib/json/editor.rb +0 -1371
  124. data/vendor/json_pure/lib/json/json.xpm +0 -1499
  125. data/vendor/json_pure/tests/test_json_rails.rb +0 -144
@@ -1,1371 +0,0 @@
1
- # To use the GUI JSON editor, start the edit_json.rb executable script. It
2
- # requires ruby-gtk to be installed.
3
-
4
- require 'gtk2'
5
- require 'iconv'
6
- require 'json'
7
- require 'rbconfig'
8
- require 'open-uri'
9
-
10
- module JSON
11
- module Editor
12
- include Gtk
13
-
14
- # Beginning of the editor window title
15
- TITLE = 'JSON Editor'.freeze
16
-
17
- # Columns constants
18
- ICON_COL, TYPE_COL, CONTENT_COL = 0, 1, 2
19
-
20
- # JSON primitive types (Containers)
21
- CONTAINER_TYPES = %w[Array Hash].sort
22
- # All JSON primitive types
23
- ALL_TYPES = (%w[TrueClass FalseClass Numeric String NilClass] +
24
- CONTAINER_TYPES).sort
25
-
26
- # The Nodes necessary for the tree representation of a JSON document
27
- ALL_NODES = (ALL_TYPES + %w[Key]).sort
28
-
29
- DEFAULT_DIALOG_KEY_PRESS_HANDLER = lambda do |dialog, event|
30
- case event.keyval
31
- when Gdk::Keyval::GDK_Return
32
- dialog.response Dialog::RESPONSE_ACCEPT
33
- when Gdk::Keyval::GDK_Escape
34
- dialog.response Dialog::RESPONSE_REJECT
35
- end
36
- end
37
-
38
- # Returns the Gdk::Pixbuf of the icon named _name_ from the icon cache.
39
- def Editor.fetch_icon(name)
40
- @icon_cache ||= {}
41
- unless @icon_cache.key?(name)
42
- path = File.dirname(__FILE__)
43
- @icon_cache[name] = Gdk::Pixbuf.new(File.join(path, name + '.xpm'))
44
- end
45
- @icon_cache[name]
46
- end
47
-
48
- # Opens an error dialog on top of _window_ showing the error message
49
- # _text_.
50
- def Editor.error_dialog(window, text)
51
- dialog = MessageDialog.new(window, Dialog::MODAL,
52
- MessageDialog::ERROR,
53
- MessageDialog::BUTTONS_CLOSE, text)
54
- dialog.show_all
55
- dialog.run
56
- rescue TypeError
57
- dialog = MessageDialog.new(Editor.window, Dialog::MODAL,
58
- MessageDialog::ERROR,
59
- MessageDialog::BUTTONS_CLOSE, text)
60
- dialog.show_all
61
- dialog.run
62
- ensure
63
- dialog.destroy if dialog
64
- end
65
-
66
- # Opens a yes/no question dialog on top of _window_ showing the error
67
- # message _text_. If yes was answered _true_ is returned, otherwise
68
- # _false_.
69
- def Editor.question_dialog(window, text)
70
- dialog = MessageDialog.new(window, Dialog::MODAL,
71
- MessageDialog::QUESTION,
72
- MessageDialog::BUTTONS_YES_NO, text)
73
- dialog.show_all
74
- dialog.run do |response|
75
- return Gtk::Dialog::RESPONSE_YES === response
76
- end
77
- ensure
78
- dialog.destroy if dialog
79
- end
80
-
81
- # Convert the tree model starting from Gtk::TreeIter _iter_ into a Ruby
82
- # data structure and return it.
83
- def Editor.model2data(iter)
84
- return nil if iter.nil?
85
- case iter.type
86
- when 'Hash'
87
- hash = {}
88
- iter.each { |c| hash[c.content] = Editor.model2data(c.first_child) }
89
- hash
90
- when 'Array'
91
- array = Array.new(iter.n_children)
92
- iter.each_with_index { |c, i| array[i] = Editor.model2data(c) }
93
- array
94
- when 'Key'
95
- iter.content
96
- when 'String'
97
- iter.content
98
- when 'Numeric'
99
- content = iter.content
100
- if /\./.match(content)
101
- content.to_f
102
- else
103
- content.to_i
104
- end
105
- when 'TrueClass'
106
- true
107
- when 'FalseClass'
108
- false
109
- when 'NilClass'
110
- nil
111
- else
112
- fail "Unknown type found in model: #{iter.type}"
113
- end
114
- end
115
-
116
- # Convert the Ruby data structure _data_ into tree model data for Gtk and
117
- # returns the whole model. If the parameter _model_ wasn't given a new
118
- # Gtk::TreeStore is created as the model. The _parent_ parameter specifies
119
- # the parent node (iter, Gtk:TreeIter instance) to which the data is
120
- # appended, alternativeley the result of the yielded block is used as iter.
121
- def Editor.data2model(data, model = nil, parent = nil)
122
- model ||= TreeStore.new(Gdk::Pixbuf, String, String)
123
- iter = if block_given?
124
- yield model
125
- else
126
- model.append(parent)
127
- end
128
- case data
129
- when Hash
130
- iter.type = 'Hash'
131
- data.sort.each do |key, value|
132
- pair_iter = model.append(iter)
133
- pair_iter.type = 'Key'
134
- pair_iter.content = key.to_s
135
- Editor.data2model(value, model, pair_iter)
136
- end
137
- when Array
138
- iter.type = 'Array'
139
- data.each do |value|
140
- Editor.data2model(value, model, iter)
141
- end
142
- when Numeric
143
- iter.type = 'Numeric'
144
- iter.content = data.to_s
145
- when String, true, false, nil
146
- iter.type = data.class.name
147
- iter.content = data.nil? ? 'null' : data.to_s
148
- else
149
- iter.type = 'String'
150
- iter.content = data.to_s
151
- end
152
- model
153
- end
154
-
155
- # The Gtk::TreeIter class is reopened and some auxiliary methods are added.
156
- class Gtk::TreeIter
157
- include Enumerable
158
-
159
- # Traverse each of this Gtk::TreeIter instance's children
160
- # and yield to them.
161
- def each
162
- n_children.times { |i| yield nth_child(i) }
163
- end
164
-
165
- # Recursively traverse all nodes of this Gtk::TreeIter's subtree
166
- # (including self) and yield to them.
167
- def recursive_each(&block)
168
- yield self
169
- each do |i|
170
- i.recursive_each(&block)
171
- end
172
- end
173
-
174
- # Remove the subtree of this Gtk::TreeIter instance from the
175
- # model _model_.
176
- def remove_subtree(model)
177
- while current = first_child
178
- model.remove(current)
179
- end
180
- end
181
-
182
- # Returns the type of this node.
183
- def type
184
- self[TYPE_COL]
185
- end
186
-
187
- # Sets the type of this node to _value_. This implies setting
188
- # the respective icon accordingly.
189
- def type=(value)
190
- self[TYPE_COL] = value
191
- self[ICON_COL] = Editor.fetch_icon(value)
192
- end
193
-
194
- # Returns the content of this node.
195
- def content
196
- self[CONTENT_COL]
197
- end
198
-
199
- # Sets the content of this node to _value_.
200
- def content=(value)
201
- self[CONTENT_COL] = value
202
- end
203
- end
204
-
205
- # This module bundles some method, that can be used to create a menu. It
206
- # should be included into the class in question.
207
- module MenuExtension
208
- include Gtk
209
-
210
- # Creates a Menu, that includes MenuExtension. _treeview_ is the
211
- # Gtk::TreeView, on which it operates.
212
- def initialize(treeview)
213
- @treeview = treeview
214
- @menu = Menu.new
215
- end
216
-
217
- # Returns the Gtk::TreeView of this menu.
218
- attr_reader :treeview
219
-
220
- # Returns the menu.
221
- attr_reader :menu
222
-
223
- # Adds a Gtk::SeparatorMenuItem to this instance's #menu.
224
- def add_separator
225
- menu.append SeparatorMenuItem.new
226
- end
227
-
228
- # Adds a Gtk::MenuItem to this instance's #menu. _label_ is the label
229
- # string, _klass_ is the item type, and _callback_ is the procedure, that
230
- # is called if the _item_ is activated.
231
- def add_item(label, keyval = nil, klass = MenuItem, &callback)
232
- label = "#{label} (C-#{keyval.chr})" if keyval
233
- item = klass.new(label)
234
- item.signal_connect(:activate, &callback)
235
- if keyval
236
- self.signal_connect(:'key-press-event') do |item, event|
237
- if event.state & Gdk::Window::ModifierType::CONTROL_MASK != 0 and
238
- event.keyval == keyval
239
- callback.call item
240
- end
241
- end
242
- end
243
- menu.append item
244
- item
245
- end
246
-
247
- # This method should be implemented in subclasses to create the #menu of
248
- # this instance. It has to be called after an instance of this class is
249
- # created, to build the menu.
250
- def create
251
- raise NotImplementedError
252
- end
253
-
254
- def method_missing(*a, &b)
255
- treeview.__send__(*a, &b)
256
- end
257
- end
258
-
259
- # This class creates the popup menu, that opens when clicking onto the
260
- # treeview.
261
- class PopUpMenu
262
- include MenuExtension
263
-
264
- # Change the type or content of the selected node.
265
- def change_node(item)
266
- if current = selection.selected
267
- parent = current.parent
268
- old_type, old_content = current.type, current.content
269
- if ALL_TYPES.include?(old_type)
270
- @clipboard_data = Editor.model2data(current)
271
- type, content = ask_for_element(parent, current.type,
272
- current.content)
273
- if type
274
- current.type, current.content = type, content
275
- current.remove_subtree(model)
276
- toplevel.display_status("Changed a node in tree.")
277
- window.change
278
- end
279
- else
280
- toplevel.display_status(
281
- "Cannot change node of type #{old_type} in tree!")
282
- end
283
- end
284
- end
285
-
286
- # Cut the selected node and its subtree, and save it into the
287
- # clipboard.
288
- def cut_node(item)
289
- if current = selection.selected
290
- if current and current.type == 'Key'
291
- @clipboard_data = {
292
- current.content => Editor.model2data(current.first_child)
293
- }
294
- else
295
- @clipboard_data = Editor.model2data(current)
296
- end
297
- model.remove(current)
298
- window.change
299
- toplevel.display_status("Cut a node from tree.")
300
- end
301
- end
302
-
303
- # Copy the selected node and its subtree, and save it into the
304
- # clipboard.
305
- def copy_node(item)
306
- if current = selection.selected
307
- if current and current.type == 'Key'
308
- @clipboard_data = {
309
- current.content => Editor.model2data(current.first_child)
310
- }
311
- else
312
- @clipboard_data = Editor.model2data(current)
313
- end
314
- window.change
315
- toplevel.display_status("Copied a node from tree.")
316
- end
317
- end
318
-
319
- # Paste the data in the clipboard into the selected Array or Hash by
320
- # appending it.
321
- def paste_node_appending(item)
322
- if current = selection.selected
323
- if @clipboard_data
324
- case current.type
325
- when 'Array'
326
- Editor.data2model(@clipboard_data, model, current)
327
- expand_collapse(current)
328
- when 'Hash'
329
- if @clipboard_data.is_a? Hash
330
- parent = current.parent
331
- hash = Editor.model2data(current)
332
- model.remove(current)
333
- hash.update(@clipboard_data)
334
- Editor.data2model(hash, model, parent)
335
- if parent
336
- expand_collapse(parent)
337
- elsif @expanded
338
- expand_all
339
- end
340
- window.change
341
- else
342
- toplevel.display_status(
343
- "Cannot paste non-#{current.type} data into '#{current.type}'!")
344
- end
345
- else
346
- toplevel.display_status(
347
- "Cannot paste node below '#{current.type}'!")
348
- end
349
- else
350
- toplevel.display_status("Nothing to paste in clipboard!")
351
- end
352
- else
353
- toplevel.display_status("Append a node into the root first!")
354
- end
355
- end
356
-
357
- # Paste the data in the clipboard into the selected Array inserting it
358
- # before the selected element.
359
- def paste_node_inserting_before(item)
360
- if current = selection.selected
361
- if @clipboard_data
362
- parent = current.parent or return
363
- parent_type = parent.type
364
- if parent_type == 'Array'
365
- selected_index = parent.each_with_index do |c, i|
366
- break i if c == current
367
- end
368
- Editor.data2model(@clipboard_data, model, parent) do |m|
369
- m.insert_before(parent, current)
370
- end
371
- expand_collapse(current)
372
- toplevel.display_status("Inserted an element to " +
373
- "'#{parent_type}' before index #{selected_index}.")
374
- window.change
375
- else
376
- toplevel.display_status(
377
- "Cannot insert node below '#{parent_type}'!")
378
- end
379
- else
380
- toplevel.display_status("Nothing to paste in clipboard!")
381
- end
382
- else
383
- toplevel.display_status("Append a node into the root first!")
384
- end
385
- end
386
-
387
- # Append a new node to the selected Hash or Array.
388
- def append_new_node(item)
389
- if parent = selection.selected
390
- parent_type = parent.type
391
- case parent_type
392
- when 'Hash'
393
- key, type, content = ask_for_hash_pair(parent)
394
- key or return
395
- iter = create_node(parent, 'Key', key)
396
- iter = create_node(iter, type, content)
397
- toplevel.display_status(
398
- "Added a (key, value)-pair to '#{parent_type}'.")
399
- window.change
400
- when 'Array'
401
- type, content = ask_for_element(parent)
402
- type or return
403
- iter = create_node(parent, type, content)
404
- window.change
405
- toplevel.display_status("Appendend an element to '#{parent_type}'.")
406
- else
407
- toplevel.display_status("Cannot append to '#{parent_type}'!")
408
- end
409
- else
410
- type, content = ask_for_element
411
- type or return
412
- iter = create_node(nil, type, content)
413
- window.change
414
- end
415
- end
416
-
417
- # Insert a new node into an Array before the selected element.
418
- def insert_new_node(item)
419
- if current = selection.selected
420
- parent = current.parent or return
421
- parent_parent = parent.parent
422
- parent_type = parent.type
423
- if parent_type == 'Array'
424
- selected_index = parent.each_with_index do |c, i|
425
- break i if c == current
426
- end
427
- type, content = ask_for_element(parent)
428
- type or return
429
- iter = model.insert_before(parent, current)
430
- iter.type, iter.content = type, content
431
- toplevel.display_status("Inserted an element to " +
432
- "'#{parent_type}' before index #{selected_index}.")
433
- window.change
434
- else
435
- toplevel.display_status(
436
- "Cannot insert node below '#{parent_type}'!")
437
- end
438
- else
439
- toplevel.display_status("Append a node into the root first!")
440
- end
441
- end
442
-
443
- # Recursively collapse/expand a subtree starting from the selected node.
444
- def collapse_expand(item)
445
- if current = selection.selected
446
- if row_expanded?(current.path)
447
- collapse_row(current.path)
448
- else
449
- expand_row(current.path, true)
450
- end
451
- else
452
- toplevel.display_status("Append a node into the root first!")
453
- end
454
- end
455
-
456
- # Create the menu.
457
- def create
458
- add_item("Change node", ?n, &method(:change_node))
459
- add_separator
460
- add_item("Cut node", ?X, &method(:cut_node))
461
- add_item("Copy node", ?C, &method(:copy_node))
462
- add_item("Paste node (appending)", ?A, &method(:paste_node_appending))
463
- add_item("Paste node (inserting before)", ?I,
464
- &method(:paste_node_inserting_before))
465
- add_separator
466
- add_item("Append new node", ?a, &method(:append_new_node))
467
- add_item("Insert new node before", ?i, &method(:insert_new_node))
468
- add_separator
469
- add_item("Collapse/Expand node (recursively)", ?e,
470
- &method(:collapse_expand))
471
-
472
- menu.show_all
473
- signal_connect(:button_press_event) do |widget, event|
474
- if event.kind_of? Gdk::EventButton and event.button == 3
475
- menu.popup(nil, nil, event.button, event.time)
476
- end
477
- end
478
- signal_connect(:popup_menu) do
479
- menu.popup(nil, nil, 0, Gdk::Event::CURRENT_TIME)
480
- end
481
- end
482
- end
483
-
484
- # This class creates the File pulldown menu.
485
- class FileMenu
486
- include MenuExtension
487
-
488
- # Clear the model and filename, but ask to save the JSON document, if
489
- # unsaved changes have occured.
490
- def new(item)
491
- window.clear
492
- end
493
-
494
- # Open a file and load it into the editor. Ask to save the JSON document
495
- # first, if unsaved changes have occured.
496
- def open(item)
497
- window.file_open
498
- end
499
-
500
- def open_location(item)
501
- window.location_open
502
- end
503
-
504
- # Revert the current JSON document in the editor to the saved version.
505
- def revert(item)
506
- window.instance_eval do
507
- @filename and file_open(@filename)
508
- end
509
- end
510
-
511
- # Save the current JSON document.
512
- def save(item)
513
- window.file_save
514
- end
515
-
516
- # Save the current JSON document under the given filename.
517
- def save_as(item)
518
- window.file_save_as
519
- end
520
-
521
- # Quit the editor, after asking to save any unsaved changes first.
522
- def quit(item)
523
- window.quit
524
- end
525
-
526
- # Create the menu.
527
- def create
528
- title = MenuItem.new('File')
529
- title.submenu = menu
530
- add_item('New', &method(:new))
531
- add_item('Open', ?o, &method(:open))
532
- add_item('Open location', ?l, &method(:open_location))
533
- add_item('Revert', &method(:revert))
534
- add_separator
535
- add_item('Save', ?s, &method(:save))
536
- add_item('Save As', ?S, &method(:save_as))
537
- add_separator
538
- add_item('Quit', ?q, &method(:quit))
539
- title
540
- end
541
- end
542
-
543
- # This class creates the Edit pulldown menu.
544
- class EditMenu
545
- include MenuExtension
546
-
547
- # Copy data from model into primary clipboard.
548
- def copy(item)
549
- data = Editor.model2data(model.iter_first)
550
- json = JSON.pretty_generate(data, :max_nesting => false)
551
- c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
552
- c.text = json
553
- end
554
-
555
- # Copy json text from primary clipboard into model.
556
- def paste(item)
557
- c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
558
- if json = c.wait_for_text
559
- window.ask_save if @changed
560
- begin
561
- window.edit json
562
- rescue JSON::ParserError
563
- window.clear
564
- end
565
- end
566
- end
567
-
568
- # Find a string in all nodes' contents and select the found node in the
569
- # treeview.
570
- def find(item)
571
- @search = ask_for_find_term(@search) or return
572
- iter = model.get_iter('0') or return
573
- iter.recursive_each do |i|
574
- if @iter
575
- if @iter != i
576
- next
577
- else
578
- @iter = nil
579
- next
580
- end
581
- elsif @search.match(i[CONTENT_COL])
582
- set_cursor(i.path, nil, false)
583
- @iter = i
584
- break
585
- end
586
- end
587
- end
588
-
589
- # Repeat the last search given by #find.
590
- def find_again(item)
591
- @search or return
592
- iter = model.get_iter('0')
593
- iter.recursive_each do |i|
594
- if @iter
595
- if @iter != i
596
- next
597
- else
598
- @iter = nil
599
- next
600
- end
601
- elsif @search.match(i[CONTENT_COL])
602
- set_cursor(i.path, nil, false)
603
- @iter = i
604
- break
605
- end
606
- end
607
- end
608
-
609
- # Sort (Reverse sort) all elements of the selected array by the given
610
- # expression. _x_ is the element in question.
611
- def sort(item)
612
- if current = selection.selected
613
- if current.type == 'Array'
614
- parent = current.parent
615
- ary = Editor.model2data(current)
616
- order, reverse = ask_for_order
617
- order or return
618
- begin
619
- block = eval "lambda { |x| #{order} }"
620
- if reverse
621
- ary.sort! { |a,b| block[b] <=> block[a] }
622
- else
623
- ary.sort! { |a,b| block[a] <=> block[b] }
624
- end
625
- rescue => e
626
- Editor.error_dialog(self, "Failed to sort Array with #{order}: #{e}!")
627
- else
628
- Editor.data2model(ary, model, parent) do |m|
629
- m.insert_before(parent, current)
630
- end
631
- model.remove(current)
632
- expand_collapse(parent)
633
- window.change
634
- toplevel.display_status("Array has been sorted.")
635
- end
636
- else
637
- toplevel.display_status("Only Array nodes can be sorted!")
638
- end
639
- else
640
- toplevel.display_status("Select an Array to sort first!")
641
- end
642
- end
643
-
644
- # Create the menu.
645
- def create
646
- title = MenuItem.new('Edit')
647
- title.submenu = menu
648
- add_item('Copy', ?c, &method(:copy))
649
- add_item('Paste', ?v, &method(:paste))
650
- add_separator
651
- add_item('Find', ?f, &method(:find))
652
- add_item('Find Again', ?g, &method(:find_again))
653
- add_separator
654
- add_item('Sort', ?S, &method(:sort))
655
- title
656
- end
657
- end
658
-
659
- class OptionsMenu
660
- include MenuExtension
661
-
662
- # Collapse/Expand all nodes by default.
663
- def collapsed_nodes(item)
664
- if expanded
665
- self.expanded = false
666
- collapse_all
667
- else
668
- self.expanded = true
669
- expand_all
670
- end
671
- end
672
-
673
- # Toggle pretty saving mode on/off.
674
- def pretty_saving(item)
675
- @pretty_item.toggled
676
- window.change
677
- end
678
-
679
- attr_reader :pretty_item
680
-
681
- # Create the menu.
682
- def create
683
- title = MenuItem.new('Options')
684
- title.submenu = menu
685
- add_item('Collapsed nodes', nil, CheckMenuItem, &method(:collapsed_nodes))
686
- @pretty_item = add_item('Pretty saving', nil, CheckMenuItem,
687
- &method(:pretty_saving))
688
- @pretty_item.active = true
689
- window.unchange
690
- title
691
- end
692
- end
693
-
694
- # This class inherits from Gtk::TreeView, to configure it and to add a lot
695
- # of behaviour to it.
696
- class JSONTreeView < Gtk::TreeView
697
- include Gtk
698
-
699
- # Creates a JSONTreeView instance, the parameter _window_ is
700
- # a MainWindow instance and used for self delegation.
701
- def initialize(window)
702
- @window = window
703
- super(TreeStore.new(Gdk::Pixbuf, String, String))
704
- self.selection.mode = SELECTION_BROWSE
705
-
706
- @expanded = false
707
- self.headers_visible = false
708
- add_columns
709
- add_popup_menu
710
- end
711
-
712
- # Returns the MainWindow instance of this JSONTreeView.
713
- attr_reader :window
714
-
715
- # Returns true, if nodes are autoexpanding, false otherwise.
716
- attr_accessor :expanded
717
-
718
- private
719
-
720
- def add_columns
721
- cell = CellRendererPixbuf.new
722
- column = TreeViewColumn.new('Icon', cell,
723
- 'pixbuf' => ICON_COL
724
- )
725
- append_column(column)
726
-
727
- cell = CellRendererText.new
728
- column = TreeViewColumn.new('Type', cell,
729
- 'text' => TYPE_COL
730
- )
731
- append_column(column)
732
-
733
- cell = CellRendererText.new
734
- cell.editable = true
735
- column = TreeViewColumn.new('Content', cell,
736
- 'text' => CONTENT_COL
737
- )
738
- cell.signal_connect(:edited, &method(:cell_edited))
739
- append_column(column)
740
- end
741
-
742
- def unify_key(iter, key)
743
- return unless iter.type == 'Key'
744
- parent = iter.parent
745
- if parent.any? { |c| c != iter and c.content == key }
746
- old_key = key
747
- i = 0
748
- begin
749
- key = sprintf("%s.%d", old_key, i += 1)
750
- end while parent.any? { |c| c != iter and c.content == key }
751
- end
752
- iter.content = key
753
- end
754
-
755
- def cell_edited(cell, path, value)
756
- iter = model.get_iter(path)
757
- case iter.type
758
- when 'Key'
759
- unify_key(iter, value)
760
- toplevel.display_status('Key has been changed.')
761
- when 'FalseClass'
762
- value.downcase!
763
- if value == 'true'
764
- iter.type, iter.content = 'TrueClass', 'true'
765
- end
766
- when 'TrueClass'
767
- value.downcase!
768
- if value == 'false'
769
- iter.type, iter.content = 'FalseClass', 'false'
770
- end
771
- when 'Numeric'
772
- iter.content =
773
- if value == 'Infinity'
774
- value
775
- else
776
- (Integer(value) rescue Float(value) rescue 0).to_s
777
- end
778
- when 'String'
779
- iter.content = value
780
- when 'Hash', 'Array'
781
- return
782
- else
783
- fail "Unknown type found in model: #{iter.type}"
784
- end
785
- window.change
786
- end
787
-
788
- def configure_value(value, type)
789
- value.editable = false
790
- case type
791
- when 'Array', 'Hash'
792
- value.text = ''
793
- when 'TrueClass'
794
- value.text = 'true'
795
- when 'FalseClass'
796
- value.text = 'false'
797
- when 'NilClass'
798
- value.text = 'null'
799
- when 'Numeric', 'String'
800
- value.text ||= ''
801
- value.editable = true
802
- else
803
- raise ArgumentError, "unknown type '#{type}' encountered"
804
- end
805
- end
806
-
807
- def add_popup_menu
808
- menu = PopUpMenu.new(self)
809
- menu.create
810
- end
811
-
812
- public
813
-
814
- # Create a _type_ node with content _content_, and add it to _parent_
815
- # in the model. If _parent_ is nil, create a new model and put it into
816
- # the editor treeview.
817
- def create_node(parent, type, content)
818
- iter = if parent
819
- model.append(parent)
820
- else
821
- new_model = Editor.data2model(nil)
822
- toplevel.view_new_model(new_model)
823
- new_model.iter_first
824
- end
825
- iter.type, iter.content = type, content
826
- expand_collapse(parent) if parent
827
- iter
828
- end
829
-
830
- # Ask for a hash key, value pair to be added to the Hash node _parent_.
831
- def ask_for_hash_pair(parent)
832
- key_input = type_input = value_input = nil
833
-
834
- dialog = Dialog.new("New (key, value) pair for Hash", nil, nil,
835
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
836
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
837
- )
838
- dialog.width_request = 640
839
-
840
- hbox = HBox.new(false, 5)
841
- hbox.pack_start(Label.new("Key:"), false)
842
- hbox.pack_start(key_input = Entry.new)
843
- key_input.text = @key || ''
844
- dialog.vbox.pack_start(hbox, false)
845
- key_input.signal_connect(:activate) do
846
- if parent.any? { |c| c.content == key_input.text }
847
- toplevel.display_status('Key already exists in Hash!')
848
- key_input.text = ''
849
- else
850
- toplevel.display_status('Key has been changed.')
851
- end
852
- end
853
-
854
- hbox = HBox.new(false, 5)
855
- hbox.pack_start(Label.new("Type:"), false)
856
- hbox.pack_start(type_input = ComboBox.new(true))
857
- ALL_TYPES.each { |t| type_input.append_text(t) }
858
- type_input.active = @type || 0
859
- dialog.vbox.pack_start(hbox, false)
860
-
861
- type_input.signal_connect(:changed) do
862
- value_input.editable = false
863
- case ALL_TYPES[type_input.active]
864
- when 'Array', 'Hash'
865
- value_input.text = ''
866
- when 'TrueClass'
867
- value_input.text = 'true'
868
- when 'FalseClass'
869
- value_input.text = 'false'
870
- when 'NilClass'
871
- value_input.text = 'null'
872
- else
873
- value_input.text = ''
874
- value_input.editable = true
875
- end
876
- end
877
-
878
- hbox = HBox.new(false, 5)
879
- hbox.pack_start(Label.new("Value:"), false)
880
- hbox.pack_start(value_input = Entry.new)
881
- value_input.width_chars = 60
882
- value_input.text = @value || ''
883
- dialog.vbox.pack_start(hbox, false)
884
-
885
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
886
- dialog.show_all
887
- self.focus = dialog
888
- dialog.run do |response|
889
- if response == Dialog::RESPONSE_ACCEPT
890
- @key = key_input.text
891
- type = ALL_TYPES[@type = type_input.active]
892
- content = value_input.text
893
- return @key, type, content
894
- end
895
- end
896
- return
897
- ensure
898
- dialog.destroy
899
- end
900
-
901
- # Ask for an element to be appended _parent_.
902
- def ask_for_element(parent = nil, default_type = nil, value_text = @content)
903
- type_input = value_input = nil
904
-
905
- dialog = Dialog.new(
906
- "New element into #{parent ? parent.type : 'root'}",
907
- nil, nil,
908
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
909
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
910
- )
911
- hbox = HBox.new(false, 5)
912
- hbox.pack_start(Label.new("Type:"), false)
913
- hbox.pack_start(type_input = ComboBox.new(true))
914
- default_active = 0
915
- types = parent ? ALL_TYPES : CONTAINER_TYPES
916
- types.each_with_index do |t, i|
917
- type_input.append_text(t)
918
- if t == default_type
919
- default_active = i
920
- end
921
- end
922
- type_input.active = default_active
923
- dialog.vbox.pack_start(hbox, false)
924
- type_input.signal_connect(:changed) do
925
- configure_value(value_input, types[type_input.active])
926
- end
927
-
928
- hbox = HBox.new(false, 5)
929
- hbox.pack_start(Label.new("Value:"), false)
930
- hbox.pack_start(value_input = Entry.new)
931
- value_input.width_chars = 60
932
- value_input.text = value_text if value_text
933
- configure_value(value_input, types[type_input.active])
934
-
935
- dialog.vbox.pack_start(hbox, false)
936
-
937
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
938
- dialog.show_all
939
- self.focus = dialog
940
- dialog.run do |response|
941
- if response == Dialog::RESPONSE_ACCEPT
942
- type = types[type_input.active]
943
- @content = case type
944
- when 'Numeric'
945
- if (t = value_input.text) == 'Infinity'
946
- 1 / 0.0
947
- else
948
- Integer(t) rescue Float(t) rescue 0
949
- end
950
- else
951
- value_input.text
952
- end.to_s
953
- return type, @content
954
- end
955
- end
956
- return
957
- ensure
958
- dialog.destroy if dialog
959
- end
960
-
961
- # Ask for an order criteria for sorting, using _x_ for the element in
962
- # question. Returns the order criterium, and true/false for reverse
963
- # sorting.
964
- def ask_for_order
965
- dialog = Dialog.new(
966
- "Give an order criterium for 'x'.",
967
- nil, nil,
968
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
969
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
970
- )
971
- hbox = HBox.new(false, 5)
972
-
973
- hbox.pack_start(Label.new("Order:"), false)
974
- hbox.pack_start(order_input = Entry.new)
975
- order_input.text = @order || 'x'
976
- order_input.width_chars = 60
977
-
978
- hbox.pack_start(reverse_checkbox = CheckButton.new('Reverse'), false)
979
-
980
- dialog.vbox.pack_start(hbox, false)
981
-
982
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
983
- dialog.show_all
984
- self.focus = dialog
985
- dialog.run do |response|
986
- if response == Dialog::RESPONSE_ACCEPT
987
- return @order = order_input.text, reverse_checkbox.active?
988
- end
989
- end
990
- return
991
- ensure
992
- dialog.destroy if dialog
993
- end
994
-
995
- # Ask for a find term to search for in the tree. Returns the term as a
996
- # string.
997
- def ask_for_find_term(search = nil)
998
- dialog = Dialog.new(
999
- "Find a node matching regex in tree.",
1000
- nil, nil,
1001
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
1002
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
1003
- )
1004
- hbox = HBox.new(false, 5)
1005
-
1006
- hbox.pack_start(Label.new("Regex:"), false)
1007
- hbox.pack_start(regex_input = Entry.new)
1008
- hbox.pack_start(icase_checkbox = CheckButton.new('Icase'), false)
1009
- regex_input.width_chars = 60
1010
- if search
1011
- regex_input.text = search.source
1012
- icase_checkbox.active = search.casefold?
1013
- end
1014
-
1015
- dialog.vbox.pack_start(hbox, false)
1016
-
1017
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
1018
- dialog.show_all
1019
- self.focus = dialog
1020
- dialog.run do |response|
1021
- if response == Dialog::RESPONSE_ACCEPT
1022
- begin
1023
- return Regexp.new(regex_input.text, icase_checkbox.active? ? Regexp::IGNORECASE : 0)
1024
- rescue => e
1025
- Editor.error_dialog(self, "Evaluation of regex /#{regex_input.text}/ failed: #{e}!")
1026
- return
1027
- end
1028
- end
1029
- end
1030
- return
1031
- ensure
1032
- dialog.destroy if dialog
1033
- end
1034
-
1035
- # Expand or collapse row pointed to by _iter_ according
1036
- # to the #expanded attribute.
1037
- def expand_collapse(iter)
1038
- if expanded
1039
- expand_row(iter.path, true)
1040
- else
1041
- collapse_row(iter.path)
1042
- end
1043
- end
1044
- end
1045
-
1046
- # The editor main window
1047
- class MainWindow < Gtk::Window
1048
- include Gtk
1049
-
1050
- def initialize(encoding)
1051
- @changed = false
1052
- @encoding = encoding
1053
- super(TOPLEVEL)
1054
- display_title
1055
- set_default_size(800, 600)
1056
- signal_connect(:delete_event) { quit }
1057
-
1058
- vbox = VBox.new(false, 0)
1059
- add(vbox)
1060
- #vbox.border_width = 0
1061
-
1062
- @treeview = JSONTreeView.new(self)
1063
- @treeview.signal_connect(:'cursor-changed') do
1064
- display_status('')
1065
- end
1066
-
1067
- menu_bar = create_menu_bar
1068
- vbox.pack_start(menu_bar, false, false, 0)
1069
-
1070
- sw = ScrolledWindow.new(nil, nil)
1071
- sw.shadow_type = SHADOW_ETCHED_IN
1072
- sw.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
1073
- vbox.pack_start(sw, true, true, 0)
1074
- sw.add(@treeview)
1075
-
1076
- @status_bar = Statusbar.new
1077
- vbox.pack_start(@status_bar, false, false, 0)
1078
-
1079
- @filename ||= nil
1080
- if @filename
1081
- data = read_data(@filename)
1082
- view_new_model Editor.data2model(data)
1083
- end
1084
-
1085
- signal_connect(:button_release_event) do |_,event|
1086
- if event.button == 2
1087
- c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
1088
- if url = c.wait_for_text
1089
- location_open url
1090
- end
1091
- false
1092
- else
1093
- true
1094
- end
1095
- end
1096
- end
1097
-
1098
- # Creates the menu bar with the pulldown menus and returns it.
1099
- def create_menu_bar
1100
- menu_bar = MenuBar.new
1101
- @file_menu = FileMenu.new(@treeview)
1102
- menu_bar.append @file_menu.create
1103
- @edit_menu = EditMenu.new(@treeview)
1104
- menu_bar.append @edit_menu.create
1105
- @options_menu = OptionsMenu.new(@treeview)
1106
- menu_bar.append @options_menu.create
1107
- menu_bar
1108
- end
1109
-
1110
- # Sets editor status to changed, to indicate that the edited data
1111
- # containts unsaved changes.
1112
- def change
1113
- @changed = true
1114
- display_title
1115
- end
1116
-
1117
- # Sets editor status to unchanged, to indicate that the edited data
1118
- # doesn't containt unsaved changes.
1119
- def unchange
1120
- @changed = false
1121
- display_title
1122
- end
1123
-
1124
- # Puts a new model _model_ into the Gtk::TreeView to be edited.
1125
- def view_new_model(model)
1126
- @treeview.model = model
1127
- @treeview.expanded = true
1128
- @treeview.expand_all
1129
- unchange
1130
- end
1131
-
1132
- # Displays _text_ in the status bar.
1133
- def display_status(text)
1134
- @cid ||= nil
1135
- @status_bar.pop(@cid) if @cid
1136
- @cid = @status_bar.get_context_id('dummy')
1137
- @status_bar.push(@cid, text)
1138
- end
1139
-
1140
- # Opens a dialog, asking, if changes should be saved to a file.
1141
- def ask_save
1142
- if Editor.question_dialog(self,
1143
- "Unsaved changes to JSON model. Save?")
1144
- if @filename
1145
- file_save
1146
- else
1147
- file_save_as
1148
- end
1149
- end
1150
- end
1151
-
1152
- # Quit this editor, that is, leave this editor's main loop.
1153
- def quit
1154
- ask_save if @changed
1155
- if Gtk.main_level > 0
1156
- destroy
1157
- Gtk.main_quit
1158
- end
1159
- nil
1160
- end
1161
-
1162
- # Display the new title according to the editor's current state.
1163
- def display_title
1164
- title = TITLE.dup
1165
- title << ": #@filename" if @filename
1166
- title << " *" if @changed
1167
- self.title = title
1168
- end
1169
-
1170
- # Clear the current model, after asking to save all unsaved changes.
1171
- def clear
1172
- ask_save if @changed
1173
- @filename = nil
1174
- self.view_new_model nil
1175
- end
1176
-
1177
- def check_pretty_printed(json)
1178
- pretty = !!((nl_index = json.index("\n")) && nl_index != json.size - 1)
1179
- @options_menu.pretty_item.active = pretty
1180
- end
1181
- private :check_pretty_printed
1182
-
1183
- # Open the data at the location _uri_, if given. Otherwise open a dialog
1184
- # to ask for the _uri_.
1185
- def location_open(uri = nil)
1186
- uri = ask_for_location unless uri
1187
- uri or return
1188
- ask_save if @changed
1189
- data = load_location(uri) or return
1190
- view_new_model Editor.data2model(data)
1191
- end
1192
-
1193
- # Open the file _filename_ or call the #select_file method to ask for a
1194
- # filename.
1195
- def file_open(filename = nil)
1196
- filename = select_file('Open as a JSON file') unless filename
1197
- data = load_file(filename) or return
1198
- view_new_model Editor.data2model(data)
1199
- end
1200
-
1201
- # Edit the string _json_ in the editor.
1202
- def edit(json)
1203
- if json.respond_to? :read
1204
- json = json.read
1205
- end
1206
- data = parse_json json
1207
- view_new_model Editor.data2model(data)
1208
- end
1209
-
1210
- # Save the current file.
1211
- def file_save
1212
- if @filename
1213
- store_file(@filename)
1214
- else
1215
- file_save_as
1216
- end
1217
- end
1218
-
1219
- # Save the current file as the filename
1220
- def file_save_as
1221
- filename = select_file('Save as a JSON file')
1222
- store_file(filename)
1223
- end
1224
-
1225
- # Store the current JSON document to _path_.
1226
- def store_file(path)
1227
- if path
1228
- data = Editor.model2data(@treeview.model.iter_first)
1229
- File.open(path + '.tmp', 'wb') do |output|
1230
- data or break
1231
- if @options_menu.pretty_item.active?
1232
- output.puts JSON.pretty_generate(data, :max_nesting => false)
1233
- else
1234
- output.write JSON.generate(data, :max_nesting => false)
1235
- end
1236
- end
1237
- File.rename path + '.tmp', path
1238
- @filename = path
1239
- toplevel.display_status("Saved data to '#@filename'.")
1240
- unchange
1241
- end
1242
- rescue SystemCallError => e
1243
- Editor.error_dialog(self, "Failed to store JSON file: #{e}!")
1244
- end
1245
-
1246
- # Load the file named _filename_ into the editor as a JSON document.
1247
- def load_file(filename)
1248
- if filename
1249
- if File.directory?(filename)
1250
- Editor.error_dialog(self, "Try to select a JSON file!")
1251
- nil
1252
- else
1253
- @filename = filename
1254
- if data = read_data(filename)
1255
- toplevel.display_status("Loaded data from '#@filename'.")
1256
- end
1257
- display_title
1258
- data
1259
- end
1260
- end
1261
- end
1262
-
1263
- # Load the data at location _uri_ into the editor as a JSON document.
1264
- def load_location(uri)
1265
- data = read_data(uri) or return
1266
- @filename = nil
1267
- toplevel.display_status("Loaded data from '#{uri}'.")
1268
- display_title
1269
- data
1270
- end
1271
-
1272
- def parse_json(json)
1273
- check_pretty_printed(json)
1274
- if @encoding && !/^utf8$/i.match(@encoding)
1275
- iconverter = Iconv.new('utf8', @encoding)
1276
- json = iconverter.iconv(json)
1277
- end
1278
- JSON::parse(json, :max_nesting => false, :create_additions => false)
1279
- end
1280
- private :parse_json
1281
-
1282
- # Read a JSON document from the file named _filename_, parse it into a
1283
- # ruby data structure, and return the data.
1284
- def read_data(filename)
1285
- open(filename) do |f|
1286
- json = f.read
1287
- return parse_json(json)
1288
- end
1289
- rescue => e
1290
- Editor.error_dialog(self, "Failed to parse JSON file: #{e}!")
1291
- return
1292
- end
1293
-
1294
- # Open a file selecton dialog, displaying _message_, and return the
1295
- # selected filename or nil, if no file was selected.
1296
- def select_file(message)
1297
- filename = nil
1298
- fs = FileSelection.new(message)
1299
- fs.set_modal(true)
1300
- @default_dir = File.join(Dir.pwd, '') unless @default_dir
1301
- fs.set_filename(@default_dir)
1302
- fs.set_transient_for(self)
1303
- fs.signal_connect(:destroy) { Gtk.main_quit }
1304
- fs.ok_button.signal_connect(:clicked) do
1305
- filename = fs.filename
1306
- @default_dir = File.join(File.dirname(filename), '')
1307
- fs.destroy
1308
- Gtk.main_quit
1309
- end
1310
- fs.cancel_button.signal_connect(:clicked) do
1311
- fs.destroy
1312
- Gtk.main_quit
1313
- end
1314
- fs.show_all
1315
- Gtk.main
1316
- filename
1317
- end
1318
-
1319
- # Ask for location URI a to load data from. Returns the URI as a string.
1320
- def ask_for_location
1321
- dialog = Dialog.new(
1322
- "Load data from location...",
1323
- nil, nil,
1324
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
1325
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
1326
- )
1327
- hbox = HBox.new(false, 5)
1328
-
1329
- hbox.pack_start(Label.new("Location:"), false)
1330
- hbox.pack_start(location_input = Entry.new)
1331
- location_input.width_chars = 60
1332
- location_input.text = @location || ''
1333
-
1334
- dialog.vbox.pack_start(hbox, false)
1335
-
1336
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
1337
- dialog.show_all
1338
- dialog.run do |response|
1339
- if response == Dialog::RESPONSE_ACCEPT
1340
- return @location = location_input.text
1341
- end
1342
- end
1343
- return
1344
- ensure
1345
- dialog.destroy if dialog
1346
- end
1347
- end
1348
-
1349
- class << self
1350
- # Starts a JSON Editor. If a block was given, it yields
1351
- # to the JSON::Editor::MainWindow instance.
1352
- def start(encoding = 'utf8') # :yield: window
1353
- Gtk.init
1354
- @window = Editor::MainWindow.new(encoding)
1355
- @window.icon_list = [ Editor.fetch_icon('json') ]
1356
- yield @window if block_given?
1357
- @window.show_all
1358
- Gtk.main
1359
- end
1360
-
1361
- # Edit the string _json_ with encoding _encoding_ in the editor.
1362
- def edit(json, encoding = 'utf8')
1363
- start(encoding) do |window|
1364
- window.edit json
1365
- end
1366
- end
1367
-
1368
- attr_reader :window
1369
- end
1370
- end
1371
- end