ektoplayer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +49 -0
- data/bin/ektoplayer +7 -0
- data/lib/ektoplayer.rb +10 -0
- data/lib/ektoplayer/application.rb +148 -0
- data/lib/ektoplayer/bindings.rb +230 -0
- data/lib/ektoplayer/browsepage.rb +138 -0
- data/lib/ektoplayer/client.rb +18 -0
- data/lib/ektoplayer/common.rb +91 -0
- data/lib/ektoplayer/config.rb +247 -0
- data/lib/ektoplayer/controllers/browser.rb +47 -0
- data/lib/ektoplayer/controllers/controller.rb +9 -0
- data/lib/ektoplayer/controllers/help.rb +21 -0
- data/lib/ektoplayer/controllers/info.rb +22 -0
- data/lib/ektoplayer/controllers/mainwindow.rb +40 -0
- data/lib/ektoplayer/controllers/playlist.rb +60 -0
- data/lib/ektoplayer/database.rb +199 -0
- data/lib/ektoplayer/events.rb +56 -0
- data/lib/ektoplayer/models/browser.rb +127 -0
- data/lib/ektoplayer/models/database.rb +49 -0
- data/lib/ektoplayer/models/model.rb +15 -0
- data/lib/ektoplayer/models/player.rb +28 -0
- data/lib/ektoplayer/models/playlist.rb +72 -0
- data/lib/ektoplayer/models/search.rb +42 -0
- data/lib/ektoplayer/models/trackloader.rb +17 -0
- data/lib/ektoplayer/mp3player.rb +151 -0
- data/lib/ektoplayer/operations/browser.rb +19 -0
- data/lib/ektoplayer/operations/operations.rb +26 -0
- data/lib/ektoplayer/operations/player.rb +11 -0
- data/lib/ektoplayer/operations/playlist.rb +67 -0
- data/lib/ektoplayer/theme.rb +102 -0
- data/lib/ektoplayer/trackloader.rb +146 -0
- data/lib/ektoplayer/ui.rb +404 -0
- data/lib/ektoplayer/ui/colors.rb +105 -0
- data/lib/ektoplayer/ui/widgets.rb +195 -0
- data/lib/ektoplayer/ui/widgets/container.rb +125 -0
- data/lib/ektoplayer/ui/widgets/labelwidget.rb +43 -0
- data/lib/ektoplayer/ui/widgets/listwidget.rb +332 -0
- data/lib/ektoplayer/ui/widgets/tabbedcontainer.rb +110 -0
- data/lib/ektoplayer/updater.rb +77 -0
- data/lib/ektoplayer/views/browser.rb +25 -0
- data/lib/ektoplayer/views/help.rb +46 -0
- data/lib/ektoplayer/views/info.rb +208 -0
- data/lib/ektoplayer/views/mainwindow.rb +64 -0
- data/lib/ektoplayer/views/playinginfo.rb +135 -0
- data/lib/ektoplayer/views/playlist.rb +39 -0
- data/lib/ektoplayer/views/progressbar.rb +51 -0
- data/lib/ektoplayer/views/splash.rb +99 -0
- data/lib/ektoplayer/views/trackrenderer.rb +137 -0
- data/lib/ektoplayer/views/volumemeter.rb +74 -0
- metadata +164 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6f7e3bffc50021abf9f3c8384db4c81952abb036
|
4
|
+
data.tar.gz: a7d7612d2175422b4e1f59d01e6c3992915bbb6c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 788048c6464ae80c0d1d1ef2d0a85a307787ab8d8b7116cb4f2bf934223087238d5eb2af9ce48973899a248daee415aca40a23cf3a63c80d2554ab5a8fd7c8a7
|
7
|
+
data.tar.gz: 16bb67c65cd1b0b6c530d4f6e74f36b32d3a727e44125c8ba29c1b5e1d467362ec36d5b81ceb7a6ce5aa223d4cedd6accd8ea354b2f871d765fd581c1b82cd1a
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Ektoplayer
|
2
|
+
|
3
|
+
Ektoplayer is a console audio player for [ektplazm.com](http://www.ektoplazm.com).
|
4
|
+
|
5
|
+
![Screenshot 2017-03-16](http://pixelbanane.de/yafu/118231024/ekto1.png)
|
6
|
+
![Screenshot 2017-03-16](http://pixelbanane.de/yafu/324630271/ekto2.png)
|
7
|
+
|
8
|
+
## Requirements
|
9
|
+
|
10
|
+
* Ruby (1.9)
|
11
|
+
* Portaudio (19)
|
12
|
+
* Mpg123 (1.14)
|
13
|
+
* Sqlite3
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Assuming you have Ruby/Rubygems installed, you need portaudio and mpg123 as
|
18
|
+
library to compile the native extensions.
|
19
|
+
|
20
|
+
### Arch Linux
|
21
|
+
|
22
|
+
pacman -S portaudio mpg123 ncurses ruby sqlite
|
23
|
+
gem install ektoplayer
|
24
|
+
|
25
|
+
### Debian / Ubuntu (not yet tested)
|
26
|
+
|
27
|
+
apt-get install portaudio19-dev libmpg123-dev libncurses-dev ruby1.9.1-dev sqlite-??
|
28
|
+
gem install ektoplayer
|
29
|
+
|
30
|
+
## Features
|
31
|
+
|
32
|
+
* Listen to ektoplazm tracks
|
33
|
+
* Download whole albums
|
34
|
+
* Browse database by tags
|
35
|
+
* Vi keybindings (`hjkl`, `^d`, `^u`, `/`, `?`, `n`, `N`, `:`, ...)
|
36
|
+
* Mouse is supported
|
37
|
+
* Supports 256/16/mono colors
|
38
|
+
* Local sound file cache and download archive
|
39
|
+
* Highly configurable
|
40
|
+
|
41
|
+
## Authors
|
42
|
+
|
43
|
+
* [Benjamin Abendroth](https://github.com/braph)
|
44
|
+
|
45
|
+
## See also
|
46
|
+
|
47
|
+
* Ektoplayer was inspired by [Soundcloud2000](https://github.com/grobie/soundcloud2000) and [ncmpcpp](https://github.com/arybczak/ncmpcpp)
|
48
|
+
* It uses [Audite](https://github.com/georgi/audite) as playback engine and [Nokogiri](http://www.nokogiri.org/) for parsing HTML
|
49
|
+
|
data/bin/ektoplayer
ADDED
data/lib/ektoplayer.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
{ '.' => %w(config theme bindings client common ui),
|
5
|
+
'views' => %w(mainwindow),
|
6
|
+
'models' => %w(player browser playlist database trackloader),
|
7
|
+
'operations' => %w(operations player browser playlist),
|
8
|
+
'controllers' => %w(mainwindow browser playlist info help)
|
9
|
+
}.each { |d,files| files.each { |f| require_relative "#{d}/#{f}" } }
|
10
|
+
|
11
|
+
module Ektoplayer
|
12
|
+
class Application
|
13
|
+
VERSION = '0.1'.freeze
|
14
|
+
GITHUB_URL = 'https://github.com/braph/ektoplayer'.freeze
|
15
|
+
EKTOPLAZM_URL = 'http://www.ektoplazm.com'.freeze
|
16
|
+
|
17
|
+
CONFIG_DIR = File.join(Dir.home, '.config', 'ektoplayer').freeze
|
18
|
+
CONFIG_FILE = File.join(CONFIG_DIR, 'ektoplayer.rc').freeze
|
19
|
+
|
20
|
+
def self.log(from, *msgs)
|
21
|
+
func = caller[0][/`.*'/][1..-2]
|
22
|
+
from = from.class unless from.is_a?String
|
23
|
+
$stderr.puts("#{DateTime.now.rfc3339} #{from}.#{func}: " + msgs.join(' '))
|
24
|
+
|
25
|
+
if e = msgs.select { |m| m.kind_of?Exception }[0]
|
26
|
+
$stderr.puts "#{e.backtrace.first}: #{e.message} (#{e.class})", e.backtrace.drop(1).map{|s| "\t#{s}"}
|
27
|
+
end
|
28
|
+
$stderr.flush
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.open_log(file)
|
32
|
+
$stderr.reopen(file, ?a)
|
33
|
+
$stderr.sync=(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
#Thread.abort_on_exception=(true)
|
38
|
+
|
39
|
+
# make each configuration object globally accessible as a singleton
|
40
|
+
[Config, Bindings, Theme].each { |c| Common::mksingleton(c) }
|
41
|
+
|
42
|
+
if File.file? Config::CONFIG_FILE
|
43
|
+
Config.parse(Config::CONFIG_FILE, Bindings, Theme) rescue (
|
44
|
+
fail "Config: #{$!}"
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
FileUtils::mkdir_p Config::CONFIG_DIR rescue (
|
49
|
+
fail "Could not create config dir: #{$!}"
|
50
|
+
)
|
51
|
+
|
52
|
+
Application.open_log(Config[:log_file])
|
53
|
+
|
54
|
+
if Config[:use_cache]
|
55
|
+
unless File.directory? Config[:cache_dir]
|
56
|
+
FileUtils::mkdir Config[:cache_dir] rescue (
|
57
|
+
fail "Could not create cache dir: #{$!}"
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
[:temp_dir, :download_dir, :archive_dir].each do |key|
|
63
|
+
unless File.directory? Config[key]
|
64
|
+
FileUtils::mkdir Config[key] rescue (
|
65
|
+
fail "Could not create #{key}: #{$!}"
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
UI::Canvas.run do
|
71
|
+
if Config[:use_colors] == :auto
|
72
|
+
Theme.use_colors(ENV['TERM'] =~ /256/ ? 256 : 8)
|
73
|
+
else
|
74
|
+
Theme.use_colors(Config[:use_colors])
|
75
|
+
end
|
76
|
+
|
77
|
+
client = Client.new
|
78
|
+
|
79
|
+
# ... models ...
|
80
|
+
player = Models::Player.new(client)
|
81
|
+
browser = Models::Browser.new(client)
|
82
|
+
playlist = Models::Playlist.new
|
83
|
+
database = Models::Database.new(client)
|
84
|
+
trackloader = Models::Trackloader.new(client)
|
85
|
+
|
86
|
+
# ... operations ...
|
87
|
+
operations = Operations::Operations.new
|
88
|
+
operations.register(:quit, &method(:exit))
|
89
|
+
operations.register(:reload, &browser.method(:reload))
|
90
|
+
operations.register(:update, &database.method(:update))
|
91
|
+
operations.register(:refresh) { UI::Canvas.on_winch; UI::Canvas.update_screen(true) }
|
92
|
+
Operations::Player.new(operations, player)
|
93
|
+
Operations::Browser.new(operations, browser, playlist)
|
94
|
+
Operations::Playlist.new(operations, playlist, player, trackloader)
|
95
|
+
|
96
|
+
# ... views ...
|
97
|
+
main_w = UI::Canvas.sub(Views::MainWindow)
|
98
|
+
|
99
|
+
# next operations may take some time, espacially the ones
|
100
|
+
# using the database (browser), so we put this inside a thread
|
101
|
+
Thread.new do
|
102
|
+
# ... controllers ...
|
103
|
+
view_ops = Operations::Operations.new
|
104
|
+
Controllers::MainWindow.new(main_w, view_ops)
|
105
|
+
Controllers::Browser.new(main_w.browser, browser, view_ops, operations)
|
106
|
+
Controllers::Playlist.new(main_w.playlist, playlist, view_ops, operations)
|
107
|
+
Controllers::Help.new(main_w.help, view_ops)
|
108
|
+
Controllers::Info.new(main_w.info, playlist, trackloader, database, view_ops)
|
109
|
+
main_w.progressbar.attach(player)
|
110
|
+
main_w.volumemeter.attach(player)
|
111
|
+
main_w.playinginfo.attach(playlist, player)
|
112
|
+
|
113
|
+
# ... events ...
|
114
|
+
database.events.on(:update_finished, &browser.method(:reload ))
|
115
|
+
player.events.on(:stop) do |reason|
|
116
|
+
operations.send(:'playlist.play_next') if reason == :track_completed
|
117
|
+
end
|
118
|
+
|
119
|
+
# ... bindings ...
|
120
|
+
Bindings.bind_view(:global, main_w, view_ops, operations)
|
121
|
+
%w(splash playlist browser info help).each do |w|
|
122
|
+
Bindings.bind_view(w, main_w.send(w), view_ops, operations)
|
123
|
+
end
|
124
|
+
|
125
|
+
player.stop
|
126
|
+
|
127
|
+
# If database is empty, start an initial update
|
128
|
+
if browser.tracks(0).size < 1
|
129
|
+
operations.send(:update)
|
130
|
+
elsif (c = Config[:small_update_pages]) > 0
|
131
|
+
operations.send(:update, pages: c)
|
132
|
+
end
|
133
|
+
|
134
|
+
if (n = Config[:playlist_load_newest]) > 0
|
135
|
+
r = client.database.select(order_by: 'date', limit: n)
|
136
|
+
playlist.add(*r)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
UI::Canvas.update_screen
|
141
|
+
UI::Input.start_loop
|
142
|
+
end
|
143
|
+
rescue
|
144
|
+
puts "Error: #{e}"
|
145
|
+
Application.log(self, $!)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'curses'
|
3
|
+
|
4
|
+
module Ektoplayer
|
5
|
+
# Keybinding storage.
|
6
|
+
#
|
7
|
+
# Provides access for changing keybinds (with collision detection):
|
8
|
+
# bind(), unbind()
|
9
|
+
#
|
10
|
+
# Provides binding these keys to widgets
|
11
|
+
# bind_section_to_widget(), bind_to_widget()
|
12
|
+
#
|
13
|
+
class Bindings
|
14
|
+
attr_reader :bindings, :commands
|
15
|
+
|
16
|
+
def register(command, description)
|
17
|
+
@commands[command.to_sym] = description.freeze
|
18
|
+
end
|
19
|
+
alias :reg :register
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@commands = {}
|
23
|
+
|
24
|
+
reg 'quit', 'Quit the program'
|
25
|
+
reg 'refresh', 'Refresh the screen'
|
26
|
+
reg 'reload', 'Apply database changes to browser'
|
27
|
+
reg 'update', 'Start a database update'
|
28
|
+
|
29
|
+
reg 'player.stop', 'Stop playing'
|
30
|
+
reg 'player.toggle', 'Toggle play/pause'
|
31
|
+
reg 'player.forward', 'Seek forward'
|
32
|
+
reg 'player.backward', 'Seek backward'
|
33
|
+
|
34
|
+
reg 'tabs.next', 'Select next tab'
|
35
|
+
reg 'tabs.prev', 'Select previous tab'
|
36
|
+
|
37
|
+
reg 'browser.enter', 'Enter selected directory'
|
38
|
+
reg 'browser.add_to_playlist', 'Add tracks under cursor to playlist'
|
39
|
+
reg 'browser.back', 'Change to parent directory'
|
40
|
+
|
41
|
+
reg 'playinginfo.toggle', 'Toggle playinginfo visibility'
|
42
|
+
reg 'progressbar.toggle', 'Toggle progressbar visibility'
|
43
|
+
reg 'volumemeter.toggle', 'Toggle volumemeter visibility'
|
44
|
+
|
45
|
+
reg 'playlist.goto_current', 'Go to current playing track'
|
46
|
+
reg 'playlist.clear', 'Delete all items in playlist'
|
47
|
+
reg 'playlist.delete', 'Delete selected track from playlist'
|
48
|
+
reg 'playlist.download_album', 'Download album archive for selected track'
|
49
|
+
reg 'playlist.play', 'Play selected track'
|
50
|
+
reg 'playlist.reload', 'Force redownload of selected track'
|
51
|
+
reg 'playlist.play_next', 'Play next track in playlist'
|
52
|
+
reg 'playlist.play_prev', 'Play previous track in playlist'
|
53
|
+
|
54
|
+
%w(info help browser playlist splash).each do |w|
|
55
|
+
reg("#{w}.show", "Show #{w}")
|
56
|
+
end
|
57
|
+
|
58
|
+
{ up: 'Move cursor up', down: 'Move cursor down',
|
59
|
+
page_up: 'Scroll page up', page_down: 'Scroll page down',
|
60
|
+
top: 'Move to top', bottom: 'Move to bottom'
|
61
|
+
}.each do |cmd, desc|
|
62
|
+
%w(browser info help playlist).each { |w| reg("#{w}.#{cmd}", desc) }
|
63
|
+
end
|
64
|
+
|
65
|
+
{ search_up: 'Start search upwards', search_next: 'Goto next search result',
|
66
|
+
search_down: 'Start search downwards', search_prev: 'Goto previous search result'
|
67
|
+
}.each do |cmd, desc|
|
68
|
+
%w(browser playlist).each { |w| reg("#{w}.#{cmd}", desc) }
|
69
|
+
end
|
70
|
+
|
71
|
+
@bindings = {
|
72
|
+
global: {
|
73
|
+
'splash.show': [?`, ?^ ],
|
74
|
+
'playlist.show': [?1 ],
|
75
|
+
'browser.show': [?2 ],
|
76
|
+
'info.show': [?3 ],
|
77
|
+
'help.show': [?4 ],
|
78
|
+
|
79
|
+
'playinginfo.toggle': [ Curses::KEY_F2 ],
|
80
|
+
'progressbar.toggle': [ Curses::KEY_F3 ],
|
81
|
+
'volumemeter.toggle': [ Curses::KEY_F4 ],
|
82
|
+
|
83
|
+
'player.forward': [?f, Curses::KEY_RIGHT ],
|
84
|
+
'player.backward': [?b, Curses::KEY_LEFT ],
|
85
|
+
'player.stop': [?s ],
|
86
|
+
'player.toggle': [?p ],
|
87
|
+
|
88
|
+
'playlist.play_next': [?> ],
|
89
|
+
'playlist.play_prev': [?< ],
|
90
|
+
|
91
|
+
'tabs.next': [?l ],
|
92
|
+
'tabs.prev': [?h ],
|
93
|
+
|
94
|
+
quit: [?q ],
|
95
|
+
refresh: ['^l' ],
|
96
|
+
reload: ['^r' ],
|
97
|
+
update: [?U ]},
|
98
|
+
playlist: {
|
99
|
+
# movement
|
100
|
+
'playlist.top': [?g, Curses::KEY_HOME ],
|
101
|
+
'playlist.bottom': [?G, Curses::KEY_END ],
|
102
|
+
'playlist.up': [?k, Curses::KEY_UP ],
|
103
|
+
'playlist.down': [?j, Curses::KEY_DOWN ],
|
104
|
+
'playlist.page_down': ['^d', Curses::KEY_NPAGE ],
|
105
|
+
'playlist.page_up': ['^u', Curses::KEY_PPAGE ],
|
106
|
+
# search
|
107
|
+
'playlist.search_next': [?n ],
|
108
|
+
'playlist.search_prev': [?N ],
|
109
|
+
'playlist.search_up': [?? ],
|
110
|
+
'playlist.search_down': [?/ ],
|
111
|
+
# playlist
|
112
|
+
'playlist.play': [ Curses::KEY_ENTER ],
|
113
|
+
'playlist.download_album': [?$ ],
|
114
|
+
'playlist.reload': [?r ],
|
115
|
+
'playlist.goto_current': [?o ],
|
116
|
+
'playlist.clear': [?c ],
|
117
|
+
'playlist.delete': [?d ],
|
118
|
+
# other
|
119
|
+
'player.toggle': [' ' ]},
|
120
|
+
browser: {
|
121
|
+
# movement
|
122
|
+
'browser.top': [?g, Curses::KEY_HOME ],
|
123
|
+
'browser.bottom': [?G, Curses::KEY_END ],
|
124
|
+
'browser.up': [?k, Curses::KEY_UP ],
|
125
|
+
'browser.down': [?j, Curses::KEY_DOWN ],
|
126
|
+
'browser.page_up': ['^u', Curses::KEY_PPAGE ],
|
127
|
+
'browser.page_down': ['^d', Curses::KEY_NPAGE ],
|
128
|
+
# search
|
129
|
+
'browser.search_next': [?n ],
|
130
|
+
'browser.search_prev': [?N ],
|
131
|
+
'browser.search_up': [?? ],
|
132
|
+
'browser.search_down': [?/ ],
|
133
|
+
# browser
|
134
|
+
'browser.add_to_playlist': [' ', ?a ],
|
135
|
+
'browser.enter': [?E, Curses::KEY_ENTER ],
|
136
|
+
'browser.back': [?b, Curses::KEY_BACKSPACE ]},
|
137
|
+
help: {
|
138
|
+
'help.top': [?g, Curses::KEY_HOME ],
|
139
|
+
'help.bottom': [?G, Curses::KEY_END ],
|
140
|
+
'help.up': [?k, Curses::KEY_UP ],
|
141
|
+
'help.down': [?j, Curses::KEY_DOWN ],
|
142
|
+
'help.page_up': ['^u', Curses::KEY_PPAGE ],
|
143
|
+
'help.page_down': ['^d', Curses::KEY_NPAGE ]},
|
144
|
+
info: {
|
145
|
+
'info.top': [?g, Curses::KEY_HOME ],
|
146
|
+
'info.bottom': [?G, Curses::KEY_END ],
|
147
|
+
'info.up': [?k, Curses::KEY_UP ],
|
148
|
+
'info.down': [?j, Curses::KEY_DOWN ],
|
149
|
+
'info.page_up': ['^u', Curses::KEY_PPAGE ],
|
150
|
+
'info.page_down': ['^d', Curses::KEY_NPAGE ]},
|
151
|
+
splash: {}
|
152
|
+
}
|
153
|
+
|
154
|
+
@bindings.default_proc = proc { |h,k| fail "Unknown widget #{k}" }
|
155
|
+
@bindings.each do |widget, hash|
|
156
|
+
hash.default_proc = proc { |h,k| h[k] = {} }
|
157
|
+
hash.values.each do |keys|
|
158
|
+
keys.map! { |key| parse_key(key) }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def keyname(key)
|
164
|
+
return 'SPACE' if key.to_s == ' '
|
165
|
+
return key.to_s if key.is_a? Symbol
|
166
|
+
|
167
|
+
name = Curses.keyname(key)
|
168
|
+
if name.start_with? 'KEY_'
|
169
|
+
name.sub('KEY_', '').sub(/\((\d+)\)/, '\1')
|
170
|
+
else
|
171
|
+
name
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def parse_key(key)
|
176
|
+
if key.is_a? Integer
|
177
|
+
key
|
178
|
+
elsif key.size == 1
|
179
|
+
key.to_sym
|
180
|
+
elsif key.size == 2 and key.start_with?(?^)
|
181
|
+
Curses.const_get("KEY_CTRL_#{key[1].upcase}")
|
182
|
+
else
|
183
|
+
key = key.upcase.tr(?-, ?_)
|
184
|
+
key = "KEY_#{key}" unless key.start_with?('KEY_')
|
185
|
+
Curses.const_get(key)
|
186
|
+
end
|
187
|
+
rescue NameError
|
188
|
+
fail "Unknown key: #{key}"
|
189
|
+
end
|
190
|
+
|
191
|
+
def bind(widget, key, command)
|
192
|
+
widget, command = widget.to_sym, command.to_sym
|
193
|
+
fail "Unknown command #{command}" unless COMMANDS.include? command
|
194
|
+
|
195
|
+
@bindings[widget][command].delete parse_key(key)
|
196
|
+
@bindings[widget][command] << parse_key(key)
|
197
|
+
check_collisions
|
198
|
+
end
|
199
|
+
|
200
|
+
def unbind(widget, key)
|
201
|
+
@bindings[widget.to_sym].each do |command, keys|
|
202
|
+
keys.delete( (parsed_key ||= parse_key(k)) )
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def bind_view(section, view, view_operations, operations)
|
207
|
+
@bindings[section.to_sym].each do |command, keys|
|
208
|
+
keys.each do |key|
|
209
|
+
meth = view_operations.method(command) rescue operations.method(command)
|
210
|
+
view.keys.on(key, &meth)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
private def check_collisions
|
216
|
+
global_keys = @bindings[:global].values.flatten
|
217
|
+
global_keys.each do |k|
|
218
|
+
fail "Double binding in 'global': #{k}" if global_keys.count(k) > 1
|
219
|
+
end
|
220
|
+
|
221
|
+
@bindings.each_pair do |widget, commands|
|
222
|
+
widget_keys = commands.values.flatten
|
223
|
+
widget_keys.each do |k|
|
224
|
+
fail "Double binding in '#{widget}': #{k}" if widget_keys.count(k) > 1
|
225
|
+
fail "Double binding in 'global/#{widget}': #{k}" if global_keys.include? k
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|