#! /usr/bin/env python
import pygtk; pygtk.require("2.0")

import sys
import gobject
#gobject.threads_init()
import gtk
import gtk.gdk as gdk
import pango
import bonobo
import gosd
import gconfsync
import gc
import os.path
import commands
import gnome
import gnome_osd_conf
import warnings

bgcolor = "#000000"
fgcolor = "#80ff80"
fontdesc = pango.FontDescription("sans serif 20")
HIDE_TIMEOUT = 7000 # miliseconds
gosd.BORDER_WIDTH=1
ANIMATION=True
ANIMATION_STEPS=10
ANIMATION_TIME=200


gnome.program_init("gnome-osd-server", gnome_osd_conf.VERSION)
prefs = gconfsync.GConfSync("/apps/gnome-osd",
                            dict(osd_halignment=dict(left=pango.ALIGN_LEFT,
                                                     right=pango.ALIGN_RIGHT,
                                                     center=pango.ALIGN_CENTER)))

def get_sound_sample_file():
    sound_sample = prefs['sound_sample']
    if sound_sample[0] == '/':
        return sound_sample
    else:
        return os.path.join(gnome_osd_conf.datadir, "sounds", sound_sample)

# Note: interning atoms avoids X server roundtrips
_NET_CURRENT_DESKTOP = gtk.gdk.atom_intern("_NET_CURRENT_DESKTOP")
_NET_WORKAREA = gtk.gdk.atom_intern("_NET_WORKAREA")
def get_current_workarea():
    root = gtk.gdk.screen_get_default().get_root_window()
    prop = root.property_get(_NET_CURRENT_DESKTOP)
    if prop is None: return None
    propname, proptype, propvalue = prop
    current = propvalue[0]
    prop = root.property_get(_NET_WORKAREA)
    if prop is None: return None
    propname, proptype, propvalue = prop
    return propvalue[current*4 : (current + 1)*4]


class OSDMessage(object):

    class Message(object):
        __slots__ = ('text', 'timeout')

    class Animation(object):
        def __init__(self, win, x0, y0, xf, yf, callback, arg):
            self.callback = callback
            self.callback_arg = arg

            if not prefs['animations']:
                win.move(xf, yf)
                win.show()
                self.__animation_finished()
                return
                
            self.x = float(x0)
            self.y = float(y0)
            self.dx = (xf - x0) / ANIMATION_STEPS
            self.dy = (yf - y0) / ANIMATION_STEPS
            self.win = win
            self.counter = ANIMATION_STEPS
            self.timeout = gobject.timeout_add(ANIMATION_TIME // ANIMATION_STEPS,
                                               self.__timeout_cb)
            win.move(x0, y0)
            win.show()

        def __timeout_cb(self):
            self.x += self.dx
            self.y += self.dy
            self.win.move(int(self.x), int(self.y))
            self.counter -= 1
            if not self.counter:
                self.__animation_finished()
                return False
            return True

        def __animation_finished(self):
            self.timeout = 0
            if self.callback is not None:
                if self.callback_arg is None:
                    self.callback()
                else:
                    self.callback(self.callback_arg)
            self.win = None
            self.callback = None
            self.callback_arg = None

    
    def __init__(self):
        self.win = None
        self.hide_timeout = None
        self.message_queue = []
        self.__show_lock = False
        self.animation_vector = None

    def show(self, text, hide_timeout=HIDE_TIMEOUT):
        msg = self.Message()
        msg.text = text
        msg.timeout = hide_timeout
        self.message_queue.append(msg)
        self.__show_next()

    def __show_next(self):
        if self.__show_lock:
            return
        self.__show_lock = True
        if self.win is not None:
            self.hide(self.__show_next1)
        else:
            self.__show_next1()
            
    def __show_next1(self):
        halign = prefs['osd_halignment']
        vpos = prefs['osd_vposition']
        msg = self.message_queue.pop(0)

        if prefs['avoid_panels']:
            workarea = get_current_workarea()
            if workarea is not None:
                desk_x, desk_y, desk_w, desk_h = workarea
            else:
                warnings.warn("get_current_workarea failed, somehow")
                desk_x, desk_y = 0, 0
                desk_w = gdk.screen_width()
                desk_h = gdk.screen_height()
        else:
            desk_x, desk_y = 0, 0
            desk_w = gdk.screen_width()
            desk_h = gdk.screen_height()
        
        w = gosd.osd(msg.text, bgcolor, fgcolor,
                     pango.FontDescription(prefs.osd_font),
                     use_markup=True,
                     alignment=halign,
                     fake_translucent_bg=prefs["osd_fake_translucent_bg"],
                     drop_shadow=prefs["drop_shadow"],
                     max_width=desk_w)
        if prefs['hide_on_hover']:
            w.connect("motion-notify-event", self.__osd_motion_notify)
        if prefs['enable_sound']:
            gnome.sound_play(get_sound_sample_file())

        if vpos == 'top':
            y = -w.height
            y_final = desk_y + 2
        else:
            y = gdk.screen_height()
            y_final = desk_y + desk_h - w.height - 2
        
        if halign == pango.ALIGN_LEFT:
            x = 2 + desk_x
        elif halign == pango.ALIGN_RIGHT:
            x = desk_x + desk_w - w.width - 2
        else: # center
            x = desk_x + desk_w/2 - w.width/2
        self.win = w
        self.Animation(w, x, y, x, y_final,
                       self.__show_next2, msg)
        self.animation_vector = (x, y, x, y_final)

    def __show_next2(self, msg):
        self.hide_timeout = gobject.timeout_add(msg.timeout, self.hide)
        self.__show_lock = False
        if self.message_queue:
            self.__show_next()

    def hide(self, callback=None, cb_args=None):
        if self.win is None:
            if callback is not None:
                if cb_args is None:
                    callback()
                else:
                    callback(cb_args)
            return
        
        if not self.hide_timeout:
            return
        gobject.source_remove(self.hide_timeout)
        self.hide_timeout = None
        xi, yi, xf, yf = self.animation_vector
        self.Animation(self.win, xf, yf, xi, yi,
                       self.__hide1, (callback, cb_args))

    def __hide1(self, data):
        callback, cb_args = data
        self.win.destroy()
        self.win = None
        gc.collect()
        if callback is not None:
            if cb_args is None:
                callback()
            else:
                callback(cb_args)

    def __osd_motion_notify(self, widget, event):
        self.hide()


def message_quit_cb(app_client, message, app):
    print "received quit message"
    app.unref()
    bonobo.main_quit()

def message_show_cb(app_client, message, args):
    text  = args[0]
    try:
        timeout = args[1]
    except IndexError:
        timeout = HIDE_TIMEOUT
    global osd_msg
    osd_msg.show(text, timeout)

def main(argv):
    global osd_msg
    bonobo.activate()
    app = bonobo.Application("GNOME_OSD")
    def last_unref_cb(*args):
        print "Last Unref, quitting"
        bonobo.main_quit()
    bonobo.activation.active_server_register("OAFIID:GNOME_OSD", app.corba_objref())
    osd_msg = OSDMessage()
    app.connect("message::quit", message_quit_cb, app)
    app.connect("message::show", message_show_cb)
    bonobo.context_running_get().connect("last-unref", last_unref_cb)
    print "entering main loop"
    bonobo.main()

main(sys.argv)

