"""SC-Controller - Controller Registration.

Dialog that asks a lot of questions to create configuration node in config file.
Most "interesting" thing here may be that this works 100% independently from
daemon.
"""

import json
import logging
import os
import re

import evdev
from gi.repository import GdkPixbuf, GLib, Gtk

from scc.config import Config
from scc.constants import STICK_PAD_MAX, STICK_PAD_MIN, SCButtons
from scc.gui import BUTTON_ORDER
from scc.gui.app import App
from scc.gui.controller_image import ControllerImage
from scc.gui.creg.constants import (
	AXIS_ORDER,
	SDL_AXES,
	SDL_DPAD,
	SDL_TO_SCC_NAMES,
	STICK_PAD_AREAS,
	TRIGGER_AREAS,
)
from scc.gui.creg.data import AxisData, DPadEmuData
from scc.gui.creg.grabs import InputGrabber, StickGrabber, TriggerGrabber
from scc.gui.creg.tester import Tester
from scc.gui.editor import Editor
from scc.paths import get_config_path, get_share_path
from scc.tools import _, clamp, nameof

log = logging.getLogger("CRegistration")


class ControllerRegistration(Editor):
	GLADE = "creg.glade"
	UNASSIGNED_COLOR = "#FFFF0000"  # ARGB
	OBSERVE_COLORS = (
		App.OBSERVE_COLOR,
		# Following just replaces 'full alpha' in ARGB with various alpha values
		App.OBSERVE_COLOR.replace("#FF", "#DF"),
		App.OBSERVE_COLOR.replace("#FF", "#BF"),
		App.OBSERVE_COLOR.replace("#FF", "#9F"),
		App.OBSERVE_COLOR.replace("#FF", "#7F"),
	)

	def __init__(self, app):
		Editor.__init__(self)
		self.app = app
		self._gamepad_icon = GdkPixbuf.Pixbuf.new_from_file(
			os.path.join(self.app.imagepath, "controller-icons", "evdev-0.svg"),
		)
		self._other_icon = GdkPixbuf.Pixbuf.new_from_file(
			os.path.join(self.app.imagepath, "controller-icons", "unknown.svg"),
		)
		self._axis_data = [AxisData(name, xy) for (name, xy) in AXIS_ORDER]
		self.setup_widgets()
		self._controller_image = None
		self._evdevice = None
		self._tester = None
		self._grabber = None
		self._input_axes = {}
		self._mappings = {}
		self._hilights = {}
		self._unassigned = set()
		self._hilighted_area = None
		self.refresh_devices()

	def setup_widgets(self):
		Editor.setup_widgets(self)
		cursors = {}
		for axis in self._axis_data:
			if "trig" in axis.name:
				continue
			axis.cursor = cursors[axis.area] = cursors.get(axis.area) or Gtk.Image.new_from_file(
				os.path.join(self.app.imagepath, "test-cursor.svg"),
			)
			axis.cursor.position = [0, 0]
		self.builder.get_object("cbInvert_1").set_active(True)
		self.builder.get_object("cbInvert_3").set_active(True)
		self.builder.get_object("cbInvert_5").set_active(True)

	@staticmethod
	def does_he_looks_like_a_gamepad(dev):
		"""Examines device capabilities and decides if it passes for gamepad.
		Device is considered gamepad-like if has at least one button with
		keycode in gamepad range and at least two axes.
		"""
		# Strip special symbols
		name = re.sub(r"[^a-zA-Z0-9.]+", " ", dev.name.lower())

		# ... but some cheating first
		if "keyboard" in name:
			return False
		if "touchpad" in name:
			return False
		if "mouse" in name:
			return False
		if "gamepad" in name:
			return True
		caps = dev.capabilities(verbose=False)
		if evdev.ecodes.EV_ABS in caps:  # Has axes
			if evdev.ecodes.EV_KEY in caps:  # Has buttons
				for button in caps[evdev.ecodes.EV_KEY]:
					if button >= evdev.ecodes.BTN_0 and button <= evdev.ecodes.BTN_GEAR_UP:
						return True
		return False

	def load_sdl_mappings(self):
		"""Attempts to load mappings from gamecontrollerdb.txt.

		Return True on success.
		"""
		# Build list of button and axes
		buttons = self._tester.buttons
		axes = self._tester.axes

		# Generate database ID
		wordswap = lambda i: ((i & 0xFF) << 8) | ((i & 0xFF00) >> 8)
		# TODO: version?
		weird_id = "%.4x%.8x%.8x%.8x0000" % (
			wordswap(self._evdevice.info.bustype),
			wordswap(self._evdevice.info.vendor),
			wordswap(self._evdevice.info.product),
			wordswap(self._evdevice.info.version),
		)

		# Search in database
		try:
			db = open(os.path.join(get_share_path(), "gamecontrollerdb.txt"))
		except Exception as e:
			log.error("Failed to load gamecontrollerdb")
			log.exception(e)
			return False

		with db:
			for line in db:
				if line.startswith(weird_id):
					log.info("Loading mappings for '%s' from gamecontrollerdb", weird_id)
					log.debug("Buttons: %s", buttons)
					log.debug("Axes: %s", axes)
					for token in line.strip().split(","):
						if ":" in token:
							k, v = token.split(":", 1)
							k = SDL_TO_SCC_NAMES.get(k, k)
							if v.startswith("b") and hasattr(SCButtons, k.upper()):
								try:
									keycode = buttons[int(v.strip("b"))]
								except IndexError:
									log.warning("Skipping unknown gamecontrollerdb button->button mapping: '%s'", v)
									continue
								button = getattr(SCButtons, k.upper())
								self._mappings[keycode] = button
							elif v.startswith("b") and k in SDL_AXES:
								try:
									keycode = buttons[int(v.strip("b"))]
								except IndexError:
									log.warning("Skipping unknown gamecontrollerdb button->axis mapping: '%s'", v)
									continue
								log.info("Adding button -> axis mapping for %s", k)
								self._mappings[keycode] = self._axis_data[SDL_AXES.index(k)]
								self._mappings[keycode].min = STICK_PAD_MIN
								self._mappings[keycode].max = STICK_PAD_MAX
							elif v.startswith("h") and 16 in axes and 17 in axes:
								# Special case for evdev hatswitch
								if v == "h0.1" and k == "dpup":
									self._mappings[16] = self._axis_data[SDL_AXES.index("dpadx")]
									self._mappings[17] = self._axis_data[SDL_AXES.index("dpady")]
							elif k in SDL_AXES:
								try:
									code = axes[int(v.strip("a"))]
								except IndexError:
									log.warning("Skipping unknown gamecontrollerdb axis: '%s'", v)
									continue
								self._mappings[code] = self._axis_data[SDL_AXES.index(k)]
							elif k in SDL_DPAD and v.startswith("b"):
								try:
									keycode = buttons[int(v.strip("b"))]
								except IndexError:
									log.warning("Skipping unknown gamecontrollerdb button->dpad mapping: %s", v)
									continue
								index, positive = SDL_DPAD[k]
								data = DPadEmuData(self._axis_data[index], positive)
								self._mappings[keycode] = data
							elif k == "platform":
								# Not interesting
								pass
							else:
								log.warning("Skipping unknown gamecontrollerdb mapping %s:%s", k, v)
					return True
			log.debug("Mappings for '%s' not found in gamecontrollerdb", weird_id)

		return False

	def generate_mappings(self):
		"""Generates initial mappings, just to have some preset to show.
		"""
		buttons = list(BUTTON_ORDER)
		axes = list(self._axis_data)
		log.info("Generating default mappings")
		log.debug("Buttons: %s", self._tester.buttons)
		log.debug("Axes: %s", self._tester.axes)
		for axis in self._tester.axes:
			self._mappings[axis], axes = axes[0], axes[1:]
			if len(axes) == 0:
				break
		for button in self._tester.buttons:
			self._mappings[button], buttons = buttons[0], buttons[1:]
			if len(buttons) == 0:
				break

	def generate_unassigned(self):
		unassigned = set()
		unassigned.clear()
		assigned_axes = set([x for x in self._mappings.values() if isinstance(x, AxisData)])
		assigned_axes.update([x.axis_data for x in self._mappings.values() if isinstance(x, DPadEmuData)])
		assigned_buttons = set([x for x in self._mappings.values() if x in SCButtons.__members__.values()])
		assigned_buttons.update([x.button for x in self._mappings.values() if isinstance(x, DPadEmuData)])
		for a in BUTTON_ORDER:
			if a not in assigned_buttons:
				if a not in (SCButtons.RGRIP, SCButtons.LGRIP):
					# Grips are not colored red as most of controllers doesn't
					# have them anyway
					unassigned.add(nameof(a))
		for a in TRIGGER_AREAS:
			axis = self._axis_data[TRIGGER_AREAS[a]]
			if axis in assigned_axes and a in unassigned:
				unassigned.remove(a)
			elif axis not in assigned_axes:
				unassigned.add(a)
		for a in STICK_PAD_AREAS:
			area_name, axes = STICK_PAD_AREAS[a]
			has_mapping = bool(sum([self._axis_data[index] in assigned_axes for index in axes]))
			if not has_mapping:
				unassigned.add(area_name)

		hilight = unassigned - self._unassigned
		unhilight = self._unassigned - unassigned
		self._unassigned = unassigned
		for a in hilight:
			self.hilight(a, self.UNASSIGNED_COLOR)
		for a in unhilight:
			self.unhilight(a)

	def generate_raw_data(self):
		cbControllerButtons = self.builder.get_object("cbControllerButtons")
		cbControllerType = self.builder.get_object("cbControllerType")
		buffRawData = self.builder.get_object("buffRawData")
		config = dict(
			buttons={},
			axes={},
			dpads={},
		)

		def axis_to_json(axisdata):
			index = self._axis_data.index(axisdata)
			target_axis, xy = AXIS_ORDER[index]
			min, max = axisdata.min, axisdata.max
			if axisdata.invert:
				min, max = max, min

			rv = dict(axis=target_axis, min=min, max=max)
			if target_axis not in ("ltrig", "rtrig"):
				# Deadzone is generated with assumption that all sticks are left
				# in center position before 'Save' is pressed.
				center = axisdata.min + (axisdata.max - axisdata.min) / 2
				deadzone = abs(axisdata.pos - center) * 2 + 2
				if abs(axisdata.max) < 2:
					# DPADs
					deadzone = 0
				rv["deadzone"] = deadzone

			return rv

		for code, target in self._mappings.items():
			if target in SCButtons.__members__.values():
				config["buttons"][code] = nameof(target)
			elif isinstance(target, DPadEmuData):
				config["dpads"][code] = axis_to_json(target.axis_data)
				config["dpads"][code]["positive"] = target.positive
				config["dpads"][code]["button"] = nameof(target.button)
			elif isinstance(target, AxisData):
				config["axes"][code] = axis_to_json(target)

		group = cbControllerButtons.get_model()[cbControllerButtons.get_active()][0]
		controller = cbControllerType.get_model()[cbControllerType.get_active()][0]
		config["gui"] = {"background": controller, "buttons": self._groups[group]}

		buffRawData.set_text(json.dumps(config, sort_keys=True, indent=4, separators=(",", ": ")))

	def load_buttons(self):
		cbControllerButtons = self.builder.get_object("cbControllerButtons")
		self._groups = {}
		model = cbControllerButtons.get_model()
		groups = json.loads(open(os.path.join(self.app.imagepath, "button-images", "groups.json")).read())
		for group in groups:
			images = [
				GdkPixbuf.Pixbuf.new_from_file(os.path.join(self.app.imagepath, "button-images", "%s.svg" % (b,)))
				for b in group["buttons"][0:4]
			]
			model.append([group["key"]] + images)
			self._groups[group["key"]] = group["buttons"]
		cbControllerButtons.set_active(0)

	def save_registration(self):
		self.generate_raw_data()
		buffRawData = self.builder.get_object("buffRawData")
		jsondata = buffRawData.get_text(buffRawData.get_start_iter(), buffRawData.get_end_iter(), True)
		try:
			os.makedirs(os.path.join(get_config_path(), "devices"))
		except:
			pass

		filename = self._evdevice.name.strip().replace("/", "")
		if self._tester.driver == "hid":
			filename = "%.4x:%.4x-%s" % (self._evdevice.info.vendor, self._evdevice.info.product, filename)

		config_file = os.path.join(
			get_config_path(),
			"devices",
			"%s-%s.json"
			% (
				self._tester.driver,
				filename,
			),
		)

		open(config_file, "w").write(jsondata)
		log.debug("Controller configuration '%s' written", config_file)

		self.kill_tester()
		self.window.destroy()
		GLib.timeout_add_seconds(1, self.app.dm.rescan)

	def on_buffRawData_changed(self, buffRawData, *a):
		btNext = self.builder.get_object("btNext")
		jsondata = buffRawData.get_text(buffRawData.get_start_iter(), buffRawData.get_end_iter(), True)
		try:
			json.loads(jsondata)
			btNext.set_sensitive(True)
		except Exception:
			# User can modify generated json code before hitting save,
			# but if he writes something unparsable, save button is disabled
			btNext.set_sensitive(False)

	def on_ibHIDWarning_response(self, *a):
		rvHIDWarning = self.builder.get_object("rvHIDWarning")
		rvHIDWarning.set_reveal_child(False)

	def on_btNext_clicked(self, *a):
		rvController = self.builder.get_object("rvController")
		tvDevices = self.builder.get_object("tvDevices")
		stDialog = self.builder.get_object("stDialog")
		btBack = self.builder.get_object("btBack")
		btNext = self.builder.get_object("btNext")
		pages = stDialog.get_children()
		index = pages.index(stDialog.get_visible_child())
		if index == 0:
			model, iter = tvDevices.get_selection().get_selected()
			dev = evdev.InputDevice(model[iter][0])
			if (dev.info.vendor, dev.info.product) == (0x054C, 0x09CC):
				# Special case for PS4 controller
				cbDS4 = self.builder.get_object("cbDS4")
				imgDS4 = self.builder.get_object("imgDS4")
				imgDS4.set_from_file(os.path.join(self.app.imagepath, "ds4-small.svg"))
				cbDS4.set_active(Config()["drivers"]["ds4drv"])
				stDialog.set_visible_child(pages[3])
				btBack.set_sensitive(True)
				btNext.set_label("_Restart Emulation")
				return
			stDialog.set_visible_child(pages[1])
			self.load_buttons()
			self.refresh_controller_image()
			rvController.set_reveal_child(True)
			self.load_buttons()
			self.refresh_controller_image()
			btBack.set_sensitive(True)
		elif index == 1:
			# Disable Next button and determine which driver should be used
			btNext.set_sensitive(False)
			model, iter = tvDevices.get_selection().get_selected()
			dev = evdev.InputDevice(model[iter][0])
			self.prepare_registration(dev)
		elif index == 2:
			self.save_registration()
		elif index == 3:
			# Next pressed on DS4 info page
			# where it means 'Restart Emulation and close'
			self.app.dm.stop()
			GLib.timeout_add_seconds(1, self.app.dm.start)
			self.kill_tester()
			self.window.destroy()

	def on_btBack_clicked(self, *a):
		stDialog = self.builder.get_object("stDialog")
		btBack = self.builder.get_object("btBack")
		btNext = self.builder.get_object("btNext")
		pages = stDialog.get_children()
		index = pages.index(stDialog.get_visible_child())
		if index == 1:
			stDialog.set_visible_child(pages[0])
			btBack.set_sensitive(False)
			btNext.set_sensitive(True)
		elif index == 2:
			stDialog.set_visible_child(pages[1])
			rvController = self.builder.get_object("rvController")
			self._controller_image.get_parent().remove(self._controller_image)
			rvController.add(self._controller_image)
			btNext.set_label("_Next")
		elif index == 3:
			stDialog.set_visible_child(pages[0])
			btNext.set_label("_Next")
			btBack.set_sensitive(False)
			btNext.set_sensitive(True)

	def on_cbDS4_toggled(self, button):
		config = Config()
		config["drivers"]["ds4drv"] = button.get_active()
		config.save()

	def prepare_registration(self, dev):
		self._evdevice = dev
		self.set_hid_enabled(True)

		def retry_with_evdev(tester, code):
			if tester:
				for s in tester.__signals:
					tester.disconnect(s)
				tester.stop()
			self.set_hid_enabled(False)
			if code != 0:
				log.error("HID driver test failed (code %s)", code)
			if code == 2:
				# Special case, device may be usable, but there is no access
				rvHIDWarning = self.builder.get_object("rvHIDWarning")
				rvHIDWarning.set_reveal_child(False)
			log.debug(f"Trying to use '{dev.path}' with evdev driver...")
			self._tester = Tester("evdev", dev.path)
			self._tester.__signals = [
				self._tester.connect("ready", self.on_registration_ready),
				self._tester.connect("error", self.on_device_open_failed),
			]
			self._tester.start()

		# Check VID+PID info and whether HID driver support is enabled
		if (dev.info.vendor == 0 and dev.info.product == 0) or not self.app.config["drivers"].get("hiddrv"):
			# Not an USB device, skip HID test altogether
			retry_with_evdev(None, 0)
		else:
			log.debug("Trying to use %.4x:%.4x with HID driver...", dev.info.vendor, dev.info.product)
			self._tester = Tester("hid", "%.4x:%.4x" % (dev.info.vendor, dev.info.product))
			self._tester.__signals = [
				self._tester.connect("ready", self.on_registration_ready),
				self._tester.connect("error", retry_with_evdev),
			]
			self._tester.start()

	def on_registration_ready(self, tester):
		cbAccessMode = self.builder.get_object("cbAccessMode")
		fxController = self.builder.get_object("fxController")
		cbEmulateC = self.builder.get_object("cbEmulateC")
		stDialog = self.builder.get_object("stDialog")
		btNext = self.builder.get_object("btNext")

		self.set_cb(cbAccessMode, tester.driver)
		cbAccessMode.set_sensitive(True)

		if not self._mappings:
			self._mappings = {}
			if not self.load_sdl_mappings():
				self.generate_mappings()
			self.generate_unassigned()
			self.generate_raw_data()

		for s in tester.__signals:
			tester.disconnect(s)
		tester.__signals = [
			tester.connect("axis", self.on_tester_axis),
			tester.connect("button", self.on_tester_button),
		]

		self._controller_image.get_parent().remove(self._controller_image)
		fxController.add(self._controller_image)
		pages = stDialog.get_children()
		stDialog.set_visible_child(pages[2])
		cbEmulateC.grab_focus()
		btNext.set_label("_Save")
		btNext.set_sensitive(True)

	def on_device_open_failed(self, *a):
		"""Called when all (or user-selected) driver fails
		to communicate with controller.

		Shoudln't be really possible, but something
		_has_ to happen in such case.
		"""
		d = Gtk.MessageDialog(
			parent=self.window,
			flags=Gtk.DialogFlags.MODAL,
			type=Gtk.MessageType.ERROR,
			buttons=Gtk.ButtonsType.OK,
			message_format=_("Failed to open device"),
		)
		d.run()
		d.destroy()
		self.window.destroy()

	def kill_tester(self, *a):
		"""Called when window is closed"""
		if self._tester:
			tester, self._tester = self._tester, None
			for s in tester.__signals:
				tester.disconnect(s)
			tester.stop()

	def set_hid_enabled(self, enabled):
		"""Enables or disables option to use HID driver"""
		cbAccessMode = self.builder.get_object("cbAccessMode")
		for x in cbAccessMode.get_model():
			if x[0] == "hid":
				x[1] = _("USB HID (recommended)") if enabled else _("USB HID")
				x[2] = enabled

	def on_cbAccessMode_changed(self, cb):
		if self._tester:
			btNext = self.builder.get_object("btNext")
			target = cb.get_model().get_value(cb.get_active_iter(), 0)
			if self._tester.driver != target:
				# User changed driver that should be used, a lot of stuff has
				# to be restarted
				log.debug("User-requested driver change")
				self.kill_tester()
				cb.set_sensitive(False)
				btNext.set_sensitive(False)
				if target == "hid":
					self._tester = Tester(
						"hid", "%.4x:%.4x" % (self._evdevice.info.vendor, self._evdevice.info.product),
					)
				else:
					self._tester = Tester("evdev", self._evdevice.path)
				self._tester.__signals = [
					self._tester.connect("ready", self.on_registration_ready),
					self._tester.connect("error", self.on_device_open_failed),
				]
				GLib.timeout_add_seconds(1, self._tester.start)

	def cbInvert_toggled_cb(self, cb, *a):
		index = int(cb.get_name().split("_")[-1])
		self._axis_data[index].invert = cb.get_active()

	def on_tester_button(self, tester, keycode, pressed):
		if self._grabber:
			return self._grabber.on_button(keycode, pressed)

		what = self._mappings.get(keycode)
		if isinstance(what, AxisData):
			if pressed:
				self.hilight_axis(what, STICK_PAD_MAX)
			else:
				self.hilight_axis(what, STICK_PAD_MIN)
		if isinstance(what, DPadEmuData):
			if pressed:
				self.hilight(nameof(what.button))
				if what.positive:
					self.hilight_axis(what.axis_data, STICK_PAD_MAX)
				else:
					self.hilight_axis(what.axis_data, STICK_PAD_MIN)
			else:
				self.unhilight(nameof(what.button))
				self.hilight_axis(what.axis_data, 0)
		elif what is not None:
			if pressed:
				self.hilight(nameof(what))
			else:
				self.unhilight(nameof(what))

	def on_tester_axis(self, tester, number, value):
		self._input_axes[number] = value
		if self._grabber:
			return self._grabber.on_axis(number, value)

		axis = self._mappings.get(number)
		if axis:
			self.hilight_axis(axis, value)

	def hilight_axis(self, axis, value):
		cursor = axis.cursor
		if cursor is None:
			changed, value = axis.set_position(value)
			value = clamp(STICK_PAD_MIN, value, STICK_PAD_MAX)
			# In this very specific case, trigger uses same min/max as stick
			if value > STICK_PAD_MAX * 2 / 3:
				self.hilight(axis.area, self.OBSERVE_COLORS[0])
			elif value > STICK_PAD_MAX * 1 / 3:
				self.hilight(axis.area, self.OBSERVE_COLORS[1])
			elif value > 0:
				self.hilight(axis.area, self.OBSERVE_COLORS[2])
			elif value > STICK_PAD_MIN * 1 / 3:
				self.hilight(axis.area, self.OBSERVE_COLORS[3])
			elif value > STICK_PAD_MIN * 2 / 3:
				self.hilight(axis.area, self.OBSERVE_COLORS[4])
			else:
				self.unhilight(axis.area)
			# Update raw data if needed
			if changed:
				self.generate_raw_data()
		else:
			parent = cursor.get_parent()
			if parent is None:
				parent = self._controller_image.get_parent()
				parent.add(cursor)
				cursor.show()
			# Make position
			changed, value = axis.set_position(value)
			cursor.position[axis.xy] = clamp(STICK_PAD_MIN, value, STICK_PAD_MAX)
			px, py = cursor.position
			# Grab values
			try:
				trash, ay, trash, ah = self._controller_image.get_area_position(axis.area)
				ax, trash, aw, trash = self._controller_image.get_area_position(axis.area + "TEST")
			except ValueError:
				# Area not found
				cursor.set_visible(False)
				return
			cw = cursor.get_allocation().width
			# Compute center
			x, y = ax + aw * 0.5 - cw * 0.5, ay + aw * 0.5 - cw * 0.5
			# Add pad position
			x += px * aw / STICK_PAD_MAX * 0.5
			y -= py * aw / STICK_PAD_MAX * 0.5
			# Move circle
			parent.move(cursor, x, y)
			cursor.set_visible(True)
			# Update raw data if needed
			if changed:
				self.generate_raw_data()

	def hilight(self, what, color=None):
		self._hilights[what] = color or self.OBSERVE_COLORS[0]
		self._controller_image.hilight(self._hilights)

	def unhilight(self, what):
		if what in self._hilights:
			del self._hilights[what]
		if what in self._unassigned:
			self._hilights[what] = self.UNASSIGNED_COLOR
		self._controller_image.hilight(self._hilights)

	def on_exAdditionalOptions_activate(self, ex):
		rv = self.builder.get_object("rvAdditionalOptions")
		rv.set_reveal_child(not ex.get_expanded())

	def on_exRawData_activate(self, ex):
		rv = self.builder.get_object("rvRawData")
		dialog = self.builder.get_object("Dialog")
		rv.set_reveal_child(not ex.get_expanded())
		if not ex.get_expanded():
			dialog.set_resizable(True)

	def on_area_hover(self, trash, what):
		self.on_area_leave()
		self._hilighted_area = what
		self.hilight(what, self.app.HILIGHT_COLOR)

	def on_area_leave(self, *a):
		if self._hilighted_area:
			self.unhilight(self._hilighted_area)
			self._hilighted_area = None

	def on_area_click(self, trash, what):
		stDialog = self.builder.get_object("stDialog")
		pages = stDialog.get_children()
		index = pages.index(stDialog.get_visible_child())
		if index == 2:
			if what in STICK_PAD_AREAS:
				area_name, axes = STICK_PAD_AREAS[what]
				mnuStick = self.builder.get_object("mnuStick")
				mnuStick._what = "STICKPRESS" if what == "STICK" else what
				mnuStick._axes = [self._axis_data[index] for index in axes]
				mnuStick.popup(None, None, None, None, 1, Gtk.get_current_event_time())
			elif what in TRIGGER_AREAS:
				self._grabber = TriggerGrabber(self, self._axis_data[TRIGGER_AREAS[what]])
			elif hasattr(SCButtons, what):
				self._grabber = InputGrabber(self, getattr(SCButtons, what))

	def on_mnuStickPress_activate(self, *a):
		mnuStick = self.builder.get_object("mnuStick")
		self._grabber = InputGrabber(self, getattr(SCButtons, mnuStick._what), text=_("Press stick or button..."))

	def on_mnuStickmove_activate(self, *a):
		mnuStick = self.builder.get_object("mnuStick")
		self._grabber = StickGrabber(self, mnuStick._axes)

	def on_btCancelInput_clicked(self, *a):
		if self._grabber:
			self._grabber.cancel()

	def refresh_devices(self, *a):
		log.debug("Refreshing device list")
		lstDevices = self.builder.get_object("lstDevices")
		cbShowAllDevices = self.builder.get_object("cbShowAllDevices")
		lstDevices.clear()
		for fname in evdev.list_devices():
			dev = evdev.InputDevice(fname)
			is_gamepad = ControllerRegistration.does_he_looks_like_a_gamepad(dev)
			# bustype 3 is BUS_USB, which is the type used for emulated
			# gamepads. phys is blank for BUS_BLUETOOTH devices.
			if dev.info.bustype == 3 and not dev.phys:
				# Skipping over virtual devices so list doesn't show
				# gamepads emulated by SCC
				continue
			if is_gamepad or cbShowAllDevices.get_active():
				lstDevices.append((fname, dev.name, self._gamepad_icon if is_gamepad else self._other_icon))

	def refresh_controller_image(self, *a):
		cbControllerButtons = self.builder.get_object("cbControllerButtons")
		cbControllerType = self.builder.get_object("cbControllerType")
		rvController = self.builder.get_object("rvController")
		group = cbControllerButtons.get_model()[cbControllerButtons.get_active()][0]
		controller = cbControllerType.get_model()[cbControllerType.get_active()][0]
		config = {"gui": {"background": controller, "buttons": self._groups[group]}}

		if self._controller_image:
			self._controller_image.use_config(config)
		else:
			self._controller_image = ControllerImage(self.app)
			self._controller_image.connect("hover", self.on_area_hover)
			self._controller_image.connect("leave", self.on_area_leave)
			self._controller_image.connect("click", self.on_area_click)
			rvController.add(self._controller_image)
