5 # Copyright (c) 2006 Tilman Sauerbeck (tilman at code-monkey de)
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of version 2 of the GNU General Public License as
9 # published by the Free Software Foundation.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
25 require "embrace/imap"
28 DATADIR = "/usr/local/share/#{PKG_NAME}/"
30 class Evas::EvasObject
31 def move_relative(obj, x, y)
32 # FIXME investigate whether there's an easier way
33 move(*obj.geometry[0..1].zip([x, y]).map { |(a, b)| a + b })
40 move_relative(obj, (b[2] / 2) - (a[2] / 2),
41 (b[3] / 2) - (a[3] / 2))
45 set_color(*(get_color[0..-2] << alpha))
51 ICON_FILE = DATADIR + "l33t_MAI_envelope.png"
54 class ZeroToOneAnimator < Ecore::Animator
55 def initialize(duration)
61 v = [(Time.now - started) / duration, 1.0].min
65 @finished.call(self) unless (v < 1.0) || @finished.nil?
76 def on_finished(&block)
81 # an animator that runs for the specified number of seconds,
82 # and yields values between 0 and 255
83 class AlphaAnimator < ZeroToOneAnimator
84 def initialize(duration, *objects)
85 super(duration) do |v|
86 objects.each { |o| o.alpha = (255 * v).to_i }
91 class MoveAnimator < ZeroToOneAnimator
92 def initialize(duration, movement, *objects)
93 zipped = objects.zip(objects.map { |o| o.geometry[0..1] })
95 super(duration) do |v|
97 v = Math.sin(v * Math::PI / 2.0)
99 zipped.each do |(o, orig_pos)|
100 o.move(orig_pos.first,
101 orig_pos.last + (movement * v).to_i)
107 class MailboxIcon < Evas::Smart
110 def initialize(evas, label)
116 @img = Evas::Image.new(evas)
117 @label = Evas::Text.new(evas)
119 @objects = [@img, @label]
121 @img.set_color(255, 255, 255, 0)
122 @label.set_color(255, 255, 255, 0)
123 @label.set_color(255, 0, 0, 0)
125 @img.set_file(ICON_FILE)
126 @img.set_fill(0, 0, *@img.get_size)
129 @label.set_font("VeraBd", 10)
131 resize(*@img.get_size)
140 @objects.each { |o| o.show }
142 @alpha_anim ||= AlphaAnimator.new(2, @img, @label)
143 @alpha_anim.on_finished { @alpha_anim = nil }
147 @objects.each { |o| o.hide }
149 @alpha_anim && @alpha_anim.delete
154 @objects.each { |o| o.delete }
157 @alpha_anim && @alpha_anim.delete
159 @img = @label = @alpha_anim = nil
162 def on_layer_set(layer)
163 @objects.each { |o| o.layer = layer }
166 def on_stack_above(other)
167 @objects.each { |o| o.stack_above = other }
170 def on_stack_below(other)
171 @objects.each { |o| o.stack_below = other }
175 @objects.each { |o| o.move(x, y) }
185 class FixedSizeArray < Array
191 super { |item| yield item unless item.nil? }
207 class Container < Evas::Smart
208 class ContainerError < StandardError; end
209 class ContainerFullError < ContainerError; end
210 class ContainerLockedError < ContainerError; end
217 @bg = Evas::Rectangle.new(evas)
218 @bg.set_color(0, 0, 0, 255)
220 @icons = FixedSizeArray.new(MAX_ICONS)
228 @icons.each { |i| yield i unless i.nil? }
232 Kernel.raise(ContainerFullError) if slots_left.zero?
233 Kernel.raise(ContainerLockedError) if @lock_count > 0
235 i.move_relative(self, 0, 0)
240 # check whether we need to need to move this icon
246 movement = Main.instance.icon_height * (slots_left - 1)
250 move_time = 0.25 * slots_left
251 @animators << MoveAnimator.new(move_time, movement, i)
253 @animators.last.on_finished do |ani|
254 @animators.delete(ani)
257 # FIXME check whether we can always shift the array instead
258 #puts "really added #{i.label} now (slot #{i.slot})"
259 @about_to_add.delete(i)
264 # icons that are placed above the one that's deleted need
266 i = @icons.index(icon)
267 return (block_given? ? yield : nil) if i.nil?
276 ar = @icons[i..-1].reject { |i| i.nil? }
281 @animators << MoveAnimator.new(2, Main.instance.icon_height, *ar)
282 @animators.last.on_finished do |ani|
283 @animators.delete(ani)
306 def on_layer_set(layer)
310 def on_stack_above(other)
311 @bg.stack_above = other
314 def on_stack_below(other)
315 @bg.stack_below = other
328 MAX_ICONS - @icons.nitems - @about_to_add.length
332 @icons.nitems + @about_to_add.length
336 class Main < Ecore::Evas::SoftwareX11
339 attr_reader :container
345 self.borderless = true
347 @icon_dim = IO.read(ICON_FILE, 8, 16).unpack("NN")
349 self.on_resize { @container.resize(*geometry[2, 3]) }
351 @container = Container.new(evas)
352 @container.move(0, 0)
353 @container.layer = -1
356 size = [@icon_dim.first, icon_height * MAX_ICONS]
361 evas.font_path_append("/usr/lib/X11/fonts/TTF")
364 Ecore::EventHandler.new(IMAP::MailboxStatusEvent,
365 &method(:on_mailbox_status)),
366 Ecore::EventHandler.new(IMAP::FinishedEvent,
367 &method(:on_finished))
371 @timer = Ecore::Timer.new(30, &method(:on_timer))
380 Ecore.main_loop_begin
385 @container << MailboxIcon.new(evas, name)
389 return unless @server.nil?
400 Lists.enlightenment-cvs
401 Lists.enlightenment-devel
411 s = File.expand_path("~/.e/apps/embrace/config.yaml")
414 @server = IMAP::Session.new(YAML.load(f), mboxes)
420 def on_mailbox_status(ev)
421 md = ev.name.match(/^Lists.(.+)$/)
425 lbl = md.captures.first
428 found = @container.find { |i| i.label == lbl }
433 #puts "removing icon #{lbl}"
434 @container.delete(found)
436 #puts "count == 0, but icon not found (#{lbl})"
439 #puts "adding icon #{lbl}"
442 #puts "count > 0, but already there (#{lbl})"
444 rescue Exception => e
457 Embrace::Main.instance.show
458 Embrace::Main.instance.run