Popup the menu in the button_press_event handler.
[snett.git] / bin / snett
1 #!/usr/bin/env ruby
2
3 # vim:syn=ruby
4 #
5 # Copyright (c) 2006 Tilman Sauerbeck (tilman at code-monkey de)
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be
16 # included in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26 require "gtk2"
27 require "xmmsclient"
28 require "xmmsclient_glib"
29 require "singleton"
30 require "pp"
31
32 PKG_NAME = "snett"
33 DATADIR = "/usr/local/share/#{PKG_NAME}/"
34 SIZE = 64
35
36 class Gtk::MenuItem
37         def set_callback(meth, args = [])
38                 args.unshift(meth)
39
40                 signal_connect("activate") do
41                         Snett::Snett.instance.xmms.send(*args)
42                         false
43                 end
44         end
45 end
46
47 class Xmms::Client
48         def playback_next
49                 playlist_set_next_rel(1).notifier { playback_tickle }
50         end
51
52         def playback_prev
53                 playlist_set_next_rel(-1).notifier { playback_tickle }
54         end
55 end
56
57 module Snett
58         VERSION = "0.0.1"
59
60         class Snett < Gtk::Window
61                 attr_reader :icon, :xmms
62
63                 include Singleton
64
65                 def initialize
66                         super(Gtk::Window::TOPLEVEL)
67
68                         @menu = nil
69                         @tooltips = Gtk::Tooltips.new
70
71                         @xmms = Xmms::Client.new(PKG_NAME)
72                         @xmms.connect(ENV["XMMS_PATH"])
73                         @xmms.add_to_glib_mainloop
74
75                         @broadcasts = []
76
77                         self.set_title(PKG_NAME)
78
79                         signal_connect("destroy") do
80                                 @broadcasts.each { |bc| bc.disconnect }
81
82                                 Gtk.main_quit
83                         end
84
85                         self.resizable = false
86                         self.decorated = false
87
88                         @icon = Gdk::Pixbuf.new(DATADIR + "logo#{SIZE}.png")
89
90                         self.resize(@icon.width, @icon.height)
91
92                         #self.colormap = self.screen.rgba_colormap
93
94                         pixmap, mask = @icon.render_pixmap_and_mask(self.colormap, 255)
95                         self.shape_combine_mask(mask, 0, 0)
96
97                         # load icon and put it in an eventbox
98                         event_box = Gtk::EventBox.new.add(Gtk::Image.new(@icon))
99                         event_box.events = Gdk::Event::BUTTON_PRESS_MASK |
100                                            Gdk::Event::BUTTON_RELEASE_MASK
101
102                         event_box.signal_connect("button_press_event") do |_, event|
103                                 if event.button == 1
104                                         x, y = Gdk::Display.default.pointer[1, 2]
105                                         self.begin_move_drag(event.button, x, y, event.time)
106                                 elsif event.button == 3
107                                         menu.popup(event)
108                                 end
109
110                                 false
111                         end
112
113                         @broadcasts << @xmms.broadcast_playback_current_id
114                         @broadcasts.last.notifier(&method(:on_playback_current_id))
115
116                         @xmms.playback_current_id.
117                               notifier(&method(:on_playback_current_id))
118
119                         add(event_box)
120                 end
121
122                 private
123                 def menu
124                         @menu ||= Menu.new
125                 end
126
127                 def on_playback_current_id(res)
128                         return if res.value.zero?
129
130                         @xmms.medialib_get_info(res.value).
131                               notifier(&method(:on_mlib_get_info))
132                 end
133
134                 def on_mlib_get_info(res)
135                         info = res.value[:server]
136
137                         s = "%s - %s" % [info[:artist], info[:title]]
138                         @tooltips.set_tip(self, s, nil)
139                 end
140         end
141
142         class Menu < Gtk::Menu
143                 def initialize
144                         super
145
146                         [
147                                 [Gtk::Stock::MEDIA_PLAY, :playback_start],
148                                 [Gtk::Stock::MEDIA_PAUSE, :playback_pause],
149                                 [Gtk::Stock::MEDIA_STOP, :playback_stop]
150                         ].each do |(stock, meth)|
151                                 item = Gtk::ImageMenuItem.new(stock)
152                                 item.set_callback(meth)
153
154                                 append(item)
155                         end
156
157                         append(Gtk::SeparatorMenuItem.new)
158
159                         [
160                                 [Gtk::Stock::MEDIA_NEXT, :playback_next],
161                                 [Gtk::Stock::MEDIA_PREVIOUS, :playback_prev]
162                         ].each do |(stock, meth)|
163                                 item = Gtk::ImageMenuItem.new(stock)
164                                 item.set_callback(meth)
165
166                                 append(item)
167                         end
168
169                         append(Gtk::SeparatorMenuItem.new)
170
171                         [
172                                 [Gtk::Stock::OPEN, "Load playlist...", :load],
173                                 [Gtk::Stock::ADD, "Append playlist...", :append]
174                         ].each do |(stock, text, sym)|
175                                 item = Gtk::ImageMenuItem.new(text)
176                                 item.image = Gtk::Image.new(stock, Gtk::IconSize::MENU)
177
178                                 item.signal_connect("activate") do
179                                         PlaylistDialog.new(sym).show_all
180                                         false
181                                 end
182
183                                 append(item)
184                         end
185
186                         item = Gtk::ImageMenuItem.new(Gtk::Stock::CLEAR)
187                         item.set_callback(:playlist_clear)
188
189                         append(item)
190
191                         append(Gtk::SeparatorMenuItem.new)
192
193                         item = Gtk::ImageMenuItem.new(Gtk::Stock::ABOUT)
194                         item.signal_connect("activate") do
195                                 props = {
196                                         "name" => PKG_NAME,
197                                         "version" => VERSION,
198                                         "copyright" => "(c) 2006 Tilman Sauerbeck",
199                                         "logo" => Gdk::Pixbuf.new(DATADIR + "logo128.png")
200                                 }
201
202                                 Gtk::AboutDialog.show(Snett.instance, props)
203                                 false
204                         end
205
206                         append(item)
207
208                         item = Gtk::ImageMenuItem.new(Gtk::Stock::QUIT)
209                         item.signal_connect("activate") do
210                                 Snett.instance.signal_emit("destroy")
211                                 false
212                         end
213
214                         append(item)
215
216                         show_all
217                 end
218
219                 def popup(event)
220                         super(nil, nil, event.button, event.time)
221                 end
222         end
223
224         class PlaylistDialog < Gtk::Dialog
225                 def initialize(mode)
226                         super("Playlists", Snett.instance,
227                               Gtk::Dialog::DESTROY_WITH_PARENT | Gtk::Dialog::MODAL,
228                               [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
229                                   [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])
230
231                         @mode = mode
232
233                         signal_connect("response") do |_, id|
234                                 on_ok if id == Gtk::Dialog::RESPONSE_ACCEPT
235                                 destroy
236                         end
237
238                         @store = Gtk::ListStore.new(String)
239
240                         @view = Gtk::TreeView.new(@store)
241                         renderer = Gtk::CellRendererText.new
242
243                         col = Gtk::TreeViewColumn.new("Name", renderer, :text => 0)
244                         @view.append_column(col)
245
246                         scrolled = Gtk::ScrolledWindow.new
247                         scrolled.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
248                         scrolled.add(@view)
249
250                         vbox.add(scrolled)
251
252                         resize(350, 400)
253
254                         Snett.instance.xmms.medialib_playlists_list.
255                                             notifier(&method(:on_playlists_list))
256                 end
257
258                 private
259                 def on_playlists_list(res)
260                         res.value.sort.each do |list|
261                                 next if list[0, 1] == "_"
262
263                                 @store.append[0] = list
264                         end
265                 end
266
267                 def on_ok
268                         selection = @view.selection
269                         iter = selection.selected
270                         return if iter.nil?
271
272                         Snett.instance.xmms.playlist_clear if @mode == :load
273                         Snett.instance.xmms.medialib_playlist_load(iter[0])
274                 end
275         end
276 end
277
278 Gtk.init
279 Snett::Snett.instance.show_all
280 Gtk.main