bitcoin-ruby 0.0.1

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.
Files changed (140) hide show
  1. data/.gitignore +12 -0
  2. data/COPYING +18 -0
  3. data/Gemfile +4 -0
  4. data/README.rdoc +189 -0
  5. data/Rakefile +104 -0
  6. data/bin/bitcoin_dns_seed +130 -0
  7. data/bin/bitcoin_gui +80 -0
  8. data/bin/bitcoin_node +174 -0
  9. data/bin/bitcoin_shell +12 -0
  10. data/bin/bitcoin_wallet +323 -0
  11. data/bitcoin-ruby.gemspec +27 -0
  12. data/concept-examples/blockchain-pow.rb +151 -0
  13. data/doc/CONFIG.rdoc +66 -0
  14. data/doc/EXAMPLES.rdoc +9 -0
  15. data/doc/NODE.rdoc +35 -0
  16. data/doc/STORAGE.rdoc +21 -0
  17. data/doc/WALLET.rdoc +102 -0
  18. data/examples/balance.rb +60 -0
  19. data/examples/bbe_verify_tx.rb +55 -0
  20. data/examples/connect.rb +36 -0
  21. data/examples/relay_tx.rb +22 -0
  22. data/examples/verify_tx.rb +57 -0
  23. data/lib/bitcoin.rb +370 -0
  24. data/lib/bitcoin/builder.rb +266 -0
  25. data/lib/bitcoin/config.rb +56 -0
  26. data/lib/bitcoin/connection.rb +126 -0
  27. data/lib/bitcoin/ffi/openssl.rb +121 -0
  28. data/lib/bitcoin/gui/addr_view.rb +42 -0
  29. data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
  30. data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
  31. data/lib/bitcoin/gui/conn_view.rb +36 -0
  32. data/lib/bitcoin/gui/connection.rb +68 -0
  33. data/lib/bitcoin/gui/em_gtk.rb +28 -0
  34. data/lib/bitcoin/gui/gui.builder +1643 -0
  35. data/lib/bitcoin/gui/gui.rb +290 -0
  36. data/lib/bitcoin/gui/helpers.rb +113 -0
  37. data/lib/bitcoin/gui/tree_view.rb +82 -0
  38. data/lib/bitcoin/gui/tx_view.rb +67 -0
  39. data/lib/bitcoin/key.rb +125 -0
  40. data/lib/bitcoin/logger.rb +65 -0
  41. data/lib/bitcoin/network/command_client.rb +93 -0
  42. data/lib/bitcoin/network/command_handler.rb +179 -0
  43. data/lib/bitcoin/network/connection_handler.rb +274 -0
  44. data/lib/bitcoin/network/node.rb +399 -0
  45. data/lib/bitcoin/protocol.rb +140 -0
  46. data/lib/bitcoin/protocol/address.rb +48 -0
  47. data/lib/bitcoin/protocol/alert.rb +47 -0
  48. data/lib/bitcoin/protocol/block.rb +154 -0
  49. data/lib/bitcoin/protocol/handler.rb +38 -0
  50. data/lib/bitcoin/protocol/parser.rb +148 -0
  51. data/lib/bitcoin/protocol/tx.rb +205 -0
  52. data/lib/bitcoin/protocol/txin.rb +97 -0
  53. data/lib/bitcoin/protocol/txout.rb +73 -0
  54. data/lib/bitcoin/protocol/version.rb +70 -0
  55. data/lib/bitcoin/script.rb +634 -0
  56. data/lib/bitcoin/storage/dummy.rb +164 -0
  57. data/lib/bitcoin/storage/models.rb +133 -0
  58. data/lib/bitcoin/storage/sequel.rb +335 -0
  59. data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
  60. data/lib/bitcoin/storage/storage.rb +243 -0
  61. data/lib/bitcoin/version.rb +3 -0
  62. data/lib/bitcoin/wallet/coinselector.rb +30 -0
  63. data/lib/bitcoin/wallet/keygenerator.rb +75 -0
  64. data/lib/bitcoin/wallet/keystore.rb +203 -0
  65. data/lib/bitcoin/wallet/txdp.rb +116 -0
  66. data/lib/bitcoin/wallet/wallet.rb +243 -0
  67. data/spec/bitcoin/bitcoin_spec.rb +472 -0
  68. data/spec/bitcoin/builder_spec.rb +90 -0
  69. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
  70. data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
  71. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
  72. data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
  73. data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
  74. data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
  75. data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
  76. data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
  77. data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
  78. data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
  79. data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
  80. data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
  81. data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
  82. data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
  83. data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
  84. data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
  85. data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
  86. data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
  87. data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
  88. data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
  89. data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
  90. data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
  91. data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
  92. data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
  93. data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
  94. data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
  95. data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
  96. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
  97. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
  98. data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
  99. data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
  100. data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
  101. data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
  102. data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
  103. data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
  104. data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
  105. data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
  106. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
  107. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
  108. data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
  109. data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
  110. data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
  111. data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
  112. data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
  113. data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
  114. data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
  115. data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
  116. data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
  117. data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
  118. data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
  119. data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
  120. data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
  121. data/spec/bitcoin/key_spec.rb +123 -0
  122. data/spec/bitcoin/network_spec.rb +48 -0
  123. data/spec/bitcoin/protocol/addr_spec.rb +68 -0
  124. data/spec/bitcoin/protocol/alert_spec.rb +20 -0
  125. data/spec/bitcoin/protocol/block_spec.rb +101 -0
  126. data/spec/bitcoin/protocol/inv_spec.rb +124 -0
  127. data/spec/bitcoin/protocol/ping_spec.rb +49 -0
  128. data/spec/bitcoin/protocol/tx_spec.rb +226 -0
  129. data/spec/bitcoin/protocol/version_spec.rb +77 -0
  130. data/spec/bitcoin/reorg_spec.rb +129 -0
  131. data/spec/bitcoin/script/opcodes_spec.rb +417 -0
  132. data/spec/bitcoin/script/script_spec.rb +246 -0
  133. data/spec/bitcoin/spec_helper.rb +36 -0
  134. data/spec/bitcoin/storage_spec.rb +229 -0
  135. data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
  136. data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
  137. data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
  138. data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
  139. data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
  140. metadata +295 -0
