import sys
 
import pygtk
pygtk.require("2.0")
import gtk
import pango
 
from Boarded.i18n import _
import Boarded.keypress as keypress
from Boarded.gui_miniicon import MiniIcon
import Boarded.configreader as configreader
 
 
MAIN_WINDOW_TITLE = "Boarded"
RESIZE_AREA_SIZE = (25, 25)
 
_NEXT_KEYBOARD = None
 
 
class KeyButton(gtk.EventBox):
  def __init__(self, config, *args, **kwargs):
    super(KeyButton, self).__init__(*args, **kwargs)
    self.config = config
    self.label = None
 
  def add_label(self):
    self.remove_label()
    self.label = gtk.Label()
    self.add(self.label)
    self.label.show()
 
  def remove_label(self):
    if self.label:
      self.remove(label)
      self.label.destroy()
      self.label = None
 
  def colour(self, state):
    bgc, fgc = None, None
    if state == "normal":
      bgc = gtk.gdk.color_parse(self.config["colors"]["key_bg"])
      fgc = gtk.gdk.color_parse(self.config["colors"]["key_fg"])
    elif state == "pressed":
      bgc = gtk.gdk.color_parse(self.config["colors"]["key_pressed_bg"])
      fgc = gtk.gdk.color_parse(self.config["colors"]["key_pressed_fg"])
    elif state == "locked":
      bgc = gtk.gdk.color_parse(self.config["colors"]["key_locked_bg"])
      fgc = gtk.gdk.color_parse(self.config["colors"]["key_locked_fg"])
    if bgc and fgc:
      self.modify_bg(gtk.STATE_NORMAL, bgc)
      self.modify_fg(gtk.STATE_NORMAL, fgc)
      self.modify_bg(gtk.STATE_ACTIVE, bgc)
      self.modify_fg(gtk.STATE_ACTIVE, fgc)
      self.modify_bg(gtk.STATE_PRELIGHT, bgc)
      self.modify_fg(gtk.STATE_PRELIGHT, fgc)
      self.modify_bg(gtk.STATE_SELECTED, bgc)
      self.modify_fg(gtk.STATE_SELECTED, fgc)
      self.modify_bg(gtk.STATE_INSENSITIVE, bgc)
      self.modify_fg(gtk.STATE_INSENSITIVE, fgc)
 
  def pos_is_inside(self, pos):
    _size = self.size_request()
    return pos[0] >= 0 and pos[1] >= 0 and \
           pos[0] < _size[0] and pos[1] < _size[1]
 
 
