Esempio n. 1
0
class Menu(OSDWindow):
    EPILOG = """Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled menu
  -2  - clean exit, menu closed from callback method
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while menu is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""
    SUBMENU_OFFSET = 50
    PREFER_BW_ICONS = True

    def __init__(self, cls="osd-menu"):
        OSDWindow.__init__(self, cls)
        self.daemon = None
        self.config = None
        self.feedback = None
        self.controller = None
        self.xdisplay = X.Display(hash(
            GdkX11.x11_get_default_xdisplay()))  # Magic

        cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
        self.cursor = Gtk.Image.new_from_file(cursor)
        self.cursor.set_name("osd-menu-cursor")

        self.parent = self.create_parent()
        self.f = Gtk.Fixed()
        self.f.add(self.parent)
        self.add(self.f)

        self._submenu = None
        self._scon = StickController()
        self._scon.connect("direction", self.on_stick_direction)
        self._is_submenu = False
        self._selected = None
        self._menuid = None
        self._use_cursor = False
        self._eh_ids = []
        self._control_with = STICK
        self._confirm_with = 'A'
        self._cancel_with = 'B'

    def set_is_submenu(self):
        """
		Marks menu as submenu. This changes behaviour of some methods,
		especially disables (un)locking of input stick and buttons.
		"""
        self._is_submenu = True

    def create_parent(self):
        v = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        v.set_name("osd-menu")
        return v

    def pack_items(self, parent, items):
        for item in items:
            parent.pack_start(item.widget, True, True, 0)

    def use_daemon(self, d):
        """
		Allows (re)using already existing DaemonManager instance in same process.
		use_config() should be be called before parse_argumets() if this is used.
		"""
        self.daemon = d
        if not self._is_submenu:
            self._connect_handlers()
            self.on_daemon_connected(self.daemon)

    def use_config(self, c):
        """
		Allows reusing already existin Config instance in same process.
		Has to be called before parse_argumets()
		"""
        self.config = c

    def get_menuid(self):
        """
		Returns ID of used menu.
		"""
        return self._menuid

    def get_selected_item_id(self):
        """
		Returns ID of selected item or None if nothing is selected.
		"""
        if self._selected:
            return self._selected.id
        return None

    def _add_arguments(self):
        OSDWindow._add_arguments(self)
        self.argparser.add_argument(
            '--control-with',
            '-c',
            type=str,
            metavar="option",
            default=DEFAULT,
            choices=(DEFAULT, LEFT, RIGHT, STICK),
            help="which pad or stick should be used to navigate menu")
        self.argparser.add_argument('--confirm-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to confirm choice")
        self.argparser.add_argument('--cancel-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to cancel menu")
        self.argparser.add_argument(
            '--confirm-with-release',
            action='store_true',
            help="confirm choice with button release instead of button press")
        self.argparser.add_argument(
            '--cancel-with-release',
            action='store_true',
            help="cancel menu with button release instead of button press")
        self.argparser.add_argument('--use-cursor',
                                    '-u',
                                    action='store_true',
                                    help="display and use cursor")
        self.argparser.add_argument('--size',
                                    type=int,
                                    help="sets prefered width or height")
        self.argparser.add_argument(
            '--feedback-amplitude',
            type=int,
            help=
            "enables and sets power of feedback effect generated when active menu option is changed"
        )
        self.argparser.add_argument('--from-profile',
                                    '-p',
                                    type=str,
                                    metavar="profile_file menu_name",
                                    help="load menu items from profile file")
        self.argparser.add_argument('--from-file',
                                    '-f',
                                    type=str,
                                    metavar="filename",
                                    help="load menu items from json file")
        self.argparser.add_argument('--print-items',
                                    action='store_true',
                                    help="prints menu items to stdout")
        self.argparser.add_argument('items',
                                    type=str,
                                    nargs='*',
                                    metavar='id title',
                                    help="Menu items")

    @staticmethod
    def _get_on_screen_position(w):
        a = w.get_allocation()
        parent = w.get_parent()
        if parent:
            if isinstance(parent, Menu) and parent.get_window() is not None:
                x, y = parent.get_window().get_position()
            else:
                x, y = Menu._get_on_screen_position(parent)
            return a.x + x, a.y + y
        else:
            return a.x, a.y

    def parse_menu(self):
        if self.args.from_profile:
            try:
                self._menuid = self.args.items[0]
                self.items = MenuData.from_profile(self.args.from_profile,
                                                   self._menuid)
            except IOError:
                print >> sys.stderr, '%s: error: profile file not found' % (
                    sys.argv[0])
                return False
            except ValueError:
                print >> sys.stderr, '%s: error: menu not found' % (
                    sys.argv[0])
                return False
        elif self.args.from_file:
            try:
                self._menuid = self.args.from_file
                self.items = MenuData.from_file(self.args.from_file)
            except:
                print >> sys.stderr, '%s: error: failed to load menu file' % (
                    sys.argv[0])
                return False
        else:
            try:
                self.items = MenuData.from_args(self.args.items)
                self._menuid = None
            except ValueError:
                print >> sys.stderr, '%s: error: invalid number of arguments' % (
                    sys.argv[0])
                return False
        return True

    def parse_argumets(self, argv):
        if not OSDWindow.parse_argumets(self, argv):
            return False
        if not self.parse_menu():
            return False
        if not self.config:
            self.config = Config()

        # Parse simpler arguments
        self._size = self.args.size

        # Create buttons that are displayed on screen
        items = self.items.generate(self)
        self.items = []
        for item in items:
            item.widget = self.generate_widget(item)
            if item.widget is not None:
                self.items.append(item)
        self.pack_items(self.parent, self.items)
        if len(self.items) == 0:
            print >> sys.stderr, '%s: error: no items in menu' % (sys.argv[0])
            return False

        if self.args.print_items:
            max_id_len = max(*[len(x.id) for x in self.items])
            row_format = "{:>%s}:\t{}" % (max_id_len, )
            for item in self.items:
                print row_format.format(item.id, item.label)
        return True

    def enable_cursor(self):
        if not self._use_cursor:
            self.f.add(self.cursor)
            self.f.show_all()
            self._use_cursor = True

    def generate_widget(self, item):
        """ Generates gtk widget for specified menutitem """
        if isinstance(item, Separator) and item.label:
            widget = Gtk.Button.new_with_label(item.label)
            widget.set_relief(Gtk.ReliefStyle.NONE)
            widget.set_name("osd-menu-separator")
            return widget
        elif isinstance(item, Separator):
            widget = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
            widget.set_name("osd-menu-separator")
            return widget
        else:
            widget = Gtk.Button.new_with_label(item.label)
            widget.set_relief(Gtk.ReliefStyle.NONE)
            if hasattr(widget.get_children()[0], "set_xalign"):
                widget.get_children()[0].set_xalign(0)
            else:
                widget.get_children()[0].set_halign(Gtk.Align.START)
            if isinstance(item, Submenu):
                item.callback = self.show_submenu
                label1 = widget.get_children()[0]
                label2 = Gtk.Label(_(">>"))
                label2.set_property("margin-left", 30)
                box = Gtk.Box(Gtk.Orientation.HORIZONTAL)
                widget.remove(label1)
                box.pack_start(label1, True, True, 1)
                box.pack_start(label2, False, True, 1)
                widget.add(box)
                widget.set_name("osd-menu-item")
            elif item.id is None:
                widget.set_name("osd-menu-dummy")
            else:
                widget.set_name("osd-menu-item")

            if isinstance(item.icon, Gio.FileIcon):
                icon_file = item.icon.get_file().get_path()
                has_colors = True
            elif isinstance(item.icon, Gio.ThemedIcon):
                icon = Gtk.IconTheme.get_default().choose_icon(
                    item.icon.get_names(), 64, 0)
                icon_file = icon.get_filename() if icon else None
                has_colors = True
            else:
                icon_file, has_colors = find_icon(item.icon,
                                                  self.PREFER_BW_ICONS)

            if icon_file:
                icon = MenuIcon(icon_file, has_colors)
                label = widget.get_children()[0]
                for c in [] + widget.get_children():
                    widget.remove(c)
                box = Gtk.Box()
                box.pack_start(icon, False, True, 0)
                box.pack_start(label, True, True, 10)
                widget.add(box)

            return widget

    def select(self, index):
        if self._selected:
            self._selected.widget.set_name(
                self._selected.widget.get_name().replace("-selected", ""))
        if self.items[index].id:
            if self._selected != self.items[index]:
                if self.feedback and self.controller:
                    self.controller.feedback(*self.feedback)
            self._selected = self.items[index]
            self._selected.widget.set_name(self._selected.widget.get_name() +
                                           "-selected")
            GLib.timeout_add(2, self._check_on_screen_position)
            return True
        return False

    def _check_on_screen_position(self):
        x, y = Menu._get_on_screen_position(self._selected.widget)
        screen_height = self.get_window().get_screen().get_height()
        if y < 50:
            wx, wy = self.get_window().get_position()
            wy += 5
            self.get_window().move(wx, wy)
            GLib.timeout_add(2, self._check_on_screen_position)
        if y > screen_height - 100:
            wx, wy = self.get_window().get_position()
            wy -= 5
            self.get_window().move(wx, wy)
            GLib.timeout_add(2, self._check_on_screen_position)

    def _connect_handlers(self):
        self._eh_ids += [
            (self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('error', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('alive',
                                              self.on_daemon_connected)),
        ]

    def run(self):
        self.daemon = DaemonManager()
        self._connect_handlers()
        OSDWindow.run(self)

    def show(self, *a):
        if not self.select(0):
            self.next_item(1)
        OSDWindow.show(self, *a)

    def on_daemon_connected(self, *a):
        if not self.config:
            self.config = Config()
        self.controller = self.choose_controller(self.daemon)
        if self.controller is None or not self.controller.is_connected():
            # There is no controller connected to daemon
            self.on_failed_to_lock("Controller not connected")
            return
        self.use_controller(self.controller)

        self._eh_ids += [
            (self.controller, self.controller.connect('event', self.on_event)),
            (self.controller,
             self.controller.connect('lost', self.on_controller_lost)),
        ]
        self.lock_inputs()

    def use_controller(self, controller):
        ccfg = self.config.get_controller_config(controller.get_id())
        self._control_with = ccfg[
            "menu_control"] if self.args.control_with == DEFAULT else self.args.control_with
        self._cancel_with = ccfg[
            "menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with

        if self.args.confirm_with == DEFAULT:
            self._confirm_with = ccfg["menu_confirm"]
        elif self.args.confirm_with == SAME:
            if self._control_with == RIGHT:
                self._confirm_with = SCButtons.RPADTOUCH.name
            else:
                self._confirm_with = SCButtons.LPADTOUCH.name
        else:
            self._confirm_with = self.args.confirm_with

        if self.args.use_cursor:
            # As special case, using LEFT pad on controller with
            # actual DPAD should not display cursor
            if self._control_with != LEFT or (controller.get_flags()
                                              & ControllerFlags.HAS_DPAD) == 0:
                self.enable_cursor()

        if self.args.feedback_amplitude:
            side = "LEFT"
            if self._control_with == "RIGHT":
                side = "RIGHT"
            elif self._control_with == "STICK":
                side = "BOTH"
            self.feedback = side, int(self.args.feedback_amplitude)

    def lock_inputs(self):
        def success(*a):
            log.error("Sucessfully locked input")

        locks = [self._control_with, self._confirm_with, self._cancel_with]
        self.controller.lock(success, self.on_failed_to_lock, *locks)

    def quit(self, code=-2):
        if not self._is_submenu:
            if self.get_controller():
                self.get_controller().unlock_all()
            for source, eid in self._eh_ids:
                source.disconnect(eid)
            self._eh_ids = []
        OSDWindow.quit(self, code)

    def next_item(self, direction):
        """ Selects next menu item, based on self._direction """
        start, i = -1, 0
        try:
            start = self.items.index(self._selected)
            i = start + direction
        except:
            pass
        while True:
            if i == start:
                # Cannot find valid menu item
                self.select(start)
                break
            if i >= len(self.items):
                i = 0
                continue
            if i < 0:
                i = len(self.items) - 1
                continue
            if self.select(i):
                # Not a separator
                break
            i += direction
            if start < 0: start = 0

    def on_submenu_closed(self, *a):
        self.set_name("osd-menu")
        if self._submenu.get_exit_code() in (0, -2):
            self._menuid = self._submenu._menuid
            self._selected = self._submenu._selected
            self.quit(self._submenu.get_exit_code())
        self._submenu = None

    def show_submenu(self, trash, trash2, trash3, menuitem):
        """ Called when user chooses menu item pointing to submenu """
        filename = find_menu(menuitem.filename)
        if filename:
            self._submenu = self.__class__()
            sub_pos = list(self.position)
            for i in (0, 1):
                sub_pos[i] = (sub_pos[i] - self.SUBMENU_OFFSET
                              if sub_pos[i] < 0 else sub_pos[i] +
                              self.SUBMENU_OFFSET)

            self._submenu.use_config(self.config)
            self._submenu.parse_argumets([
                "menu.py", "-x",
                str(sub_pos[0]), "-y",
                str(sub_pos[1]), "--from-file", filename, "--control-with",
                self._control_with, "--confirm-with", self._confirm_with,
                "--cancel-with", self._cancel_with
            ])
            self._submenu.set_is_submenu()
            self._submenu.use_daemon(self.daemon)
            self._submenu.use_controller(self.controller)
            self._submenu.controller = self.controller
            self._submenu.connect('destroy', self.on_submenu_closed)
            self._submenu.show()
            self.set_name("osd-menu-inactive")

    def _control_equals_cancel(self, daemon, x, y):
        """
		Called by on_event in that very special case when both confirm_with
		and cancel_with are set to STICK.
		
		Separated because RadialMenu overrides on_event and still
		needs to call this.
		
		Returns True if menu was canceled.
		"""
        distance = sqrt(x * x + y * y)
        if distance < STICK_PAD_MAX / 8:
            self.quit(-1)
            return True
        return False

    def on_stick_direction(self, trash, x, y):
        if y != 0:
            self.next_item(y)

    def on_event(self, daemon, what, data):
        if self._submenu:
            return self._submenu.on_event(daemon, what, data)
        if what == self._control_with:
            x, y = data
            if self._use_cursor:
                # Special case, both confirm_with and cancel_with
                # can be set to STICK
                if self._cancel_with == STICK and self._control_with == STICK:
                    if self._control_equals_cancel(daemon, x, y):
                        return

                pad_w = self.cursor.get_allocation().width * 0.5
                pad_h = self.cursor.get_allocation().height * 0.5
                max_w = self.get_allocation().width - 2 * pad_w
                max_h = self.get_allocation().height - 2 * pad_h

                x, y = circle_to_square(x / (STICK_PAD_MAX * 2.0),
                                        y / (STICK_PAD_MAX * 2.0))
                x = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w,
                          max_w - pad_w)
                y = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1,
                          max_h - pad_h)
                self.f.move(self.cursor, int(x), int(y))

                for i in self.items:
                    if point_in_gtkrect(i.widget.get_allocation(), x, y):
                        self.select(self.items.index(i))
            else:
                self._scon.set_stick(x, y)
        elif what == self._cancel_with:
            if data[0] == 0:  # Button released
                self.quit(-1)
        elif what == self._confirm_with:
            if data[0] == 0:  # Button released
                if self._selected and self._selected.callback:
                    self._selected.callback(self, self.daemon, self.controller,
                                            self._selected)
                elif self._selected:
                    self.quit(0)
                else:
                    self.quit(-1)
Esempio n. 2
0
class Dialog(OSDWindow):
	EPILOG="""Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled dialog
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""
	
	def __init__(self, cls="osd-menu"):
		self._buttons = None
		self._text = None
		
		OSDWindow.__init__(self, cls)
		self.daemon = None
		self.config = None
		self.feedback = None
		self.controller = None
		self.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))	# Magic
		
		self.parent = self.create_parent()
		self.f = Gtk.Fixed()
		self.f.add(self.parent)
		self.add(self.f)
		
		self._scon = StickController()
		self._scon.connect("direction", self.on_stick_direction)
		self._selected = None
		self._eh_ids = []
	
	
	def create_parent(self):
		self._text = Gtk.Label()
		self._buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
		dialog = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		dialog.pack_start(self._text, True, True, 0)
		dialog.pack_start(self._buttons, True, True, 0)
		
		dialog.set_name("osd-dialog")
		self._buttons.set_name("osd-dialog-buttons")
		self._text.set_name("osd-dialog-text")
		return dialog
	
	
	def pack_items(self, parent, items):
		for item in items:
			if hasattr(item.widget, "set_alignment"):
				item.widget.set_alignment(0.5, 0.5)
			self._buttons.pack_end(item.widget, True, True, 0)
	
	
	def use_daemon(self, d):
		"""
		Allows (re)using already existing DaemonManager instance in same process.
		use_config() should be be called before parse_argumets() if this is used.
		"""
		self.daemon = d
		self._connect_handlers()
		self.on_daemon_connected(self.daemon)
	
	
	def use_config(self, c):
		"""
		Allows reusing already existin Config instance in same process.
		Has to be called before parse_argumets()
		"""
		self.config = c
	
	
	def get_menuid(self):
		# Just to be compatibile with menus when called from scc-osd-daemon
		return None
	
	
	def get_selected_item_id(self):
		"""
		Returns ID of selected item or None if nothing is selected.
		"""
		if self._selected:
			return self._selected.id
		return None
	
	
	def _add_arguments(self):
		OSDWindow._add_arguments(self)
		self.argparser.add_argument('--confirm-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to confirm choice")
		self.argparser.add_argument('--cancel-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to cancel dialog")
		self.argparser.add_argument('--feedback-amplitude', type=int,
			help="enables and sets power of feedback effect generated when active menu option is changed")
		self.argparser.add_argument('--text', type=str, metavar='text',
			help="Dialog text")
		self.argparser.add_argument('items', type=str, nargs='*', metavar='id text',
			help="Dialog buttons")
	
	
	def parse_argumets(self, argv):
		if not OSDWindow.parse_argumets(self, argv):
			return False
		if not self.config:
			self.config = Config()
		
		try:
			self.items = MenuData.from_args(self.args.items)
			self._menuid = None
		except ValueError:
			print >>sys.stderr, '%s: error: invalid number of arguments' % (sys.argv[0])
			return False
		
		self._text.set_label(self.args.text)
		
		if self.args.feedback_amplitude:
			side = "LEFT"
			self.feedback = side, int(self.args.feedback_amplitude)
		
		# Create buttons that are displayed on screen
		items = self.items.generate(self)
		self.items = []
		for item in items:
			item.widget = self.generate_widget(item)
			if item.widget is not None:
				self.items.append(item)
		self.pack_items(self.parent, self.items)
		if len(self.items) == 0:
			print >>sys.stderr, '%s: error: no items in menu' % (sys.argv[0])
			return False
		
		return True
	
	
	def generate_widget(self, item):
		""" Generates gtk widget for specified menutitem """
		widget = Gtk.Button.new_with_label(item.label)
		widget.set_relief(Gtk.ReliefStyle.NONE)
		if hasattr(widget.get_children()[0], "set_xalign"):
			widget.get_children()[0].set_xalign(0)
		else:
			widget.get_children()[0].set_halign(Gtk.Align.START)
		widget.set_name("osd-menu-item")
		
		return widget
	
	
	def select(self, index):
		if self._selected:
			self._selected.widget.set_name(self._selected.widget.get_name()
				.replace("-selected", ""))
		if self.items[index].id:
			if self._selected != self.items[index]:
				if self.feedback and self.controller:
					self.controller.feedback(*self.feedback)
			self._selected = self.items[index]
			self._selected.widget.set_name(
					self._selected.widget.get_name() + "-selected")
			return True
		return False
	
	
	def _connect_handlers(self):
		self._eh_ids += [
			(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('error', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),
		]
	
	
	def run(self):
		self.daemon = DaemonManager()
		self._connect_handlers()
		OSDWindow.run(self)
	
	
	def show(self, *a):
		if not self.select(0):
			self.next_item(1)
		OSDWindow.show(self, *a)
	
	
	def on_daemon_died(self, *a):
		log.error("Daemon died")
		self.quit(2)
	
	
	def on_failed_to_lock(self, error):
		log.error("Failed to lock input: %s", error)
		self.quit(3)
	
	
	def on_daemon_connected(self, *a):
		def success(*a):
			log.error("Sucessfully locked input")
		
		if not self.config:
			self.config = Config()
		self.controller = self.choose_controller(self.daemon)
		if self.controller is None or not self.controller.is_connected():
			# There is no controller connected to daemon
			self.on_failed_to_lock("Controller not connected")
			return
		
		ccfg = self.config.get_controller_config(self.controller.get_id())
		self._control_with = ccfg["menu_control"]
		self._confirm_with = ccfg["menu_confirm"] if self.args.confirm_with == DEFAULT else self.args.confirm_with
		self._cancel_with = ccfg["menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with
		
		self._eh_ids += [ (self.controller, self.controller.connect('event', self.on_event)) ]
		locks = [ self._control_with, self._confirm_with, self._cancel_with ]
		self.controller.lock(success, self.on_failed_to_lock, *locks)
	
	
	def quit(self, code=-2):
		if self.get_controller():
			self.get_controller().unlock_all()
		for source, eid in self._eh_ids:
			source.disconnect(eid)
		self._eh_ids = []
		OSDWindow.quit(self, code)
	
	
	def next_item(self, direction):
		""" Selects next menu item, based on self._direction """
		start, i = -1, 0
		try:
			start = self.items.index(self._selected)
			i = start + direction
		except: pass
		while True:
			if i == start:
				# Cannot find valid menu item
				self.select(start)
				break
			if i >= len(self.items):
				i = 0
				continue
			if i < 0:
				i = len(self.items) - 1
				continue
			if self.select(i): break
			i += direction
			if start < 0: start = 0
	
	
	def on_stick_direction(self, trash, x, y):
		if x != 0:
			self.next_item(x)
	
	
	def on_event(self, daemon, what, data):
		if what == self._control_with:
			self._scon.set_stick(*data)
		elif what == self._cancel_with:
			if data[0] == 0:	# Button released
				self.quit(-1)
		elif what == self._confirm_with:
			if data[0] == 0:	# Button released
				if self._selected and self._selected.callback:
					self._selected.callback(self, self.daemon, self.controller, self._selected)
				elif self._selected:
					self.quit(0)
				else:
					self.quit(-1)
Esempio n. 3
0
class Menu(OSDWindow):
	EPILOG="""Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled menu
  -2  - clean exit, menu closed from callback method
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while menu is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""
	SUBMENU_OFFSET = 50
	PREFER_BW_ICONS = True
	
	
	def __init__(self, cls="osd-menu"):
		OSDWindow.__init__(self, cls)
		self.daemon = None
		self.config = None
		self.feedback = None
		self.controller = None
		self.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))	# Magic
		
		cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
		self.cursor = Gtk.Image.new_from_file(cursor)
		self.cursor.set_name("osd-menu-cursor")
		
		self.parent = self.create_parent()
		self.f = Gtk.Fixed()
		self.f.add(self.parent)
		self.add(self.f)
		
		self._submenu = None
		self._scon = StickController()
		self._scon.connect("direction", self.on_stick_direction)
		self._is_submenu = False
		self._selected = None
		self._menuid = None
		self._use_cursor = False
		self._eh_ids = []
		self._control_with = STICK
		self._control_with_dpad = False
		self._confirm_with = 'A'
		self._cancel_with = 'B'
	
	
	def set_is_submenu(self):
		"""
		Marks menu as submenu. This changes behaviour of some methods,
		especially disables (un)locking of input stick and buttons.
		"""
		self._is_submenu = True
	
	
	def create_parent(self):
		v = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		v.set_name("osd-menu")
		return v
	
	
	def pack_items(self, parent, items):
		for item in items:
			parent.pack_start(item.widget, True, True, 0)
	
	
	def use_daemon(self, d):
		"""
		Allows (re)using already existing DaemonManager instance in same process.
		use_config() should be be called before parse_argumets() if this is used.
		"""
		self.daemon = d
		if not self._is_submenu:
			self._connect_handlers()
			self.on_daemon_connected(self.daemon)
	
	
	def use_config(self, c):
		"""
		Allows reusing already existin Config instance in same process.
		Has to be called before parse_argumets()
		"""
		self.config = c
	
	
	def get_menuid(self):
		"""
		Returns ID of used menu.
		"""
		return self._menuid
	
	
	def get_selected_item_id(self):
		"""
		Returns ID of selected item or None if nothing is selected.
		"""
		if self._selected:
			return self._selected.id
		return None
	
	
	def _add_arguments(self):
		OSDWindow._add_arguments(self)
		self.argparser.add_argument('--control-with', '-c', type=str,
			metavar="option", default=DEFAULT, choices=(DEFAULT, LEFT, RIGHT, STICK),
			help="which pad or stick should be used to navigate menu")
		self.argparser.add_argument('--confirm-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to confirm choice")
		self.argparser.add_argument('--cancel-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to cancel menu")
		self.argparser.add_argument('--confirm-with-release', action='store_true',
			help="confirm choice with button release instead of button press")
		self.argparser.add_argument('--cancel-with-release', action='store_true',
			help="cancel menu with button release instead of button press")
		self.argparser.add_argument('--use-cursor', '-u', action='store_true',
			help="display and use cursor")
		self.argparser.add_argument('--size', type=int,
			help="sets prefered width or height")
		self.argparser.add_argument('--feedback-amplitude', type=int,
			help="enables and sets power of feedback effect generated when active menu option is changed")
		self.argparser.add_argument('--from-profile', '-p', type=str,
			metavar="profile_file menu_name",
			help="load menu items from profile file")
		self.argparser.add_argument('--from-file', '-f', type=str,
			metavar="filename",
			help="load menu items from json file")
		self.argparser.add_argument('--print-items', action='store_true',
			help="prints menu items to stdout")
		self.argparser.add_argument('items', type=str, nargs='*', metavar='id title',
			help="Menu items")
	
	
	@staticmethod
	def _get_on_screen_position(w):
		a = w.get_allocation()
		parent = w.get_parent()
		if parent:
			if isinstance(parent, Menu) and parent.get_window() is not None:
				x, y = parent.get_window().get_position()
			else:
				x, y = Menu._get_on_screen_position(parent)
			return a.x + x, a.y + y
		else:
			return a.x, a.y
	
	
	def parse_menu(self):
		if self.args.from_profile:
			try:
				self._menuid = self.args.items[0]
				self.items = MenuData.from_profile(self.args.from_profile, self._menuid)
			except IOError:
				print >>sys.stderr, '%s: error: profile file not found' % (sys.argv[0])
				return False
			except ValueError:
				print >>sys.stderr, '%s: error: menu not found' % (sys.argv[0])
				return False
		elif self.args.from_file:
			try:
				self._menuid = self.args.from_file
				self.items = MenuData.from_file(self.args.from_file)
			except:
				print >>sys.stderr, '%s: error: failed to load menu file' % (sys.argv[0])
				return False
		else:
			try:
				self.items = MenuData.from_args(self.args.items)
				self._menuid = None
			except ValueError:
				print >>sys.stderr, '%s: error: invalid number of arguments' % (sys.argv[0])
				return False
		return True
	
	
	def parse_argumets(self, argv):
		if not OSDWindow.parse_argumets(self, argv):
			return False
		if not self.parse_menu():
			return False
		if not self.config:
			self.config = Config()
		
		# Parse simpler arguments
		self._size = self.args.size
		
		# Create buttons that are displayed on screen
		items = self.items.generate(self)
		self.items = []
		for item in items:
			item.widget = self.generate_widget(item)
			if item.widget is not None:
				self.items.append(item)
		self.pack_items(self.parent, self.items)
		if len(self.items) == 0:
			print >>sys.stderr, '%s: error: no items in menu' % (sys.argv[0])
			return False
		
		if self.args.print_items:
			max_id_len = max(*[ len(x.id) for x in self.items ])
			row_format ="{:>%s}:\t{}" % (max_id_len,)
			for item in self.items:
				print row_format.format(item.id, item.label)
		return True
	
	
	def enable_cursor(self):
		if not self._use_cursor:
			self.f.add(self.cursor)
			self.f.show_all()
			self._use_cursor = True
	
	
	def generate_widget(self, item):
		""" Generates gtk widget for specified menutitem """
		if isinstance(item, Separator) and item.label:
			widget = Gtk.Button.new_with_label(item.label)
			widget.set_relief(Gtk.ReliefStyle.NONE)
			widget.set_name("osd-menu-separator")
			return widget
		elif isinstance(item, Separator):
			widget = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
			widget.set_name("osd-menu-separator")
			return widget
		else:
			widget = Gtk.Button.new_with_label(item.label)
			widget.set_relief(Gtk.ReliefStyle.NONE)
			if hasattr(widget.get_children()[0], "set_xalign"):
				widget.get_children()[0].set_xalign(0)
			else:
				widget.get_children()[0].set_halign(Gtk.Align.START)
			if isinstance(item, Submenu):
				item.callback = self.show_submenu
				label1 = widget.get_children()[0]
				label2 = Gtk.Label(_(">>"))
				label2.set_property("margin-left", 30)
				box = Gtk.Box(Gtk.Orientation.HORIZONTAL)
				widget.remove(label1)
				box.pack_start(label1, True, True, 1)
				box.pack_start(label2, False, True, 1)
				widget.add(box)
				widget.set_name("osd-menu-item")
			elif item.id is None:
				widget.set_name("osd-menu-dummy")
			else:
				widget.set_name("osd-menu-item")
			
			if isinstance(item.icon, Gio.FileIcon):
				icon_file = item.icon.get_file().get_path()
				has_colors = True
			elif isinstance(item.icon, Gio.ThemedIcon):
				icon = Gtk.IconTheme.get_default().choose_icon(
					item.icon.get_names(), 64, 0)
				icon_file = icon.get_filename() if icon else None
				has_colors = True
			else:
				icon_file, has_colors = find_icon(item.icon, self.PREFER_BW_ICONS)
			
			if icon_file:
				icon = MenuIcon(icon_file, has_colors)
				label = widget.get_children()[0]
				for c in [] + widget.get_children():
					widget.remove(c)
				box = Gtk.Box()
				box.pack_start(icon,  False, True, 0)
				box.pack_start(label, True, True, 10)
				widget.add(box)
				
			return widget
	
	
	def select(self, index):
		if self._selected:
			self._selected.widget.set_name(self._selected.widget.get_name()
				.replace("-selected", ""))
		if self.items[index].id:
			if self._selected != self.items[index]:
				if self.feedback and self.controller:
					self.controller.feedback(*self.feedback)
			self._selected = self.items[index]
			self._selected.widget.set_name(
					self._selected.widget.get_name() + "-selected")
			GLib.timeout_add(2, self._check_on_screen_position)
			return True
		return False
	
	
	def _check_on_screen_position(self, quick=False):
		x, y = Menu._get_on_screen_position(self._selected.widget)
		try:
			m = self.get_window().get_display().get_monitor_at_window(self.get_window())
			assert m
			y_offset = m.get_geometry().y
			screen_height = m.get_geometry().height
		except:
			y_offset = 0
			screen_height = self.get_window().get_screen().get_height()
		y -= y_offset
		if y < 50:
			wx, wy = self.get_window().get_position()
			if quick:
				wy = 50 - (y - wy)
			else:
				wy += 5
				GLib.timeout_add(2, self._check_on_screen_position)
			self.get_window().move(wx, wy)
		if y > screen_height - 100:
			wx, wy = self.get_window().get_position()
			if quick:
				wy = screen_height - 100 - (y - wy)
			else:
				wy -= 5
				GLib.timeout_add(2, self._check_on_screen_position)
			self.get_window().move(wx, wy)
	
	
	def _connect_handlers(self):
		self._eh_ids += [
			(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('error', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),
		]
	
	
	def run(self):
		self.daemon = DaemonManager()
		self._connect_handlers()
		OSDWindow.run(self)
	
	
	def show(self, *a):
		if not self.select(0):
			self.next_item(1)
		OSDWindow.show(self, *a)
		GLib.timeout_add(1, self._check_on_screen_position, True)
	
	
	def on_daemon_connected(self, *a):
		if not self.config:
			self.config = Config()
		self.controller = self.choose_controller(self.daemon)
		if self.controller is None or not self.controller.is_connected():
			# There is no controller connected to daemon
			self.on_failed_to_lock("Controller not connected")
			return
		self.use_controller(self.controller)
		
		self._eh_ids += [
			(self.controller, self.controller.connect('event', self.on_event)),
			(self.controller, self.controller.connect('lost', self.on_controller_lost)),
		]
		self.lock_inputs()
	
	
	def use_controller(self, controller):
		ccfg = self.config.get_controller_config(controller.get_id())
		self._control_with = ccfg["menu_control"] if self.args.control_with == DEFAULT else self.args.control_with
		self._cancel_with = ccfg["menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with
		
		if self.args.confirm_with == DEFAULT:
			self._confirm_with = ccfg["menu_confirm"]
		elif self.args.confirm_with == SAME:
			if self._control_with == RIGHT:
				self._confirm_with = SCButtons.RPADTOUCH.name
			else:
				self._confirm_with = SCButtons.LPADTOUCH.name
		else:
			self._confirm_with = self.args.confirm_with
		
		if self.args.use_cursor:
			# As special case, using LEFT pad on controller with
			# actual DPAD should not display cursor
			if self._control_with != LEFT or (controller.get_flags() & ControllerFlags.HAS_DPAD) == 0:
				self.enable_cursor()
		
		if self.args.feedback_amplitude:
			side = "LEFT"
			if self._control_with == "RIGHT":
				side = "RIGHT"
			elif self._control_with == "STICK":
				side = "BOTH"
			self.feedback = side, int(self.args.feedback_amplitude)
	
	
	def lock_inputs(self):
		def success(*a):
			log.error("Sucessfully locked input")
		locks = [ self._control_with, self._confirm_with, self._cancel_with ]
		if self._control_with == "STICK":
			if self.controller.get_flags() & ControllerFlags.HAS_DPAD != 0:
				self._control_with_dpad = True
				locks += [ "LEFT" ]
		self.controller.lock(success, self.on_failed_to_lock, *locks)
	
	
	def quit(self, code=-2):
		if not self._is_submenu:
			if self.get_controller():
				self.get_controller().unlock_all()
			for source, eid in self._eh_ids:
				source.disconnect(eid)
			self._eh_ids = []
		OSDWindow.quit(self, code)
	
	
	def next_item(self, direction):
		""" Selects next menu item, based on self._direction """
		start, i = -1, 0
		try:
			start = self.items.index(self._selected)
			i = start + direction
		except: pass
		while True:
			if i == start:
				# Cannot find valid menu item
				self.select(start)
				break
			if i >= len(self.items):
				i = 0
				GLib.timeout_add(1, self._check_on_screen_position, True)
				continue
			if i < 0:
				i = len(self.items) - 1
				GLib.timeout_add(1, self._check_on_screen_position, True)
				continue
			if self.select(i):
				# Not a separator
				break
			i += direction
			if start < 0: start = 0
	
	
	def on_submenu_closed(self, *a):
		self.set_name("osd-menu")
		if self._submenu.get_exit_code() in (0, -2):
			self._menuid = self._submenu._menuid
			self._selected = self._submenu._selected
			self.quit(self._submenu.get_exit_code())
		self._submenu = None
	
	
	def show_submenu(self, trash, trash2, trash3, menuitem):
		""" Called when user chooses menu item pointing to submenu """
		filename = find_menu(menuitem.filename)
		if filename:
			self._submenu = self.__class__()
			sub_pos = list(self.position)
			for i in (0, 1):
				sub_pos[i] = (sub_pos[i] - self.SUBMENU_OFFSET
						if sub_pos[i] < 0 else sub_pos[i] + self.SUBMENU_OFFSET)
					
			self._submenu.use_config(self.config)
			self._submenu.parse_argumets(["menu.py",
				"-x", str(sub_pos[0]), "-y", str(sub_pos[1]),
			 	"--from-file", filename,
				"--control-with", self._control_with,
				"--confirm-with", self._confirm_with,
				"--cancel-with", self._cancel_with
			])
			self._submenu.set_is_submenu()
			self._submenu.use_daemon(self.daemon)
			self._submenu.use_controller(self.controller)
			self._submenu.controller = self.controller
			self._submenu.connect('destroy', self.on_submenu_closed)
			self._submenu.show()
			self.set_name("osd-menu-inactive")
	
	
	def _control_equals_cancel(self, daemon, x, y):
		"""
		Called by on_event in that very special case when both confirm_with
		and cancel_with are set to STICK.
		
		Separated because RadialMenu overrides on_event and still
		needs to call this.
		
		Returns True if menu was canceled.
		"""
		distance = sqrt(x*x + y*y)
		if distance < STICK_PAD_MAX / 8:
			self.quit(-1)
			return True
		return False
	
	
	def on_stick_direction(self, trash, x, y):
		if y != 0:
			self.next_item(y)
	
	
	def on_event(self, daemon, what, data):
		if self._submenu:
			return self._submenu.on_event(daemon, what, data)
		if what == self._control_with or what == "LEFT" and self._control_with_dpad:
			x, y = data
			if self._use_cursor:
				# Special case, both confirm_with and cancel_with
				# can be set to STICK
				if self._cancel_with == STICK and self._control_with == STICK:
					if self._control_equals_cancel(daemon, x, y):
						return
				
				pad_w = self.cursor.get_allocation().width * 0.5
				pad_h = self.cursor.get_allocation().height * 0.5
				max_w = self.get_allocation().width - 2 * pad_w
				max_h = self.get_allocation().height - 2 * pad_h
				
				x, y = circle_to_square(x / (STICK_PAD_MAX * 2.0), y / (STICK_PAD_MAX * 2.0))
				x = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w, max_w - pad_w)
				y = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1, max_h - pad_h)
				self.f.move(self.cursor, int(x), int(y))
				
				for i in self.items:
					if point_in_gtkrect(i.widget.get_allocation(), x, y):
						self.select(self.items.index(i))
			else:
				self._scon.set_stick(x, y)
		elif what == self._cancel_with:
			if data[0] == 0:	# Button released
				self.quit(-1)
		elif what == self._confirm_with:
			if data[0] == 0:	# Button released
				if self._selected and self._selected.callback:
					self._selected.callback(self, self.daemon, self.controller, self._selected)
				elif self._selected:
					self.quit(0)
				else:
					self.quit(-1)
class Dialog(OSDWindow):
    EPILOG = """Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled dialog
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""

    def __init__(self, cls="osd-menu"):
        self._buttons = None
        self._text = None

        OSDWindow.__init__(self, cls)
        self.daemon = None
        self.config = None
        self.feedback = None
        self.controller = None
        self.xdisplay = X.Display(hash(
            GdkX11.x11_get_default_xdisplay()))  # Magic

        self.parent = self.create_parent()
        self.f = Gtk.Fixed()
        self.f.add(self.parent)
        self.add(self.f)

        self._scon = StickController()
        self._scon.connect("direction", self.on_stick_direction)
        self._selected = None
        self._eh_ids = []

    def create_parent(self):
        self._text = Gtk.Label()
        self._buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        dialog = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        dialog.pack_start(self._text, True, True, 0)
        dialog.pack_start(self._buttons, True, True, 0)

        dialog.set_name("osd-dialog")
        self._buttons.set_name("osd-dialog-buttons")
        self._text.set_name("osd-dialog-text")
        return dialog

    def pack_items(self, parent, items):
        for item in items:
            if hasattr(item.widget, "set_alignment"):
                item.widget.set_alignment(0.5, 0.5)
            self._buttons.pack_end(item.widget, True, True, 0)

    def use_daemon(self, d):
        """
		Allows (re)using already existing DaemonManager instance in same process.
		use_config() should be be called before parse_argumets() if this is used.
		"""
        self.daemon = d
        self._connect_handlers()
        self.on_daemon_connected(self.daemon)

    def use_config(self, c):
        """
		Allows reusing already existin Config instance in same process.
		Has to be called before parse_argumets()
		"""
        self.config = c

    def get_menuid(self):
        # Just to be compatibile with menus when called from scc-osd-daemon
        return None

    def get_selected_item_id(self):
        """
		Returns ID of selected item or None if nothing is selected.
		"""
        if self._selected:
            return self._selected.id
        return None

    def _add_arguments(self):
        OSDWindow._add_arguments(self)
        self.argparser.add_argument('--confirm-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to confirm choice")
        self.argparser.add_argument('--cancel-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to cancel dialog")
        self.argparser.add_argument(
            '--feedback-amplitude',
            type=int,
            help=
            "enables and sets power of feedback effect generated when active menu option is changed"
        )
        self.argparser.add_argument('--text',
                                    type=str,
                                    metavar='text',
                                    help="Dialog text")
        self.argparser.add_argument('items',
                                    type=str,
                                    nargs='*',
                                    metavar='id text',
                                    help="Dialog buttons")

    def parse_argumets(self, argv):
        if not OSDWindow.parse_argumets(self, argv):
            return False
        if not self.config:
            self.config = Config()

        try:
            self.items = MenuData.from_args(self.args.items)
            self._menuid = None
        except ValueError:
            print >> sys.stderr, '%s: error: invalid number of arguments' % (
                sys.argv[0])
            return False

        self._text.set_label(self.args.text)

        if self.args.feedback_amplitude:
            side = "LEFT"
            self.feedback = side, int(self.args.feedback_amplitude)

        # Create buttons that are displayed on screen
        items = self.items.generate(self)
        self.items = []
        for item in items:
            item.widget = self.generate_widget(item)
            if item.widget is not None:
                self.items.append(item)
        self.pack_items(self.parent, self.items)
        if len(self.items) == 0:
            print >> sys.stderr, '%s: error: no items in menu' % (sys.argv[0])
            return False

        return True

    def generate_widget(self, item):
        """ Generates gtk widget for specified menutitem """
        widget = Gtk.Button.new_with_label(item.label)
        widget.set_relief(Gtk.ReliefStyle.NONE)
        if hasattr(widget.get_children()[0], "set_xalign"):
            widget.get_children()[0].set_xalign(0)
        else:
            widget.get_children()[0].set_halign(Gtk.Align.START)
        widget.set_name("osd-menu-item")

        return widget

    def select(self, index):
        if self._selected:
            self._selected.widget.set_name(
                self._selected.widget.get_name().replace("-selected", ""))
        if self.items[index].id:
            if self._selected != self.items[index]:
                if self.feedback and self.controller:
                    self.controller.feedback(*self.feedback)
            self._selected = self.items[index]
            self._selected.widget.set_name(self._selected.widget.get_name() +
                                           "-selected")
            return True
        return False

    def _connect_handlers(self):
        self._eh_ids += [
            (self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('error', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('alive',
                                              self.on_daemon_connected)),
        ]

    def run(self):
        self.daemon = DaemonManager()
        self._connect_handlers()
        OSDWindow.run(self)

    def show(self, *a):
        if not self.select(0):
            self.next_item(1)
        OSDWindow.show(self, *a)

    def on_daemon_died(self, *a):
        log.error("Daemon died")
        self.quit(2)

    def on_failed_to_lock(self, error):
        log.error("Failed to lock input: %s", error)
        self.quit(3)

    def on_daemon_connected(self, *a):
        def success(*a):
            log.error("Sucessfully locked input")

        if not self.config:
            self.config = Config()
        self.controller = self.choose_controller(self.daemon)
        if self.controller is None or not self.controller.is_connected():
            # There is no controller connected to daemon
            self.on_failed_to_lock("Controller not connected")
            return

        ccfg = self.config.get_controller_config(self.controller.get_id())
        self._control_with = ccfg["menu_control"]
        self._confirm_with = ccfg[
            "menu_confirm"] if self.args.confirm_with == DEFAULT else self.args.confirm_with
        self._cancel_with = ccfg[
            "menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with

        self._eh_ids += [(self.controller,
                          self.controller.connect('event', self.on_event))]
        locks = [self._control_with, self._confirm_with, self._cancel_with]
        self.controller.lock(success, self.on_failed_to_lock, *locks)

    def quit(self, code=-2):
        if self.get_controller():
            self.get_controller().unlock_all()
        for source, eid in self._eh_ids:
            source.disconnect(eid)
        self._eh_ids = []
        OSDWindow.quit(self, code)

    def next_item(self, direction):
        """ Selects next menu item, based on self._direction """
        start, i = -1, 0
        try:
            start = self.items.index(self._selected)
            i = start + direction
        except:
            pass
        while True:
            if i == start:
                # Cannot find valid menu item
                self.select(start)
                break
            if i >= len(self.items):
                i = 0
                continue
            if i < 0:
                i = len(self.items) - 1
                continue
            if self.select(i): break
            i += direction
            if start < 0: start = 0

    def on_stick_direction(self, trash, x, y):
        if x != 0:
            self.next_item(x)

    def on_event(self, daemon, what, data):
        if what == self._control_with:
            self._scon.set_stick(*data)
        elif what == self._cancel_with:
            if data[0] == 0:  # Button released
                self.quit(-1)
        elif what == self._confirm_with:
            if data[0] == 0:  # Button released
                if self._selected and self._selected.callback:
                    self._selected.callback(self, self.daemon, self.controller,
                                            self._selected)
                elif self._selected:
                    self.quit(0)
                else:
                    self.quit(-1)
Esempio n. 5
0
class Launcher(OSDWindow):
    EPILOG = """Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled dialog
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""

    BUTTONS = [
        "1", "2 ABC", "3 DEF", "5 GHI", "5 JKL", "6 MNO", "7 PQRS", "8 TUV",
        "9 WXYZ", "", "0"
    ]

    VALID_CHARS = "12ABC3DEF5GHI5JKL6MNO7PQRS8TUV9WXYZ0"
    CHAR_TO_NUMBER = {}  # Generated on runtime

    MAX_ROWS = 5

    _app_db = None  # Static list of all know applications

    def __init__(self, cls="osd-menu"):
        self._buttons = None
        self._string = ""

        OSDWindow.__init__(self, cls)
        self.daemon = None
        self.config = None
        self.feedback = None
        self.controller = None
        self.xdisplay = X.Display(hash(
            GdkX11.x11_get_default_xdisplay()))  # Magic

        self.create_parent()
        self.create_app_list()
        self.create_buttons()

        cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
        self.cursors = [
            Gtk.Image.new_from_file(cursor),
            Gtk.Image.new_from_file(cursor)
        ]
        for c in self.cursors:
            c.set_name("osd-menu-cursor")
            c.selected = None
            self.f.add(c)
        self.f.show_all()

        self._scon = StickController()
        self._scon.connect("direction", self.on_stick_direction)
        self._selected = None
        self._menuid = None
        self._eh_ids = []
        self._confirm_with = 'A'
        self._cancel_with = 'B'

        if Launcher._app_db is None:
            Launcher._app_db = []
            for x in Launcher.BUTTONS:
                for c in x:
                    Launcher.CHAR_TO_NUMBER[c] = x[0]

            for x in Gio.AppInfo.get_all():
                try:
                    Launcher._app_db.append((Launcher.name_to_keys(x), x))
                except UnicodeDecodeError:
                    # Just f**k them...
                    pass

    @staticmethod
    def name_to_keys(appinfo):
        name = "".join([
            Launcher.CHAR_TO_NUMBER[x]
            for x in appinfo.get_display_name().upper()
            if x in Launcher.VALID_CHARS
        ])
        return name

    @staticmethod
    def string_to_keys_and_spaces(string):
        name = "".join([
            Launcher.CHAR_TO_NUMBER[x] if x in Launcher.VALID_CHARS else " "
            for x in string.upper()
        ])
        return name

    def create_parent(self):
        self.parent = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.parent.set_name("osd-dialog")
        self.f = Gtk.Fixed()
        self.f.add(self.parent)
        self.add(self.f)

    def create_app_list(self):
        lst = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        lst.set_name("osd-application-list")
        self.items = [self.generate_widget("") for x in xrange(self.MAX_ROWS)]
        for a in self.items:
            lst.pack_start(a, False, True, 0)
        self.parent.pack_start(lst, True, True, 0)
        self._set_launchers([])
        lst.show_all()

    def create_buttons(self):
        self.grid = Gtk.Grid()
        self.parent.pack_start(self.grid, True, True, 0)
        self._buttons = []

        x, y = 0, 0
        for label in self.BUTTONS:
            if label:
                w = self.generate_widget(label)
                w.set_name("osd-key-buton")
                self.grid.attach(w, x, y, 1, 1)
                self._buttons.append(w)
            x += 1
            if x > 2:
                x = 0
                y += 1

        w = self.generate_widget(_("Run"))
        self.grid.attach(w, x, y, 1, 1)

        self.grid.set_name("osd-dialog-buttons")

    def pack_items(self, parent, items):
        for item in items:
            if hasattr(item.widget, "set_alignment"):
                item.widget.set_alignment(0.5, 0.5)
            self._buttons.pack_end(item.widget, True, True, 0)

    def use_daemon(self, d):
        """
		Allows (re)using already existing DaemonManager instance in same process.
		use_config() should be be called before parse_argumets() if this is used.
		"""
        self.daemon = d
        self._connect_handlers()
        self.on_daemon_connected(self.daemon)

    def use_config(self, c):
        """
		Allows reusing already existin Config instance in same process.
		Has to be called before parse_argumets()
		"""
        self.config = c

    def get_menuid(self):
        """
		Returns ID of used menu.
		"""
        return None

    def get_selected_item_id(self):
        """
		Returns ID of selected item or None if nothing is selected.
		"""
        return None

    def _launch(self):
        self._selected.launcher.launch()

    def _add_arguments(self):
        OSDWindow._add_arguments(self)
        self.argparser.add_argument('--confirm-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to confirm choice")
        self.argparser.add_argument('--cancel-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to cancel dialog")
        self.argparser.add_argument(
            '--feedback-amplitude',
            type=int,
            help=
            "enables and sets power of feedback effect generated when active menu option is changed"
        )

    def parse_argumets(self, argv):
        if not OSDWindow.parse_argumets(self, argv):
            return False
        if not self.config:
            self.config = Config()

        if self.args.feedback_amplitude:
            side = "LEFT"
            self.feedback = side, int(self.args.feedback_amplitude)

        # Create buttons that are displayed on screen
        return True

    def _set_launchers(self, launchers):
        launchers = launchers[0:self.MAX_ROWS]
        for x in self.items:
            x.set_label("")
            x.set_name("osd-hidden-item")
            x.launcher = None
        for i in xrange(0, len(launchers)):
            self.items[i].set_name("osd-launcher-item")
            self.items[i].launcher = launchers[i]
            label = self.items[i].get_children()[0]
            label.set_markup(self._format_label_markup(launchers[i]))
            label.set_max_width_chars(1)
            label.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
            label.set_xalign(0)

    def _format_label_markup(self, label):
        if hasattr(label, "get_display_name"):
            label = label.get_display_name()
        else:
            label = str(label)

        def _check(substr):
            i, ch = 0, self._string
            while len(substr) > 0 and substr[0] == ch[0]:
                ch = ch[1:]
                substr = substr[1:]
                i += 1
                if len(ch) == 0: return i
            while len(substr) > 0 and substr[0] == " ":
                substr = substr[1:]
                i += 1
            return -1

        keys = Launcher.string_to_keys_and_spaces(label)
        index1, index2 = -1, -1
        for i in xrange(0, len(keys)):
            if keys[i] == self._string[0]:
                index2 = _check(keys[i:])
                if index2 > 0:
                    index1 = i
                    index2 = i + index2
                    break

        label = "%s<span color='#%s'>%s</span>%s" % (
            label[0:index1],
            self.config["osd_colors"]["menuitem_hilight_text"],
            label[index1:index2], label[index2:])
        return label

    def _update_items(self):
        if len(self._string) > 0:
            gen = (item for (keys, item) in self._app_db
                   if self._string in keys)
            launchers = []
            for i in gen:
                launchers.append(i)
                if len(launchers) > self.MAX_ROWS: break
            self._set_launchers(launchers)
            self.select(0)
        else:
            self._set_launchers([])

    def generate_widget(self, label):
        """ Generates gtk widget for specified menutitem """
        if hasattr(label, "label"): label = label.label
        widget = Gtk.Button.new_with_label(label)
        widget.set_relief(Gtk.ReliefStyle.NONE)
        if hasattr(widget.get_children()[0], "set_xalign"):
            widget.get_children()[0].set_xalign(0)
        else:
            widget.get_children()[0].set_halign(Gtk.Align.START)
        widget.set_name("osd-menu-item")

        return widget

    def select(self, index):
        if self._selected:
            self._selected.set_name(self._selected.get_name().replace(
                "-selected", ""))
            self._selected = None
        if self.items[index].launcher is not None:
            self._selected = self.items[index]
            self._selected.set_name(self._selected.get_name() + "-selected")
            return True
        return False

    def _connect_handlers(self):
        self._eh_ids += [
            (self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('error', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('alive',
                                              self.on_daemon_connected)),
        ]

    def run(self):
        self.daemon = DaemonManager()
        self._connect_handlers()
        OSDWindow.run(self)

    def show(self, *a):
        if not self.select(0):
            self.next_item(1)
        OSDWindow.show(self, *a)
        for c in self.cursors:
            c.set_visible(False)

    def on_daemon_connected(self, *a):
        def success(*a):
            log.error("Sucessfully locked input")

        if not self.config:
            self.config = Config()
        self.controller = self.choose_controller(self.daemon)
        if self.controller is None or not self.controller.is_connected():
            # There is no controller connected to daemon
            self.on_failed_to_lock("Controller not connected")
            return

        ccfg = self.config.get_controller_config(self.controller.get_id())
        self._confirm_with = ccfg[
            "menu_confirm"] if self.args.confirm_with == DEFAULT else self.args.confirm_with
        self._cancel_with = ccfg[
            "menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with

        self._eh_ids += [
            (self.controller, self.controller.connect('event', self.on_event)),
            (self.controller,
             self.controller.connect('lost', self.on_controller_lost)),
        ]
        locks = [
            LEFT, RIGHT, STICK, "LPAD", "RPAD", "LB", self._confirm_with,
            self._cancel_with
        ]
        self.controller.lock(success, self.on_failed_to_lock, *locks)

    def quit(self, code=-2):
        if self.get_controller():
            self.get_controller().unlock_all()
        for source, eid in self._eh_ids:
            source.disconnect(eid)
        self._eh_ids = []
        OSDWindow.quit(self, code)

    def next_item(self, direction):
        """ Selects next menu item, based on self._direction """
        start, i = -1, 0
        try:
            start = self.items.index(self._selected)
            i = start + direction
        except:
            pass
        while True:
            if i == start:
                # Cannot find valid menu item
                self.select(start)
                break
            if i >= len(self.items):
                i = 0
                continue
            if i < 0:
                i = len(self.items) - 1
                continue
            if self.select(i): break
            i += direction
            if start < 0: start = 0

    def on_stick_direction(self, trash, x, y):
        if y != 0:
            self.next_item(y)

    def _move_cursor(self, cursor, x, y):
        if (x, y) == (0, 0):
            cursor.set_visible(False)
            return
        cursor.set_visible(True)
        pad_w = cursor.get_allocation().width * 0.5
        pad_h = cursor.get_allocation().height * 0.5
        max_w = self.grid.get_allocation().width - 2 * pad_w
        max_h = self.grid.get_allocation().height - 2 * pad_h

        x, y = circle_to_square(x / (STICK_PAD_MAX * 2.0),
                                y / (STICK_PAD_MAX * 2.0))
        x = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w, max_w - pad_w)
        y = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1, max_h - pad_h)
        x += self.grid.get_allocation().x
        y += self.grid.get_allocation().y
        self.f.move(cursor, int(x), int(y))

        for i in self._buttons:
            if point_in_gtkrect(i.get_allocation(), x, y):
                if cursor.selected:
                    cursor.selected.set_name("osd-key-buton")
                cursor.selected = i
                cursor.selected.set_name("osd-key-buton-hilight")
                break

    def _get_under_cursor(self, cursor):
        x, y = self.f.child_get(cursor, "x", "y")
        for i in self._buttons:
            if point_in_gtkrect(i.get_allocation(), x, y):
                return i
        return None

    def on_event(self, daemon, what, data):
        if what == LEFT:
            self._move_cursor(self.cursors[0], *data)
        elif what == "LPAD" and data[0] == 1:
            b = self._get_under_cursor(self.cursors[0])
            if b: self._string += b.get_label()[0]
            self._update_items()
        elif what == RIGHT:
            self._move_cursor(self.cursors[1], *data)
        elif what == "RPAD" and data[0] == 1:
            b = self._get_under_cursor(self.cursors[1])
            if b: self._string += b.get_label()[0]
            self._update_items()
        elif what == "LB":
            if len(self._string) > 0:
                self._string = self._string[:-1]
                self._update_items()
        elif what == STICK:
            self._scon.set_stick(*data)
        elif what == self._cancel_with:
            if data[0] == 0:  # Button released
                self.quit(-1)
        elif what == self._confirm_with:
            if data[0] == 0:  # Button released
                if self._selected:
                    self._launch()
                    self.quit(0)
                else:
                    self.quit(-1)
Esempio n. 6
0
class Launcher(OSDWindow):
	EPILOG="""Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled dialog
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""
	
	BUTTONS = [
		"1",	 	"2 ABC",	"3 DEF",
		"5 GHI", 	"5 JKL",	"6 MNO",
		"7 PQRS",	"8 TUV",	"9 WXYZ",
		"", 		"0"
	]
	
	VALID_CHARS = "12ABC3DEF5GHI5JKL6MNO7PQRS8TUV9WXYZ0"
	CHAR_TO_NUMBER = { }	# Generated on runtime
	
	MAX_ROWS = 5
	
	_app_db = None	# Static list of all know applications
	
	def __init__(self, cls="osd-menu"):
		self._buttons = None
		self._string = ""
		
		OSDWindow.__init__(self, cls)
		self.daemon = None
		self.config = None
		self.feedback = None
		self.controller = None
		self.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))	# Magic
		
		self.create_parent()
		self.create_app_list()
		self.create_buttons()
		
		cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
		self.cursors = [ Gtk.Image.new_from_file(cursor), Gtk.Image.new_from_file(cursor) ]
		for c in self.cursors:
			c.set_name("osd-menu-cursor")
			c.selected = None
			self.f.add(c)
		self.f.show_all()
		
		self._scon = StickController()
		self._scon.connect("direction", self.on_stick_direction)
		self._selected = None
		self._menuid = None
		self._eh_ids = []
		self._confirm_with = 'A'
		self._cancel_with = 'B'
		
		if Launcher._app_db is None:
			Launcher._app_db = []
			for x in Launcher.BUTTONS:
				for c in x:
					Launcher.CHAR_TO_NUMBER[c] = x[0]
			
			for x in Gio.AppInfo.get_all():
				try:
					Launcher._app_db.append(( Launcher.name_to_keys(x), x ))
				except UnicodeDecodeError:
					# Just f**k them...
					pass
	
	
	@staticmethod
	def name_to_keys(appinfo):
		name = "".join([
			Launcher.CHAR_TO_NUMBER[x]
			for x in appinfo.get_display_name().upper()
			if x in Launcher.VALID_CHARS
		])
		return name
	
	
	@staticmethod
	def string_to_keys_and_spaces(string):
		name = "".join([
			Launcher.CHAR_TO_NUMBER[x] if x in Launcher.VALID_CHARS else " "
			for x in string.upper()
		])
		return name
	
	
	def create_parent(self):
		self.parent = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		self.parent.set_name("osd-dialog")
		self.f = Gtk.Fixed()
		self.f.add(self.parent)
		self.add(self.f)
	
	
	def create_app_list(self):
		lst = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		lst.set_name("osd-application-list")
		self.items = [ self.generate_widget("") for x in xrange(self.MAX_ROWS) ]
		for a in self.items:
			lst.pack_start(a, False, True, 0)
		self.parent.pack_start(lst, True, True, 0)
		self._set_launchers([  ])
		lst.show_all()
	
	
	def create_buttons(self):
		self.grid = Gtk.Grid()
		self.parent.pack_start(self.grid, True, True, 0)
		self._buttons = []
		
		x, y = 0, 0
		for label in self.BUTTONS:
			if label:
				w = self.generate_widget(label)
				w.set_name("osd-key-buton")
				self.grid.attach(w, x, y, 1, 1)
				self._buttons.append(w)
			x += 1
			if x > 2:
				x = 0
				y += 1
		
		
		w = self.generate_widget(_("Run"))
		self.grid.attach(w, x, y, 1, 1)
		
		
		self.grid.set_name("osd-dialog-buttons")
	
	
	def pack_items(self, parent, items):
		for item in items:
			if hasattr(item.widget, "set_alignment"):
				item.widget.set_alignment(0.5, 0.5)
			self._buttons.pack_end(item.widget, True, True, 0)
	
	
	def use_daemon(self, d):
		"""
		Allows (re)using already existing DaemonManager instance in same process.
		use_config() should be be called before parse_argumets() if this is used.
		"""
		self.daemon = d
		self._connect_handlers()
		self.on_daemon_connected(self.daemon)
	
	
	def use_config(self, c):
		"""
		Allows reusing already existin Config instance in same process.
		Has to be called before parse_argumets()
		"""
		self.config = c
	
	
	def get_menuid(self):
		"""
		Returns ID of used menu.
		"""
		return None
	
	
	def get_selected_item_id(self):
		"""
		Returns ID of selected item or None if nothing is selected.
		"""
		return None
	
	
	def _launch(self):
		self._selected.launcher.launch()
	
	
	def _add_arguments(self):
		OSDWindow._add_arguments(self)
		self.argparser.add_argument('--confirm-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to confirm choice")
		self.argparser.add_argument('--cancel-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to cancel dialog")
		self.argparser.add_argument('--feedback-amplitude', type=int,
			help="enables and sets power of feedback effect generated when active menu option is changed")
	
	
	def parse_argumets(self, argv):
		if not OSDWindow.parse_argumets(self, argv):
			return False
		if not self.config:
			self.config = Config()
		
		if self.args.feedback_amplitude:
			side = "LEFT"
			self.feedback = side, int(self.args.feedback_amplitude)
		
		# Create buttons that are displayed on screen
		return True
	
	
	def _set_launchers(self, launchers):
		launchers = launchers[0:self.MAX_ROWS]
		for x in self.items:
			x.set_label("")
			x.set_name("osd-hidden-item")
			x.launcher = None
		for i in xrange(0, len(launchers)):
			self.items[i].set_name("osd-launcher-item")
			self.items[i].launcher = launchers[i]
			label = self.items[i].get_children()[0]
			label.set_markup(self._format_label_markup(launchers[i]))
			label.set_max_width_chars(1)
			label.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
			label.set_xalign(0)
	
	
	def _format_label_markup(self, label):
		if hasattr(label, "get_display_name"):
			label = label.get_display_name()
		else:
			label = str(label)
		
		def _check(substr):
			i, ch = 0, self._string
			while len(substr) > 0 and substr[0] == ch[0]:
				ch = ch[1:]
				substr = substr[1:]
				i += 1
				if len(ch) == 0: return i
			while len(substr) > 0 and substr[0] == " ":
				substr = substr[1:]
				i += 1
			return -1
			
		keys = Launcher.string_to_keys_and_spaces(label)
		index1, index2 = -1, -1
		for i in xrange(0, len(keys)):
			if keys[i] == self._string[0]:
				index2 = _check(keys[i:])
				if index2 > 0:
					index1 = i
					index2 = i + index2
					break
		
		label = "%s<span color='#%s'>%s</span>%s" % (
			label[0:index1],
			self.config["osd_colors"]["menuitem_hilight_text"],
			label[index1:index2],
			label[index2:]
		)
		return label
	
	
	def _update_items(self):
		if len(self._string) > 0:
			gen = ( item for (keys, item) in self._app_db if self._string in keys )
			launchers = []
			for i in gen:
				launchers.append(i)
				if len(launchers) > self.MAX_ROWS: break
			self._set_launchers(launchers)
			self.select(0)
		else:
			self._set_launchers([])
	
	
	def generate_widget(self, label):
		""" Generates gtk widget for specified menutitem """
		if hasattr(label, "label"): label = label.label
		widget = Gtk.Button.new_with_label(label)
		widget.set_relief(Gtk.ReliefStyle.NONE)
		if hasattr(widget.get_children()[0], "set_xalign"):
			widget.get_children()[0].set_xalign(0)
		else:
			widget.get_children()[0].set_halign(Gtk.Align.START)
		widget.set_name("osd-menu-item")
		
		return widget
	
	
	def select(self, index):
		if self._selected:
			self._selected.set_name(self._selected.get_name()
				.replace("-selected", ""))
			self._selected = None
		if self.items[index].launcher is not None:
			self._selected = self.items[index]
			self._selected.set_name(
					self._selected.get_name() + "-selected")
			return True
		return False
	
	
	def _connect_handlers(self):
		self._eh_ids += [
			(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('error', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),
		]
	
	
	def run(self):
		self.daemon = DaemonManager()
		self._connect_handlers()
		OSDWindow.run(self)
	
	
	def show(self, *a):
		if not self.select(0):
			self.next_item(1)
		OSDWindow.show(self, *a)
		for c in self.cursors:
			c.set_visible(False)
	
	
	def on_daemon_connected(self, *a):
		def success(*a):
			log.error("Sucessfully locked input")
		
		if not self.config:
			self.config = Config()
		self.controller = self.choose_controller(self.daemon)
		if self.controller is None or not self.controller.is_connected():
			# There is no controller connected to daemon
			self.on_failed_to_lock("Controller not connected")
			return
		
		ccfg = self.config.get_controller_config(self.controller.get_id())
		self._confirm_with = ccfg["menu_confirm"] if self.args.confirm_with == DEFAULT else self.args.confirm_with
		self._cancel_with = ccfg["menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with
		
		self._eh_ids += [
			(self.controller, self.controller.connect('event', self.on_event)),
			(self.controller, self.controller.connect('lost', self.on_controller_lost)),
		]
		locks = [ LEFT, RIGHT, STICK, "LPAD", "RPAD", "LB",
			self._confirm_with, self._cancel_with ]
		self.controller.lock(success, self.on_failed_to_lock, *locks)
	
	
	def quit(self, code=-2):
		if self.get_controller():
			self.get_controller().unlock_all()
		for source, eid in self._eh_ids:
			source.disconnect(eid)
		self._eh_ids = []
		OSDWindow.quit(self, code)
	
	
	def next_item(self, direction):
		""" Selects next menu item, based on self._direction """
		start, i = -1, 0
		try:
			start = self.items.index(self._selected)
			i = start + direction
		except: pass
		while True:
			if i == start:
				# Cannot find valid menu item
				self.select(start)
				break
			if i >= len(self.items):
				i = 0
				continue
			if i < 0:
				i = len(self.items) - 1
				continue
			if self.select(i): break
			i += direction
			if start < 0: start = 0
	
	
	def on_stick_direction(self, trash, x, y):
		if y != 0:
			self.next_item(y)
	
	
	def _move_cursor(self, cursor, x, y):
		if (x, y) == (0, 0):
			cursor.set_visible(False)
			return
		cursor.set_visible(True)
		pad_w = cursor.get_allocation().width * 0.5
		pad_h = cursor.get_allocation().height * 0.5
		max_w = self.grid.get_allocation().width - 2 * pad_w
		max_h = self.grid.get_allocation().height - 2 * pad_h
		
		x, y = circle_to_square(x / (STICK_PAD_MAX * 2.0), y / (STICK_PAD_MAX * 2.0))
		x = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w, max_w - pad_w)
		y = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1, max_h - pad_h)
		x += self.grid.get_allocation().x
		y += self.grid.get_allocation().y
		self.f.move(cursor, int(x), int(y))
		
		for i in self._buttons:
			if point_in_gtkrect(i.get_allocation(), x, y):
				if cursor.selected:
					cursor.selected.set_name("osd-key-buton")
				cursor.selected = i
				cursor.selected.set_name("osd-key-buton-hilight")
				break
	
	
	def _get_under_cursor(self, cursor):
		x, y = self.f.child_get(cursor, "x", "y")
		for i in self._buttons:
			if point_in_gtkrect(i.get_allocation(), x, y):
				return i
		return None
	
	
	def on_event(self, daemon, what, data):
		if what == LEFT:
			self._move_cursor(self.cursors[0], *data)
		elif what == "LPAD" and data[0] == 1:
			b = self._get_under_cursor(self.cursors[0])
			if b: self._string += b.get_label()[0]
			self._update_items()
		elif what == RIGHT:
			self._move_cursor(self.cursors[1], *data)
		elif what == "RPAD" and data[0] == 1:
			b = self._get_under_cursor(self.cursors[1])
			if b: self._string += b.get_label()[0]
			self._update_items()
		elif what == "LB":
			if len(self._string) > 0:
				self._string = self._string[:-1]
				self._update_items()
		elif what == STICK:
			self._scon.set_stick(*data)
		elif what == self._cancel_with:
			if data[0] == 0:	# Button released
				self.quit(-1)
		elif what == self._confirm_with:
			if data[0] == 0:	# Button released
				if self._selected:
					self._launch()
					self.quit(0)
				else:
					self.quit(-1)