@@ -0,0 +1,290 @@
1
+ require_relative "em_gtk.rb"
2
+ require_relative "helpers.rb"
3
+ require_relative "tree_view.rb"
4
+ require_relative "addr_view.rb"
5
+ require_relative "tx_view.rb"
6
+ require_relative "conn_view.rb"
7
+
8
+ Gtk.init
9
+
10
+ module Bitcoin::Gui
11
+
12
+ class Gui
13
+
14
+ include Helpers
15
+
16
+ attr_reader :builder, :wallet, :storage
17
+ attr_accessor :node, :addr_view, :conn_view
18
+
19
+ def initialize storage, wallet_file = nil
20
+ @storage = storage
21
+ @node = nil
22
+ build
23
+ @addr_view = AddrView.new(self)
24
+ @tx_view = TxView.new(self, :tx_view)
25
+ @conn_view = ConnView.new(self)
26
+ open_wallet(wallet_file) if wallet_file
27
+ main_window.show_all
28
+ # notebook.next_page
29
+ statusicon.tooltip_text = "Bitcoin-Ruby GUI"
30
+ GObject.signal_connect(statusicon, "activate") do
31
+ main_window.visible ? main_window.hide : main_window.show
32
+ end
33
+ GObject.signal_connect(statusicon, "popup-menu") do
34
+ popup_menu.popup_for_device(nil, nil, nil, ->(*a) {}, nil, nil, 0, 0)
35
+ end
36
+ end
37
+
38
+ def log
39
+ @log ||= Bitcoin::Logger.create(:gui)
40
+ end
41
+
42
+ def build
43
+ @builder = Gtk::Builder.new
44
+ @builder.add_from_file(File.join(File.dirname(__FILE__), "gui.builder"))
45
+ @builder.connect_signals_full(->(builder, widget, signal, handler, _, _, gui) do
46
+ GObject.signal_connect(widget, signal) { gui.send(handler) }
47
+ end, self)
48
+
49
+ {
50
+ "<Control>n" => :file_new,
51
+ "<Control>o" => :file_open,
52
+ "<Control>q" => :file_quit,
53
+ "<Control>c" => :edit_copy,
54
+ "<Control>v" => :edit_paste,
55
+ "<Control>p" => :edit_preferences,
56
+ "<Control>h" => :help_about,
57
+ }.each do |binding, action|
58
+ send("menu_#{action}").add_accelerator("activate", accelgroup1,
59
+ *Gtk.accelerator_parse(binding), 0)
60
+ end
61
+ end
62
+
63
+ def update_wallet_views
64
+ return unless @wallet
65
+ EM.defer do
66
+ @addr_view.update(@wallet.keystore.keys)#.select{|k| k[:mine] == true})
67
+ @tx_view.update(@wallet.get_txouts(true))
68
+ unconfirmed = check_unconfirmed.active
69
+ wallet_text = "wallet: #{@wallet.keystore.config[:file]} | " +
70
+ "addresses: #{@wallet.addrs.size} | " +
71
+ "balance: #{"%.8f" % (@wallet.get_balance(unconfirmed) / 1e8)}"
72
+ status_wallet.push 0, wallet_text
73
+ end
74
+ end
75
+
76
+ def on_check_unconfirmed_toggled
77
+ update_wallet_views
78
+ end
79
+
80
+ def on_preferences
81
+ dialog(:preferences, :setup => ->(d) {
82
+ config = Bitcoin::Config.load({}, :wallet)
83
+ model = preferences_box_network.get_model
84
+ row = model.append
85
+ model.set_value(row, 0, "foo")
86
+ preferences_box_network.set_model model
87
+ # preferences_entry_network.text = config[:network]
88
+ }) do |*a|
89
+ p a
90
+ end
91
+ end
92
+
93
+ def on_copy_addr
94
+ addrs = []
95
+ valid, i = @addr_view.model.get_iter_first
96
+ while valid
97
+ if @addr_view.view.selection.iter_is_selected(i)
98
+ a = @addr_view.model.get_value(i, 0).get_string
99
+ addrs << a
100
+ end
101
+ valid = @addr_view.model.iter_next(i.to_ptr)
102
+ end
103
+
104
+ return unless addrs.any?
105
+ text = addrs.join(", ")
106
+ c = Gtk::Clipboard.get(Gdk::Atom.intern("PRIMARY", false))
107
+ c.set_text text, text.size
108
+ c = Gtk::Clipboard.get(Gdk::Atom.intern("CLIPBOARD", false))
109
+ c.set_text text, text.size
110
+ end
111
+
112
+ def on_paste_addr
113
+ c = Gtk::Clipboard.get(Gdk::Atom.intern("PRIMARY", false))
114
+ c.request_text ->(clip, text, data) {
115
+ on_new_addr(text) if Bitcoin.valid_address?(text)
116
+ }, self
117
+ c = Gtk::Clipboard.get(Gdk::Atom.intern("CLIPBOARD", false))
118
+ c.request_text ->(clip, text, data) {
119
+ on_new_addr(text) if Bitcoin.valid_address?(text)
120
+ }, self
121
+ end
122
+
123
+ def on_new_addr addr = nil
124
+ dialog(:new_addr, setup: ->(d) {
125
+ new_addr_check_addr.active = addr ? true : false
126
+ new_addr_check_pubkey.active = false
127
+ new_addr_check_mine.active = addr ? false : true
128
+ new_addr_entry_label.text = ""
129
+ new_addr_entry_addr.text = addr ? addr : ""
130
+ new_addr_entry_pubkey.text = ""
131
+ new_addr_entry_addr.hide unless addr
132
+ new_addr_entry_pubkey.hide
133
+ GObject.signal_connect(new_addr_check_addr, "toggled") do
134
+ new_addr_check_addr.active ? new_addr_entry_addr.show :
135
+ new_addr_entry_addr.hide
136
+ end
137
+ GObject.signal_connect(new_addr_check_pubkey, "toggled") do
138
+ new_addr_check_pubkey.active ? new_addr_entry_pubkey.show :
139
+ new_addr_entry_pubkey.hide
140
+ end
141
+ }) do |*a|
142
+ begin
143
+ label = new_addr_entry_label.text
144
+ set_address = new_addr_check_addr.active
145
+ set_pubkey = new_addr_check_pubkey.active
146
+ is_mine = new_addr_check_mine.active
147
+ key = {:label => label}
148
+ if set_address
149
+ addr = new_addr_entry_addr.text
150
+ raise "Address #{addr} invalid" unless Bitcoin.valid_address?(addr)
151
+ key[:addr] = addr
152
+ end
153
+ if set_pubkey
154
+ k = Bitcoin::Key.new(nil, new_addr_entry_pubkey.text)
155
+ key[:key] = k
156
+ key[:addr] = k.addr
157
+ end
158
+ if !set_address && !set_pubkey
159
+ key[:key] = Bitcoin::Key.generate
160
+ key[:addr] = key[:key].addr
161
+ end
162
+ key[:mine] = is_mine
163
+ @wallet.add_key(key)
164
+ update_wallet_views
165
+ rescue
166
+ message(:error, "Error adding key", $!.message, [:ok]) do
167
+ new_addr_dialog.show
168
+ end
169
+ end
170
+ update_addr_store
171
+ end
172
+ end
173
+
174
+ def on_new_tx
175
+ dialog(:new_tx, :setup => ->(d) {
176
+ new_tx_entry_address.text = ""
177
+ new_tx_entry_amount.text = ""
178
+ model = Gtk::ListStore.new([GObject::TYPE_STRING, GObject::TYPE_STRING])
179
+ @wallet.keystore.keys.each do |key|
180
+ row = model.append
181
+ model.set_value(row, 0, key[:label] || "")
182
+ model.set_value(row, 1, key[:addr])
183
+ end
184
+ renderer = Gtk::CellRendererText.new
185
+ comp = Gtk::EntryCompletion.new_with_area(area)
186
+ comp.text_column = 1
187
+ comp.minimum_key_length = 1
188
+ comp.set_match_func(->(comp, text, iter, _) {
189
+ label = comp.get_model.value(iter, 0).get_string
190
+ addr = comp.get_model.value(iter, 1).get_string
191
+ !!(label =~ /#{text}/ || addr =~ /#{text}/)
192
+ }, nil, nil)
193
+ comp.area.pack_start renderer, false, false, false
194
+ comp.area.orientation = :vertical
195
+ comp.set_model model
196
+ renderer.set_padding 10, 0
197
+ renderer.foreground = "blue"
198
+ comp.set_cell_data_func(renderer, ->(layout, renderer, model, iter, data) {
199
+ renderer.text = model.get_value(iter, 0).get_string
200
+ }, nil, nil)
201
+ new_tx_entry_address.set_completion comp
202
+ # GObject.signal_connect(comp, "match-selected") do |comp, _, iter, _|
203
+ # addr = comp.get_model.get_value(iter, 1).get_string
204
+ # new_tx_entry_address.text = addr
205
+ # true
206
+ # end
207
+ }) do |dialog|
208
+
209
+ address = new_tx_entry_address.text
210
+ amount = new_tx_entry_amount.text
211
+ unless Bitcoin.valid_address?(address)
212
+ message(:error, "Invalid Address",
213
+ "Address #{address} is not a valid bitcoin address.", [:ok]) do
214
+ new_tx_dialog.show
215
+ end
216
+ next
217
+ end
218
+ unless amount =~ /[0-9]*\.[0-9]*/
219
+ message(:error, "Invalid Amount",
220
+ "Amount #{amount} can not be parsed. Please use \"0.0\" form.", [:ok]) do
221
+ new_tx_dialog.show
222
+ end
223
+ next
224
+ end
225
+ value = (amount.to_f * 1e8).to_i
226
+ unless value <= @wallet.get_balance
227
+ message(:error, "Insufficient Balance",
228
+ "Balance #{@wallet.get_balance} is not sufficient to spend #{value}.", [:ok]) do
229
+ dialog.show
230
+ end
231
+ next
232
+ end
233
+ message(:question, "Really send transaction?",
234
+ "Do you really want to send #{format_value value} to #{address}?", [:no, :yes]) do
235
+ tx = @wallet.new_tx([[:address, *[address], value]], 0.00)
236
+ # puts tx.to_json
237
+ if @node.request(:relay_tx, tx)
238
+ puts "Transaction #{tx} relayed."
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ def on_new_wallet
245
+ dialog(:wallet_save, :filters => :wallet, :callbacks => {
246
+ "update-preview" => method(:wallet_preview)}) do
247
+ file = wallet_save_dialog.filename.read_string
248
+ open_wallet(file) unless File.file?(file)
249
+ end
250
+ end
251
+
252
+ def on_open_wallet
253
+ dialog(:wallet_open, :filters => :wallet, :callbacks => {
254
+ "update-preview" => method(:wallet_preview)}) do
255
+ open_wallet(wallet_open_dialog.filename.read_string)
256
+ end
257
+ end
258
+
259
+ def on_about
260
+ dialog(:about)
261
+ end
262
+
263
+ def on_exit
264
+ puts "bye"
265
+ Gtk.main_quit
266
+ EM.stop
267
+ end
268
+
269
+ def open_wallet(file)
270
+ keystore = Bitcoin::Wallet::SimpleKeyStore.new(:file => file)
271
+ @wallet = Bitcoin::Wallet::Wallet.new(@storage, keystore,
272
+ Bitcoin::Wallet::SimpleCoinSelector)
273
+ @wallet.on_tx do |type, tx|
274
+ puts "#{type} transaction: #{tx.hash}"
275
+ update_wallet_views
276
+ value = tx.out.select {|out| (@wallet.addrs & out.get_addresses).any? }
277
+ .map(&:value).inject(:+)
278
+ title = "#{type.to_s.capitalize} transaction"
279
+ message = "#{tx.hash}\nValue: #{format_value(value)}"
280
+ Notify.notify title, message
281
+ end
282
+ update_wallet_views
283
+ rescue
284
+ message(:error, "Error loading wallet", $!.message, [:ok])
285
+ p $!
286
+ puts *$@
287
+ end
288
+
289
+ end
290
+ end
@@ -0,0 +1,113 @@
1
+ module Bitcoin::Gui::Helpers
2
+
3
+ def display_tx tx_hash
4
+ tx = @storage.get_tx(tx_hash)
5
+ dialog(:tx, setup: ->(d) {
6
+ tx_label_hash.text = tx.hash
7
+ tx_label_value.text = format_value(tx.out.map(&:value).inject(:+))
8
+ tx_label_confirmations.text = tx.confirmations.to_s
9
+ txin_view = Bitcoin::Gui::TxInView.new(self, :tx_txin_view)
10
+ # txin_view.update(tx.in)
11
+ })
12
+ end
13
+
14
+ def wallet_preview(dialog, *args)
15
+ filename = dialog.preview_filename
16
+ file = filename.read_string rescue nil
17
+ if file && File.file?(file)
18
+ keystore = Bitcoin::Wallet::SimpleKeyStore.new(:file => file)
19
+ wallet = Bitcoin::Wallet::Wallet.new(@storage, keystore,
20
+ Bitcoin::Wallet::SimpleCoinSelector)
21
+ preview = Gtk::Label.new "Keys: #{wallet.addrs.size}\n" +
22
+ "Balance:\n%.8f" % (wallet.get_balance / 1e8)
23
+ end
24
+ dialog.preview_widget = preview
25
+ end
26
+
27
+ def add_wallet_filters dialog
28
+ filter = Gtk::FileFilter.new
29
+ filter.name = "Wallet Files"
30
+ filter.add_pattern("*.json")
31
+ dialog.add_filter filter
32
+
33
+ filter = Gtk::FileFilter.new
34
+ filter.name = "All Files"
35
+ filter.add_pattern("*")
36
+ dialog.add_filter filter
37
+ end
38
+
39
+ def dialog name, opts = {}
40
+ @dialog_cb_ids ||= {}
41
+ ids = @dialog_cb_ids[name] || []
42
+ dialog = send("#{name}_dialog")
43
+ send("add_#{opts[:filters]}_filters", dialog) if opts[:filters]
44
+ opts[:setup].call(dialog) if opts[:setup]
45
+ while id = ids.shift
46
+ GObject.signal_handler_disconnect(dialog, id)
47
+ end
48
+ ids << GObject.signal_connect(dialog, "response") do |dialog, response, *data|
49
+ yield(dialog, *data) if response > 0
50
+ dialog.hide
51
+ end
52
+ if dialog.is_a?(Gtk::FileChooserDialog)
53
+ ids << GObject.signal_connect(dialog, "file-activated") do |dialog, *data|
54
+ yield(dialog, *data)
55
+ dialog.hide
56
+ end
57
+ end
58
+ (opts[:callbacks] || {}).each do |name, block|
59
+ ids << GObject.signal_connect(dialog, name) {|*a| block.call(*a) }
60
+ end
61
+ dialog.show
62
+ @dialog_cb_ids[name] = ids
63
+ rescue
64
+ p $!
65
+ puts *$@
66
+ end
67
+
68
+ def message(type, title, text, buttons)
69
+ dialog(:message, :setup => ->(dialog){
70
+ dialog.message_type = Gtk::MessageType.find(type.to_sym)
71
+ dialog.text = title
72
+ dialog.secondary_text = text
73
+ [:yes, :no, :ok].each do |n|
74
+ b = send("message_dialog_button_#{n}")
75
+ buttons.include?(n) ? b.show : b.hide
76
+ end
77
+ }) do |dialog|
78
+ yield(dialog) if block_given?
79
+ dialog.show_all
80
+ dialog.hide
81
+ end
82
+ rescue
83
+ p $!
84
+ end
85
+
86
+ def method_missing name, *args
87
+ @builder.get_object(name.to_s) rescue super(name, *args)
88
+ end
89
+
90
+ def format_value val
91
+ "%.8f" % (val / 1e8)
92
+ end
93
+
94
+ def format_address address, label = nil
95
+ "#{label} (#{address})"
96
+ end
97
+
98
+ def format_version ver
99
+ ver.insert(-3, '.') if ver.size > 2
100
+ ver.insert(-6, '.') if ver.size > 5
101
+ ver.insert(0, "0.") if ver.size <= 7
102
+ ver
103
+ end
104
+
105
+ def format_uptime started
106
+ uptime = Time.now.to_i - started
107
+ mm, ss = uptime.divmod(60) #=> [4515, 21]
108
+ hh, mm = mm.divmod(60) #=> [75, 15]
109
+ dd, hh = hh.divmod(24) #=> [3, 3]
110
+ "%02d:%02d:%02d:%02d" % [dd, hh, mm, ss]
111
+ end
112
+
113
+ end
@@ -0,0 +1,82 @@
1
+ module Bitcoin::Gui
2
+ class TreeView
3
+
4
+ include Helpers
5
+
6
+ attr_accessor :model, :view
7
+
8
+ def initialize gui, view_name, columns
9
+ @gui = gui
10
+ @view = @gui.builder.get_object(view_name.to_s)
11
+ @model = Gtk::TreeStore.new(columns.map{|c| c[0] })
12
+ @view.set_model @model
13
+
14
+ columns.each_with_index do |cdef, i|
15
+ type, label, cb = *cdef
16
+ next unless label
17
+ case type
18
+ when GObject::TYPE_BOOLEAN
19
+ renderer = Gtk::CellRendererToggle.new
20
+ col = tree_view_col(renderer, label, "active", i) do |*args|
21
+ method(cb).call(@model, i, args[1], args[3]) if cb
22
+ end
23
+ when GObject::TYPE_STRING, GObject::TYPE_INT
24
+ renderer = Gtk::CellRendererText.new
25
+ col = tree_view_col(renderer, label, "text", i) do |*args|
26
+ method(cb).call(@model, i, args[1], args[3]) if cb
27
+ end
28
+ end
29
+ @view.append_column(col)
30
+ end
31
+ end
32
+
33
+ def embed name
34
+ parent = @gui.builder.get_object(name.to_s).parent
35
+ parent.remove(parent.get_child)
36
+ parent.add(@view)
37
+ end
38
+
39
+ def tree_view_col renderer, title, key, val, &block
40
+ col = Gtk::TreeViewColumn.new
41
+ col.pack_start renderer, true
42
+ col.add_attribute renderer, key, val
43
+ col.set_title title
44
+ col.set_cell_data_func(renderer, block, nil, nil)
45
+ col
46
+ end
47
+
48
+ def format_address_col model, i, renderer, iter
49
+ address = model.get_value(iter, i).get_string
50
+ label = model.get_value(iter, i+1).get_string
51
+ renderer.text = format_address(address, label)
52
+ end
53
+
54
+ def format_value_col model, i, renderer, iter
55
+ val = model.get_value(iter, i).get_string.to_i
56
+ renderer.text = format_value(val)
57
+ if val > 0
58
+ renderer.foreground = "darkgreen"
59
+ elsif val < 0
60
+ renderer.foreground = "red"
61
+ else
62
+ renderer.foreground = "black"
63
+ end
64
+ end
65
+
66
+ def format_version_col model, i, renderer, iter
67
+ ver = model.get_value(iter, i).get_int.to_s
68
+ renderer.text = format_version(ver)
69
+ end
70
+
71
+ def format_uptime_col model, i, renderer, iter
72
+ started = model.get_value(iter, i).get_int
73
+ renderer.text = format_uptime(started)
74
+ end
75
+
76
+ def format_bool_col model, i, renderer, iter
77
+ active = model.get_value(iter, i).get_boolean
78
+ renderer.set_active active
79
+ end
80
+
81
+ end
82
+ end