class Main(object):
  def __init__(self, config, kb, key_emu):
    self.config = config
    self.kb = kb
    self.key_groups = {}
    for group in kb.key_groups:
      if (kb.name, group) not in config["keygroup_states"]:
        config["keygroup_states"][(kb.name, group)] = True
        configreader.save_properties(config)
      self.key_groups[group] = config["keygroup_states"][(kb.name, group)]
    self.key_group_menu = {}
    self.key_buttons = {}
    self.pressed_keys = set()
    self.locked_mods = set()
    self.queued_mods = []
    self.key_emu = key_emu
    self.is_iconified = False
    self.key_button_is_down = False
    self.key_button_size_factor = 1.0
    self.last_synced_size = None
 
    _size, _pos = self.get_default_geometry()
    if config["kbwindow_size"]:
      _size = config["kbwindow_size"]
    if config["kbwindow_pos"]:
      _pos = config["kbwindow_pos"]
 
    if config["xembed"]:
      self.win = PlugWindow()
      sys.stdout.write("%d\n" % self.win.get_id())
      sys.stdout.flush()
    else:
      self.win = NonPluggingWindow()
 
    self.win.set_accept_focus(False)
    self.win.set_skip_taskbar_hint(True)
    self.win.set_keep_above(True)
    self.win.set_decorated(False)
    self.win.set_resizable(True)
    self.win.set_app_paintable(True)
 
    self.win.set_geometry_hints(self.win,
        100, 50,                    # min width, min height
        -1, -1,                     # max width, max height
        _size[0], _size[1],         # base width, base height
         1,  1,                     # width, height - resize increment
        -1, -1,                     # min, max aspect ratio
       )
 
    #self.win.set_default_size(*_size)
    self.win.resize(*_size)
    self.win.move(*_pos)
 
    self.win.set_title(MAIN_WINDOW_TITLE)
 
    self.miniicon = MiniIcon(config=self.config)
    self.miniicon.connect("activated", self.deiconify)
 
    self.key_box = gtk.Fixed()
    self.win.modify_bg(gtk.STATE_NORMAL,
                       gtk.gdk.color_parse(self.config["colors"]["bg"]))
    self.win.add(self.key_box)
    self.key_box.show()
 
    self.make_key_buttons(self.win.get_size())
 
    self.win.connect("destroy", self.on_destroy)
    self.win.connect("configure_event", self.on_configure)
    self.win.connect("expose_event", self.on_expose)
 
    self.win.connect("button_press_event", self.on_button_press_on_win)
 
    self.build_main_menu()
 
    self.win.show()
 
    gtk.main()
 
 
  def iconify(self, event=None):
    if not self.is_iconified:
      self.win.hide_all()
      self.miniicon.do_show()
      self.is_iconified = True
 
  def deiconify(self, event=None):
    if self.is_iconified:
      self.win.resize(*self.config["kbwindow_size"])
      self.win.move(*self.config["kbwindow_pos"])
      self.win.show_all()
      self.is_iconified = False
 
 
  def is_in_resize_area(self, pos):
    size = self.win.get_size()
    return size[0] - RESIZE_AREA_SIZE[0] < pos[0] and pos[0] < size[0] and \
           size[1] - RESIZE_AREA_SIZE[1] < pos[1] and pos[1] < size[1]
 
 
  def on_destroy(self, widget):
    self.miniicon.destroy()
    for btn in self.key_buttons.itervalues():
      btn.destroy()
    gtk.main_quit()
 
 
  def on_configure(self, widget, event):
    size = self.win.get_size()
    if size != self.last_synced_size:
      self.make_key_buttons(size)
      self.last_synced_size = size
 
  def on_expose(self, widget, event):
    size = self.win.get_size()
    self.win.get_style().paint_resize_grip(self.win.window, gtk.STATE_NORMAL,
                                           None, self.win, None,
                                           gtk.gdk.WINDOW_EDGE_SOUTH_EAST,
                                           size[0] - RESIZE_AREA_SIZE[0],
                                           size[1] - RESIZE_AREA_SIZE[1],
                                           *RESIZE_AREA_SIZE)
 
 
  def on_menu_item_activate(self, name):
    if name == "main.hide":
      self.iconify()
    elif name.startswith("main.kbsel."):
      global _NEXT_KEYBOARD
      _NEXT_KEYBOARD = name[len("main.kbsel."):]
      self.win.destroy()
    elif name.startswith("main.groupsel."):
      group = name[len("main.groupsel."):]
      self.key_groups[group] = self.key_group_menu[group].get_active()
      self.make_key_buttons(self.win.get_size())
    elif name == "main.save_geometry":
      if self.win.get_size() != self.config["kbwindow_size"]:
        self.config["kbwindow_size"] = self.win.get_size()
      if self.win.get_position() != self.config["kbwindow_pos"]:
        self.config["kbwindow_pos"] = self.win.get_position()
      configreader.save_properties(self.config)
    elif name == "main.save_keygroups":
      for group in self.key_groups:
        self.config["keygroup_states"][(self.kb.name, group)] = self.key_groups[group]
      configreader.save_properties(self.config)
    elif name == "main.reset_to_saved_geometry":
      self.win.resize(*self.config["kbwindow_size"])
      self.win.move(*self.config["kbwindow_pos"])
    elif name == "main.reset_to_default_geometry":
      size, pos = self.get_default_geometry()
      self.win.resize(*size)
      self.win.move(*pos)
    elif name == "main.quit":
      self.win.destroy()
 
 
  def on_button_press_on_win(self, widget, event):
    if event.button == 1:
      if self.is_in_resize_area((event.x, event.y)):
        self.win.begin_resize_drag(gtk.gdk.WINDOW_EDGE_SOUTH_EAST, 1,
                                   int(event.x_root), int(event.y_root), event.time)
      else:
        self.main_menu.popup(None, None, None, event.button, event.time)
 
 
  def on_key_button_down(self, btn, event):
    if event.button != 1:
      return False
    uid = btn.get_name()
    key = self.kb.keys[uid]
    if "action" in key.actions:
      if key.actions["action"] == "boarded_menu":
        self.main_menu.popup(None, None, None, event.button, event.time)
        return True
      elif key.actions["action"] == "window_motion":
        self.win.begin_move_drag(1, int(event.x_root), int(event.y_root),
                                 event.time)
        return True
    self.key_button_is_down = True
    #print "Down:", repr(key.label)
    if not "mod" in key.actions:
      if not self.queued_mods and not key.press_after:
        self.key_down(key)
      self.pressed_keys.add(key.uid)
      btn.colour("pressed")
    return True
 
  def on_key_button_up(self, btn, event):
    if event.button != 1:
      return False
    uid = btn.get_name()
    key = self.kb.keys[uid]
    if "action" in key.actions and \
       key.actions["action"] in ("boarded_menu", "window_motion"):
      return False
    self.key_button_is_down = False
    #print "Up:", repr(key.label)
    cursor_pos = btn.get_pointer()
    if "mod" in key.actions and btn.pos_is_inside(cursor_pos):
      if key.uid in self.locked_mods:
        self.queued_mods.remove(key.uid)
        self.locked_mods.remove(key.uid)
        self.pressed_keys.remove(key.uid)
        btn.colour("normal")
      elif key.uid in self.queued_mods:
        self.locked_mods.add(key.uid)
        btn.colour("locked")
      else:
        self.queued_mods.append(key.uid)
        self.pressed_keys.add(key.uid)
        btn.colour("pressed")
    else:
      if self.queued_mods or key.press_after:
        for mod in self.queued_mods:
          self.key_down(self.kb.keys[mod])
        self.key_down(key)
        self.key_up(key)
        for mod in reversed(self.queued_mods):
          self.key_up(self.kb.keys[mod])
        for mod in [mod for mod in self.queued_mods \
                    if mod not in self.locked_mods]:
          self.queued_mods.remove(mod)
          if mod in self.pressed_keys:
            self.pressed_keys.remove(mod)
          self.key_buttons[mod].colour("normal")
      else:
        self.key_up(key)
      if key.uid in self.pressed_keys:
        self.pressed_keys.remove(key.uid)
      btn.colour("normal")
    return True
 
  def on_leave_key_button(self, btn, event):
    uid = btn.get_name()
    key = self.kb.keys[uid]
    cursor_pos = btn.get_pointer()
    if self.key_button_is_down and "mod" in key.actions and \
       not btn.pos_is_inside(cursor_pos):
      if key.uid in self.locked_mods:
        self.locked_mods.remove(key.uid)
      self.pressed_keys.add(key.uid)
      self.key_down(key)
      btn.colour("pressed")
 
 
  def get_default_geometry(self):
    ds = gtk.gdk.display_get_default().get_default_screen()
    screen_size = (ds.get_width(), ds.get_height())
    size = (screen_size[0], int(screen_size[1]/2.5))
    pos = (0, screen_size[1]-size[1])
    return size, pos
 
 
  def get_key_button_shifting(self, buttons):
    smallest = (min([key.pos[0] for key in buttons.itervalues()]),
                min([key.pos[1] for key in buttons.itervalues()]))
    shifting = (0-smallest[0], 0-smallest[1])
 
    return shifting
 
  def get_key_button_size_factor(self, buttons, size, shifting=None):
    if not shifting:
      shifting = (0, 0)
    factor = 1
 
    largest = (max([shifting[0]+key.pos[0]+key.size[0] for key in buttons.itervalues()]),
               max([shifting[0]+key.pos[1]+key.size[1] for key in buttons.itervalues()]))
    diff = (largest[0]-size[0], largest[1]-size[1])
    if diff[0] > 0:
      factor = factor*float(size[0])/largest[0]
    elif diff[0] < 0:
      factor = factor*float(size[0])/largest[0]
    largest = (max([factor*(shifting[0]+key.pos[0]+key.size[0]) for key in buttons.itervalues()]),
               max([factor*(shifting[1]+key.pos[1]+key.size[1]) for key in buttons.itervalues()]))
    diff = (largest[0]-size[0], largest[1]-size[1])
    if diff[1] > 0:
      factor = factor*float(size[1])/largest[1]
    elif diff[1] < 0 and float(size[1])/largest[1] < 1:
      factor = factor*float(size[1])/largest[1]
 
    self.key_button_size_factor = factor
    return factor
 
 
  def make_key_buttons(self, size):
    buttons = {}
    for key in [k for k in self.kb.keys.itervalues() \
                if not k.group or self.key_groups[k.group]]:
      buttons[key.uid] = key
 
    shifting = self.get_key_button_shifting(buttons)
    factor = self.get_key_button_size_factor(buttons, size, shifting)
 
    for key in [self.kb.keys[k] for k in self.key_buttons.iterkeys() if not k in buttons]:
      btn = self.key_buttons[key.uid]
      del self.key_buttons[key.uid]
      btn.destroy()
 
    for key in buttons.itervalues():
      _size = (int(key.size[0]*factor), int(key.size[1]*factor))
      _pos = (int((shifting[0]+key.pos[0])*factor),
              int((shifting[1]+key.pos[1])*factor))
      if key.uid not in self.key_buttons:
        btn = KeyButton(config=self.config)
        btn.set_name(key.uid)
        btn.add_label()
        btn.label.set_text(key.label)
        btn.connect("button_press_event", self.on_key_button_down)
        btn.connect("button_release_event", self.on_key_button_up)
        btn.connect("leave_notify_event", self.on_leave_key_button)
        self.key_box.add(btn)
        self.key_buttons[key.uid] = btn
      btn = self.key_buttons[key.uid]
      self.key_box.move(btn, *_pos)
      btn.set_size_request(*_size)
      label = btn.label
      if label:
        fd = pango.FontDescription("%s %d" % (self.config["font"], _size[1]/2))
        if fd:
          label.modify_font(fd)
          while fd.get_size() > 1000 and \
                (label.size_request()[0] > _size[0]-5 or \
                 label.size_request()[0] > _size[1]-5):
            fd.set_size(fd.get_size()-1000)
            label.modify_font(fd)
      btn.show()
 
      if key.uid in self.locked_mods:
        btn.colour("locked")
      elif key.uid in self.pressed_keys:
        btn.colour("pressed")
      else:
        btn.colour("normal")
 
 
  def build_main_menu(self):
    main_menu = gtk.Menu()
    hide_item = gtk.MenuItem(_("Hide keyboard"))
    hide_item.connect_object("activate", self.on_menu_item_activate, "main.hide")
    main_menu.append(hide_item)
    hide_item.show()
    groupsel_item = gtk.MenuItem(_("Show or hide keygroups"))
    groupsel_menu = gtk.Menu()
    groupsel_item.set_submenu(groupsel_menu)
    main_menu.append(groupsel_item)
    groupsel_item.show()
    for group in self.key_groups:
      group_item = gtk.CheckMenuItem(group)
      group_item.connect_object("activate", self.on_menu_item_activate,
                                "main.groupsel." + group)
      groupsel_menu.append(group_item)
      group_item.show()
      self.key_group_menu[group] = group_item
      group_item.set_active(bool(self.key_groups[group]))
    if len(self.config["keyboards"]) > 1:
      kbsel_item = gtk.MenuItem(_("Switch keyboard"))
      kbsel_menu = gtk.Menu()
      kbsel_item.set_submenu(kbsel_menu)
      main_menu.append(kbsel_item)
      kbsel_item.show()
      for kbuid, kb in self.config["keyboards"].iteritems():
        if kbuid != self.kb.uid:
          kb_item = gtk.MenuItem(kb.name)
          kb_item.connect_object("activate", self.on_menu_item_activate,
                                 "main.kbsel." + kbuid)
          kbsel_menu.append(kb_item)
          kb_item.show()
    save_geo_item = gtk.MenuItem(_("Save keyboard position and size"))
    save_geo_item.connect_object("activate", self.on_menu_item_activate, "main.save_geometry")
    main_menu.append(save_geo_item)
    save_geo_item.show()
    save_keygroups_item = gtk.MenuItem(_("Save key group visibility state"))
    save_keygroups_item.connect_object("activate", self.on_menu_item_activate,
                                       "main.save_keygroups")
    main_menu.append(save_keygroups_item)
    save_keygroups_item.show()
    sep_item = gtk.SeparatorMenuItem()
    main_menu.append(sep_item)
    sep_item.show()
    reset_saved_item = gtk.MenuItem(_("Reset keyboard position and size "
                                      "to last saved state"))
    reset_saved_item.connect_object("activate", self.on_menu_item_activate,
                                    "main.reset_to_saved_geometry")
    main_menu.append(reset_saved_item)
    reset_saved_item.show()
    reset_default_item = gtk.MenuItem(_("Reset keyboard position and size "
                                        "to default state"))
    reset_default_item.connect_object("activate", self.on_menu_item_activate,
                                      "main.reset_to_default_geometry")
    main_menu.append(reset_default_item)
    reset_default_item.show()
    sep_item = gtk.SeparatorMenuItem()
    main_menu.append(sep_item)
    sep_item.show()
    quit_item = gtk.MenuItem(_("Quit"))
    quit_item.connect_object("activate", self.on_menu_item_activate,
                             "main.quit")
    main_menu.append(quit_item)
    quit_item.show()
    self.main_menu = main_menu
 
 
  def key_down(self, key):
    self.key_emu.key_down(key)
 
  def key_up(self, key):
    self.key_emu.key_up(key)
 
 
class NonPluggingWindow(gtk.Window):
  def __init__(self):
    super(NonPluggingWindow, self).__init__()
    self.set_events(gtk.gdk.ALL_EVENTS_MASK)
 
class PlugWindow(gtk.Plug, gtk.Window):
  def __init__(self):
    gtk.Plug.__init__(self, 0)
    gtk.Window.__init__(self)
 
 
def run(config):
  global _NEXT_KEYBOARD
  if not config["keyboards"]:
    sys.stdout.write("No keyboards found, exiting!\n")
    return
 
  _NEXT_KEYBOARD = config["default_keyboard"]
  while _NEXT_KEYBOARD:
    kb = config["keyboards"][_NEXT_KEYBOARD]
    _NEXT_KEYBOARD = None
    key_emu = keypress.KeyEmulator()
    mw = Main(config=config, kb=kb, key_emu=key_emu)
    key_emu.shutdown()