bitcoin-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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