
# keymap.py
# represent a key map

# Copyright (c) 2005 by Matthias Urlichs <smurf@smurf.noris.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of Version 2 of the GNU General Public License as
# published by the Free Software Foundation. See the file COPYING.txt
# or (on Debian systems) /usr/share/common-licenses/GPL-2 for details.

"""\
This module contains the 'Keymap' class, which represents a particular
keyboard mapping.
"""

from sets import ImmutableSet

# This code implements a bitmask and associated bit values.
modlist=("shift","altgr","control","alt","shiftl","shiftr","controll","controlr","capsshift")

bitmods={}
modmods={}

modbits={}
n=1
for f in modlist:
	modbits[f] = n
	n = n*2
del n
del modlist

class Modifier(object):
	"""Encapsulate shift/alt/control/... state"""

	def __str__(self):
		if self.mods:
			return "+".join(self.mods)
		else:
			return "plain"
	def __repr__(self):
		return "M<%s>" % (self.__str__(),)

	def _calc_prio(self):
		"""Figure out the priority of a modifier"""
		if not self.mods:
			return 100
		if len(self.mods) == 1 and "shift" in self.mods:
			return 0
	
		pri=0
		mods=self.mods
		for mod,val in (("altgr",500),("alt",1000),("control",1500)):
			if mod in self.mods:
				pri -= val
				mods = mods-ImmutableSet(mod)
		
		if "shift" in mods:
			pri *= 1.5
			mods = mods-ImmutableSet("shift")
		pri *= 1+(len(mods)/3)
		return pri

	def _get_prio(self):
		"""read the priority"""
		if not hasattr(self,"_prio"):
			self._prio = self._calc_prio()
		return self._prio
	pri = property(_get_prio)

	def __new__(cls,mods=None,bits=None):
		"""Return an existing modifier (pseudo-Singleton)"""
		if bits is None:
			if mods is None:
				raise NameError
		elif mods is not None:
			raise NameError
		else:
			try:
				return bitmods[bits]
			except KeyError: # not found: calculate modifier list
				mods=[]
				for f,n in modbits.items():
					if bits & n:
						mods.append(f)

		mods = ImmutableSet(mods)
		try:
			return modmods[mods]
		except KeyError:
			m = object.__new__(cls)
			if bits is None:
				bits = 0
				for mod in mods:
					bits |= modbits[mod.lower()]
			m.bits = bits
			m.mods = mods
			modmods[mods] = m
			bitmods[bits] = m
			return m

	def __init__(self,*a,**k):
		pass

STD=Modifier(mods=())
SHIFT=Modifier(mods=("shift",))
ALT=Modifier(mods=("alt",))
ALTSHIFT=Modifier(mods=("alt","shift"))

#class CodeKnown(Exception):
#	"""Trying to add a code+modifier entry to a keymap that's already known"""
#	pass

class Keymap(object):
	"""encapsulates a named symbol <=> keycode+modifiers mapping"""
	
	def __init__(self,name):
		self.name = name
		self.sym2code = {} # sym => (code,modifier) list
		self.sym2cod = {} # sym => code list
#		self.code2sym = {} # <=
		self.submap_cache = {}
	
	def __str__(self):
		return "Map<%s>" % (self.name,)
	__repr__=__str__
	def __len__(self):
		return len(self.sym2cod)

	def add(self,sym,code,mod=STD):
		"""add a single key mapping to this keymap"""
		if sym == "":
			return
		if sym in self.sym2cod:
			if code not in self.sym2cod[sym]:
				self.sym2cod[sym].append(code)
		else:
			self.sym2cod[sym] = [code]

		code = (code,mod)
		if sym in self.sym2code:
			if code not in self.sym2code[sym]:
				self.sym2code[sym].append(code)
			return
#		if code in self.code2sym:
#			raise CodeKnown(self.name,code)
		self.sym2code[sym] = [code]
#		self.code2sym[code] = sym

	def symbols(self):
		"""Return a list of known symbols"""
		return self.sym2code.iterkeys()

	def priority(self,sym):
		"""Return how likely it is that this symbol is actually shown
		   on the physical keyboard"""
		if not self.sym2code[sym]:
			pass
		return reduce(max, [ k[1].pri for k in self.sym2code[sym] ])


	def dump(self):
		"""Show my mapping"""
		return self.name+": "+",".join([ "%s:%s" % (self.vis(s), "+".join([str(x) for x in c])) for s,c in self.sym2cod.items()])

	def vis(self,s):
		if len(s) == 1 and ord(s) < 128:
			return repr(s)[1:]
		else:
			return "'"+s+"'"

	def is_submap(self,m):
		"""Check if self is a submap of m."""
		try:
			return self.submap_cache[m]
		except KeyError:
			for s,cs in self.sym2cod.items():
				if s not in m.sym2cod:
					self.submap_cache[m] = False
					return False
				cm = m.sym2cod[s]
				for c in cs:
					if c not in cm:
						self.submap_cache[m] = False
						return False

			self.submap_cache[m] = True
			print "%s is a submap of %s" % (self,m)
			return True

