diff --git a/g13gui/g13gui/__init__.py b/g13gui/g13gui/__init__.py
index e69de29..a3962c5 100644
--- a/g13gui/g13gui/__init__.py
+++ b/g13gui/g13gui/__init__.py
@@ -0,0 +1,3 @@
+import g13gui.model as model
+import g13gui.ui as ui
+import g13gui.observer as observer
diff --git a/g13gui/g13gui/g13d.py b/g13gui/g13gui/g13d.py
index 6b1c938..073100f 100644
--- a/g13gui/g13gui/g13d.py
+++ b/g13gui/g13gui/g13d.py
@@ -9,11 +9,10 @@ import traceback
import xdg.BaseDirectory as basedir
import json
-from common import PROFILES_CONFIG_PATH
-from common import VERSION
+from g13gui.common import PROFILES_CONFIG_PATH
+from g13gui.common import VERSION
gi.require_version('Gtk', '3.0')
-
from gi.repository import GObject
@@ -32,24 +31,12 @@ class UploadTask():
class SaveTask():
- def __init__(self, profiles, defaultProfileName):
- self._profiles = profiles
- self._defaultProfileName = defaultProfileName
+ def __init__(self, prefsDict):
+ self._prefsDict = prefsDict
def run(self, outfp, infp, callback):
- profiles = {}
- for key, profile in self._profiles.items():
- profiles[key] = profile.toDict()
-
- config = {
- 'version': VERSION,
- 'defaultProfileName': self._defaultProfileName,
- 'profiles': profiles
- }
-
- encoder = json.JSONEncoder()
with open(PROFILES_CONFIG_PATH, 'w') as f:
- f.write(encoder.encode(config))
+ f.write(json.dumps(self._prefsDict, default=str))
f.flush()
diff --git a/g13gui/g13gui/main.py b/g13gui/g13gui/main.py
index fd53f97..483e26c 100644
--- a/g13gui/g13gui/main.py
+++ b/g13gui/g13gui/main.py
@@ -3,20 +3,23 @@
import queue
import gi
-gi.require_version('Gtk', '3.0')
+import g13gui.model as model
+import g13gui.ui as ui
+from g13gui.g13d import G13DWorker
+
+gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
-from mainwindow import MainWindow
-from g13d import G13DWorker
VERSION = '0.1.0'
if __name__ == '__main__':
+ prefs = model.Preferences()
queue = queue.Queue()
- win = MainWindow(queue)
+ win = ui.MainWindow(queue, prefs)
win.connect("destroy", Gtk.main_quit)
win.show_all()
diff --git a/g13gui/g13gui/mainwindow.py b/g13gui/g13gui/mainwindow.py
deleted file mode 100644
index ecef2cc..0000000
--- a/g13gui/g13gui/mainwindow.py
+++ /dev/null
@@ -1,234 +0,0 @@
-#!/usr/bin/python
-
-import gi
-import json
-import traceback
-
-from common import VERSION
-from common import PROFILES_CONFIG_PATH
-
-from g13d import UploadTask
-from g13d import SaveTask
-from bindings import G13D_TO_GDK_KEYBINDS
-from bindings import G13_KEYS
-from bindingprofile import BindingProfile
-from buttonmenu import ButtonMenu
-
-gi.require_version('Gtk', '3.0')
-
-from gi.repository import Gtk, GObject
-
-
-class MainWindow(Gtk.Window):
- def __init__(self, workerQueue):
- Gtk.Window.__init__(self)
-
- self._workerQueue = workerQueue
-
- GObject.signal_new("uploading", self, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_FLOAT,))
- self.connect("uploading", self.uploadStatusChanged)
- GObject.signal_new("daemon-connection-changed", self, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,))
- self.connect("daemon-connection-changed", self.daemonConnectionChanged)
-
- default_profile = BindingProfile()
- default_profile.registerObserver(self)
- self._profiles = {'Default Profile': default_profile}
- self._currentProfile = self._profiles['Default Profile']
-
- self.loadProfiles()
-
- self.headerBar = Gtk.HeaderBar()
- self.headerBar.set_title("G13 Configurator")
- self.headerBar.set_show_close_button(True)
-
- self.profileComboBox = Gtk.ComboBoxText()
- self.profileComboBox.connect("changed", self.profileChanged)
- self.headerBar.add(self.profileComboBox)
-
- addProfileButton = Gtk.Button.new_from_icon_name("add", 1)
- addProfileButton.connect("clicked", self.addProfileClicked)
- self.headerBar.add(addProfileButton)
-
- self._uploadButton = Gtk.Button.new_from_icon_name("up", 1)
- self._uploadButton.connect("clicked", self.uploadClicked)
- self.headerBar.add(self._uploadButton)
-
- Gtk.Window.set_default_size(self, 640, 480)
- Gtk.Window.set_titlebar(self, self.headerBar)
-
- self.box = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL)
- self.add(self.box)
-
- self.setupG13ButtonGrid()
- self.updateProfileBox()
-
- def loadProfiles(self):
- result = {}
- currentProfile = None
-
- try:
- with open(PROFILES_CONFIG_PATH, 'r') as f:
- serializedConfig = json.load(f)
-
- if serializedConfig['version'] != VERSION:
- print('WARNING: This profile config is from a different version (wanted %s got %s)!' %
- (VERSION, result['version']))
- print('This configuration may not load properly!')
-
- print("Loaded dict: %s" % (serializedConfig))
-
- for name, dict in serializedConfig['profiles'].items():
- result[name] = BindingProfile(dict=dict)
-
- for name, dict in result.items():
- if name == serializedConfig['defaultProfileName']:
- currentProfile = result[name]
-
- except (OSError, json.JSONDecodeError, KeyError, ValueError) as e:
- print("Failed to read profiles from disk: %s" % (e))
- traceback.print_exc()
- else:
- self._profiles = result
- self._currentProfile = currentProfile
-
- def daemonConnectionChanged(self, widget, connected):
- self._connected = connected
- if connected:
- self._uploadButton.set_state_flags(Gtk.StateFlags.NORMAL, True)
- else:
- self._uploadButton.set_state_flags(Gtk.StateFlags.INSENSITIVE, True)
-
- def uploadStatusChanged(self, widget, percentage):
- print("Upload in progress: %f" % (percentage * 100))
-
- def profileChanged(self, widget):
- pass
-
- def addProfileClicked(self, widget):
- pass
-
- def uploadClicked(self, widget):
- config = self._currentProfile.generateConfigString()
- task = UploadTask(config)
- self._workerQueue.put(task)
-
- currentProfileName = None
- for name, profile in self._profiles.items():
- if self._currentProfile == profile:
- currentProfileName = name
- break
-
- task = SaveTask(self._profiles, currentProfileName)
- self._workerQueue.put(task)
-
- def updateProfileBox(self):
- self.profileComboBox.remove_all()
- row = 0
- for profileName in self._profiles.keys():
- self.profileComboBox.append_text(profileName)
-
- if self._profiles[profileName] == self._currentProfile:
- print("Set active profile to %d (%s)" % (row, profileName))
- self.profileComboBox.set_active(row)
-
- row = row + 1
-
- def setupG13ButtonGrid(self):
- self.lcdButtons = Gtk.Box(spacing=3, orientation=Gtk.Orientation.HORIZONTAL)
- self.box.pack_start(self.lcdButtons, True, True, 6)
-
- self.mButtons = Gtk.Box(spacing=3, orientation=Gtk.Orientation.HORIZONTAL)
- self.box.pack_start(self.mButtons, True, True, 6)
-
- self.keyGrid = Gtk.Grid()
- self.keyGrid.set_row_spacing(3)
- self.keyGrid.set_column_spacing(3)
- self.box.pack_start(self.keyGrid, True, True, 6)
-
- self.stickGrid = Gtk.Grid()
- self.stickGrid.set_row_spacing(3)
- self.stickGrid.set_column_spacing(3)
- self.box.pack_start(self.stickGrid, False, False, 6)
-
- self.g13Buttons = {}
-
- self.lcdButtons.pack_start(self.newG13Button('BD'), True, True, 6)
- self.lcdButtons.pack_start(self.newG13Button('L1'), True, True, 6)
- self.lcdButtons.pack_start(self.newG13Button('L2'), True, True, 6)
- self.lcdButtons.pack_start(self.newG13Button('L3'), True, True, 6)
- self.lcdButtons.pack_start(self.newG13Button('L4'), True, True, 6)
- self.lcdButtons.pack_start(self.newG13Button('LIGHT'), True, True, 6)
-
- self.mButtons.pack_start(self.newG13Button('M1'), True, True, 6)
- self.mButtons.pack_start(self.newG13Button('M2'), True, True, 6)
- self.mButtons.pack_start(self.newG13Button('M3'), True, True, 6)
- self.mButtons.pack_start(self.newG13Button('MR'), True, True, 6)
-
- # G1 to G14
- self._buttonNum = 1
- for row in range(0, 2):
- for col in range(0, 7):
- self.keyGrid.attach(self.newG13NumberedButton(),
- col, row, 1, 1)
-
- # G15 to G19
- self.keyGrid.attach(self.newG13NumberedButton(), 1, 3, 1, 1)
- self.keyGrid.attach(self.newG13NumberedButton(), 2, 3, 1, 1)
- self.keyGrid.attach(self.newG13NumberedButton(), 3, 3, 1, 1)
- self.keyGrid.attach(self.newG13NumberedButton(), 4, 3, 1, 1)
- self.keyGrid.attach(self.newG13NumberedButton(), 5, 3, 1, 1)
-
- # G20 to G22
- self.keyGrid.attach(self.newG13NumberedButton(), 2, 4, 1, 1)
- self.keyGrid.attach(self.newG13NumberedButton(), 3, 4, 1, 1)
- self.keyGrid.attach(self.newG13NumberedButton(), 4, 4, 1, 1)
-
- self.stickGrid.attach(self.newG13Button("STICK_UP"), 4, 0, 1, 1)
- self.stickGrid.attach(self.newG13Button("LEFT"), 2, 1, 1, 1)
- self.stickGrid.attach(self.newG13Button("STICK_LEFT"), 3, 1, 1, 1)
- self.stickGrid.attach(self.newG13Button("TOP"), 4, 1, 1, 1)
- self.stickGrid.attach(self.newG13Button("STICK_RIGHT"), 5, 1, 1, 1)
- self.stickGrid.attach(self.newG13Button("STICK_DOWN"), 4, 2, 1, 1)
- self.stickGrid.attach(self.newG13Button("DOWN"), 4, 3, 1, 1)
-
- def newG13NumberedButton(self):
- button = self.newG13Button('G' + str(self._buttonNum))
- self._buttonNum = self._buttonNum + 1
- return button
-
- def newG13Button(self, name):
- popover = ButtonMenu(self._currentProfile, name)
- button = Gtk.MenuButton(popover=popover)
- self.g13Buttons[name] = button
- self.updateG13Button(name)
-
- return button
-
- def updateG13Button(self, name):
- button = self.g13Buttons[name]
- children = button.get_children()
-
- if len(children) > 0:
- button.remove(children[0])
-
- bindings = self._currentProfile.getBoundKey(name)
-
- if len(bindings) > 0:
- keybinds = [G13D_TO_GDK_KEYBINDS[binding] for binding in bindings]
- accelerator = '+'.join(keybinds)
- shortcut = Gtk.ShortcutsShortcut(
- shortcut_type=Gtk.ShortcutType.ACCELERATOR,
- accelerator=accelerator)
- shortcut.set_halign(Gtk.Align.CENTER)
- button.add(shortcut)
- else:
- label = Gtk.Label(name)
- button.add(label)
-
- button.show_all()
-
- def on_changed(self, profile):
- for key in G13_KEYS:
- self.updateG13Button(key)
-
- self.uploadClicked(self)
diff --git a/g13gui/g13gui/model/__init__.py b/g13gui/g13gui/model/__init__.py
new file mode 100644
index 0000000..ae7d54e
--- /dev/null
+++ b/g13gui/g13gui/model/__init__.py
@@ -0,0 +1,2 @@
+from g13gui.model.bindingprofile import BindingProfile
+from g13gui.model.prefs import Preferences
diff --git a/g13gui/g13gui/bindingprofile.py b/g13gui/g13gui/model/bindingprofile.py
similarity index 70%
rename from g13gui/g13gui/bindingprofile.py
rename to g13gui/g13gui/model/bindingprofile.py
index 6ad31bf..d241d5f 100644
--- a/g13gui/g13gui/bindingprofile.py
+++ b/g13gui/g13gui/model/bindingprofile.py
@@ -1,9 +1,9 @@
#!/usr/bin/python
-import bindings
+import g13gui.model.bindings as bindings
-from observer import Subject
-from observer import ChangeType
+from g13gui.observer import Subject
+from g13gui.observer import ChangeType
class BindingProfile(Subject):
@@ -17,6 +17,10 @@ class BindingProfile(Subject):
self._stickRegions = bindings.DEFAULT_STICK_REGIONS
self._stickRegionBindings = bindings.DEFAULT_STICK_REGION_BINDINGS
self._keyBindings = bindings.DEFAULT_KEY_BINDINGS
+ self._lcdColor = bindings.DEFAULT_LCD_COLOR
+
+ def lcdColor(self):
+ return self._lcdColor
def stickMode(self):
return self._stickMode
@@ -36,6 +40,14 @@ class BindingProfile(Subject):
return []
+ def _setLCDColor(self, red, green, blue):
+ self._lcdColor = (red, green, blue)
+ self.addChange(ChangeType.MODIFY, 'lcdcolor', self._lcdColor)
+
+ def setLCDColor(self, red, green, blue):
+ self._setLCDColor(red, green, blue)
+ self.notifyChanged()
+
def _bindKey(self, gkey, keybinding):
if gkey in self._stickRegions.keys():
self._stickRegionBindings[gkey] = keybinding
@@ -59,15 +71,24 @@ class BindingProfile(Subject):
self._setStickMode(stickmode)
self.notifyChanged()
+ def _lcdColorToCommandString(self):
+ return 'rgb %d %d %d' % tuple([int(x * 255) for x in self._lcdColor])
+
+ def _keyBindingToCommandString(self, gkey):
+ kbdkey = self._keyBindings[gkey]
+ if len(kbdkey) > 0:
+ keys = '+'.join(['KEY_' + key for key in kbdkey])
+ return "bind %s %s" % (gkey, keys)
+ else:
+ return "unbind %s" % (gkey)
+
def toCommandString(self):
commands = []
- for gkey, kbdkey in self._keyBindings.items():
- if len(kbdkey) > 0:
- keys = '+'.join(['KEY_' + key for key in kbdkey])
- commands.append("bind %s %s" % (gkey, keys))
- else:
- commands.append("unbind %s" % (gkey))
+ commands.append(self._lcdColorToCommandString())
+
+ for gkey in self._keyBindings.keys():
+ commands.append(self._keyBindingToCommandString(gkey))
if self._stickMode == bindings.StickMode.KEYS:
for region, bounds in self._stickRegions.items():
@@ -80,6 +101,7 @@ class BindingProfile(Subject):
return '\n'.join(commands)
def loadFromDict(self, dict):
+ self._lcdColor = dict['lcdcolor']
self._stickMode = dict['stickMode']
self._stickRegions = dict['stickRegions']
self._stickRegionBindings = dict['stickRegionBindings']
@@ -87,6 +109,7 @@ class BindingProfile(Subject):
def saveToDict(self):
return {
+ 'lcdcolor': self._lcdColor,
'stickMode': self._stickMode,
'stickRegions': self._stickRegions,
'stickRegionBindings': self._stickRegionBindings,
diff --git a/g13gui/g13gui/bindingprofile_tests.py b/g13gui/g13gui/model/bindingprofile_tests.py
similarity index 68%
rename from g13gui/g13gui/bindingprofile_tests.py
rename to g13gui/g13gui/model/bindingprofile_tests.py
index 5684115..d263f41 100644
--- a/g13gui/g13gui/bindingprofile_tests.py
+++ b/g13gui/g13gui/model/bindingprofile_tests.py
@@ -2,10 +2,10 @@
import unittest
-import bindings
-from bindingprofile import BindingProfile
-from observer import ChangeType
-from observer import ObserverTestCase
+import g13gui.model.bindings as bindings
+from g13gui.model.bindingprofile import BindingProfile
+from g13gui.observer import ChangeType
+from g13gui.observer import ObserverTestCase
class PrefsTestCase(ObserverTestCase):
@@ -16,14 +16,18 @@ class PrefsTestCase(ObserverTestCase):
bp = BindingProfile()
self.assertEqual(bp.stickMode(), bindings.StickMode.KEYS)
self.assertEqual(bp.stickRegions(), bindings.DEFAULT_STICK_REGIONS)
- self.assertEqual(bp._stickRegionBindings, bindings.DEFAULT_STICK_REGION_BINDINGS)
+ self.assertEqual(bp.lcdColor(), bindings.DEFAULT_LCD_COLOR)
+ self.assertEqual(bp._stickRegionBindings,
+ bindings.DEFAULT_STICK_REGION_BINDINGS)
self.assertEqual(bp._keyBindings, bindings.DEFAULT_KEY_BINDINGS)
+ self.assertEqual(bp._lcdColor, bindings.DEFAULT_LCD_COLOR)
def testInvalidDict(self):
bp = BindingProfile({})
self.assertEqual(bp.stickMode(), bindings.StickMode.KEYS)
self.assertEqual(bp.stickRegions(), bindings.DEFAULT_STICK_REGIONS)
- self.assertEqual(bp._stickRegionBindings, bindings.DEFAULT_STICK_REGION_BINDINGS)
+ self.assertEqual(bp._stickRegionBindings,
+ bindings.DEFAULT_STICK_REGION_BINDINGS)
self.assertEqual(bp._keyBindings, bindings.DEFAULT_KEY_BINDINGS)
def testDictLoadSave(self):
@@ -72,6 +76,21 @@ class PrefsTestCase(ObserverTestCase):
else:
self.fail('Expected ValueError from setStickMode')
+ def testLCDColor(self):
+ bp = BindingProfile()
+ bp.registerObserver(self)
+ bp.setLCDColor(1.0, 0.5, 0.1)
+ self.assertEqual(bp._lcdColor, (1.0, 0.5, 0.1))
+ self.assertEqual(bp.lcdColor(), (1.0, 0.5, 0.1))
+ self.assertChangeCount(1)
+ self.assertChangeNotified(bp, ChangeType.MODIFY, 'lcdcolor')
+ self.assertChangeDataEquals((1.0, 0.5, 0.1))
+
+ def testToCommandString(self):
+ bp = BindingProfile()
+ result = bp.toCommandString()
+ self.assertIsNotNone(result)
+
if __name__ == '__main__':
unittest.main()
diff --git a/g13gui/g13gui/bindings.py b/g13gui/g13gui/model/bindings.py
similarity index 74%
rename from g13gui/g13gui/bindings.py
rename to g13gui/g13gui/model/bindings.py
index 48b538f..3636ea7 100644
--- a/g13gui/g13gui/bindings.py
+++ b/g13gui/g13gui/model/bindings.py
@@ -1,7 +1,9 @@
#!/usr/bin/python3
-import enum
-
+"""
+Defines a whole bunch of constants relating to mapping G13D key names to GDK key
+names, as well as the symbols that g13d natively supports.
+"""
G13D_TO_GDK_KEYBINDS = {
'0': '0',
@@ -147,42 +149,35 @@ G13_KEYS = [
'LEFT', 'DOWN', 'TOP',
]
-DEFAULT_STICK_REGIONS = {
- 'STICK_UP': [0.0, 0.0, 1.0, 0.2],
- 'STICK_DOWN': [0.0, 0.8, 1.0, 1.0],
- 'STICK_LEFT': [0.0, 0.0, 0.2, 1.0],
- 'STICK_RIGHT': [0.8, 0.0, 1.0, 1.0]
-}
-
DEFAULT_KEY_BINDINGS = {
- 'G1': ['ESC'],
+ 'G1': ['GRAVE'],
'G2': ['1'],
'G3': ['2'],
'G4': ['3'],
'G5': ['4'],
'G6': ['5'],
- 'G7': ['Y'],
- 'G8': ['Q'],
- 'G9': ['Z'],
- 'G10': ['V'],
- 'G11': ['SPACE'],
- 'G12': ['E'],
- 'G13': ['R'],
- 'G14': ['U'],
- 'G15': ['LEFTSHIFT'],
- 'G16': ['F'],
- 'G17': ['X'],
- 'G18': ['C'],
- 'G19': ['H'],
- 'G20': ['LEFTCTRL'],
- 'G21': ['B'],
- 'G22': ['T'],
- 'LEFT': ['TAB'],
- 'DOWN': ['M'],
+ 'G7': ['6'],
+ 'G8': ['TAB'],
+ 'G9': ['Q'],
+ 'G10': ['W'],
+ 'G11': ['E'],
+ 'G12': ['R'],
+ 'G13': ['T'],
+ 'G14': ['Y'],
+ 'G15': ['A'],
+ 'G16': ['S'],
+ 'G17': ['D'],
+ 'G18': ['F'],
+ 'G19': ['G'],
+ 'G20': ['X'],
+ 'G21': ['C'],
+ 'G22': ['V'],
+ 'LEFT': ['B'],
+ 'DOWN': ['N'],
}
-class StickRegion(enum.Enum):
+class StickRegion():
UP = 'STICK_UP'
DOWN = 'STICK_DOWN'
LEFT = 'STICK_LEFT'
@@ -190,35 +185,54 @@ class StickRegion(enum.Enum):
ALL_STICK_REGIONS = frozenset({
- StickRegion.UP,
- StickRegion.DOWN,
- StickRegion.LEFT,
- StickRegion.RIGHT
+ StickRegion.UP, StickRegion.DOWN,
+ StickRegion.LEFT, StickRegion.RIGHT
})
+DEFAULT_STICK_REGIONS = {
+ StickRegion.UP: [0.0, 0.0, 1.0, 0.2],
+ StickRegion.DOWN: [0.0, 0.8, 1.0, 1.0],
+ StickRegion.LEFT: [0.0, 0.0, 0.2, 1.0],
+ StickRegion.RIGHT: [0.8, 0.0, 1.0, 1.0]
+}
+
DEFAULT_STICK_REGION_BINDINGS = {
- StickRegion.UP: ['W'],
- StickRegion.DOWN: ['S'],
- StickRegion.LEFT: ['A'],
+ StickRegion.UP: ['W'],
+ StickRegion.DOWN: ['S'],
+ StickRegion.LEFT: ['A'],
StickRegion.RIGHT: ['D']
}
-class StickMode(enum.Enum):
+class StickMode():
ABSOLUTE = 'ABSOLUTE'
RELATIVE = 'RELATIVE'
KEYS = 'KEYS'
ALL_STICK_MODES = frozenset({
- StickMode.ABSOLUTE,
- StickMode.RELATIVE,
- StickMode.KEYS
+ StickMode.ABSOLUTE, StickMode.RELATIVE, StickMode.KEYS
})
+
def G13DKeyIsModifier(key):
key = key.upper()
return (key == 'LEFTSHIFT' or key == 'RIGHTSHIFT' or
key == 'LEFTALT' or key == 'RIGHTALT' or
key == 'LEFTCTRL' or key == 'RIGHTCTRL')
+
+
+def G13ToGDK(keybinds):
+ if type(keybinds) == list:
+ return [G13D_TO_GDK_KEYBINDS[binding] for binding in keybinds]
+ return G13D_TO_GDK_KEYBINDS[keybinds]
+
+
+def GDKToG13(keybinds):
+ if type(keybinds) == list:
+ return [GDK_TO_G13D_KEYBINDS[binding] for binding in keybinds]
+ return GDK_TO_G13D_KEYBINDS[keybinds]
+
+
+DEFAULT_LCD_COLOR = (1.0, 0.0, 0.0)
diff --git a/g13gui/g13gui/prefs.py b/g13gui/g13gui/model/prefs.py
similarity index 89%
rename from g13gui/g13gui/prefs.py
rename to g13gui/g13gui/model/prefs.py
index 03ae1d3..b67d66d 100644
--- a/g13gui/g13gui/prefs.py
+++ b/g13gui/g13gui/model/prefs.py
@@ -2,10 +2,10 @@
import traceback
-from common import VERSION
-from bindingprofile import BindingProfile
-from observer import Subject
-from observer import ChangeType
+from g13gui.common import VERSION
+from g13gui.model.bindingprofile import BindingProfile
+from g13gui.observer import Subject
+from g13gui.observer import ChangeType
DEFAULT_PROFILE_NAME = 'Default Profile'
@@ -21,7 +21,9 @@ class Preferences(Subject):
else:
self.initDefaultProfile()
- def profiles(self):
+ def profiles(self, profileName=None):
+ if profileName:
+ return self._profiles[profileName]
return self._profiles
def profileNames(self):
@@ -83,7 +85,7 @@ class Preferences(Subject):
def saveToDict(self):
return {
'version': VERSION,
- 'profiles': dict([(name, profile.toDict()) for name, profile in self._profiles.items()]),
+ 'profiles': dict([(name, profile.saveToDict()) for name, profile in self._profiles.items()]),
'selectedProfile': self._selectedProfile
}
diff --git a/g13gui/g13gui/prefs_tests.py b/g13gui/g13gui/model/prefs_tests.py
similarity index 95%
rename from g13gui/g13gui/prefs_tests.py
rename to g13gui/g13gui/model/prefs_tests.py
index 99b35f9..03d546f 100644
--- a/g13gui/g13gui/prefs_tests.py
+++ b/g13gui/g13gui/model/prefs_tests.py
@@ -1,11 +1,11 @@
#!/usr/bin/python
import unittest
-import prefs
+import g13gui.model.prefs as prefs
-from common import VERSION
-from observer import ChangeType
-from observer import ObserverTestCase
+from g13gui.common import VERSION
+from g13gui.observer import ChangeType
+from g13gui.observer import ObserverTestCase
class PrefsTestCase(ObserverTestCase):
diff --git a/g13gui/g13gui/observer/__init__.py b/g13gui/g13gui/observer/__init__.py
new file mode 100644
index 0000000..bc2e7fc
--- /dev/null
+++ b/g13gui/g13gui/observer/__init__.py
@@ -0,0 +1,5 @@
+from g13gui.observer.observer import Observer
+from g13gui.observer.observer import Subject
+from g13gui.observer.observer import ObserverTestCase
+from g13gui.observer.observer import ChangeType
+from g13gui.observer.gtkobserver import GtkObserver
diff --git a/g13gui/g13gui/observer/gtkobserver.py b/g13gui/g13gui/observer/gtkobserver.py
new file mode 100644
index 0000000..d66c1df
--- /dev/null
+++ b/g13gui/g13gui/observer/gtkobserver.py
@@ -0,0 +1,53 @@
+import gi
+import queue
+
+from g13gui.observer import Observer
+
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, GObject
+
+
+class GObjectObserverProxy(GObject.Object):
+ def __init__(self, owner):
+ GObject.Object.__init__(self)
+ self._owner = owner
+
+ @GObject.Signal(name='subject-changed')
+ def subjectChanged(self):
+ self._owner._gtkSubjectChanged(self)
+
+
+class GtkObserver(Observer):
+ def __init__(self):
+ """Constructor. Must be called by a subclass' constructor."""
+ self._observerQueue = queue.Queue()
+ self._gobjectProxy = GObjectObserverProxy(self)
+
+ def onSubjectChanged(self, subject, changeType, key, data=None):
+ """Original Observer signal handler.
+
+ Runs on a (possibly) background thread to put the notification into a
+ queue, then signal to trampoline to the UI thread before handling the
+ notification.
+ """
+ self._observerQueue.put((subject, changeType, key, data))
+ self._gobjectProxy.emit("subject-changed")
+
+ def _gtkSubjectChanged(self, widget):
+ """GObject 'subject-changed' signal handler.
+
+ Runs on the UI thread, and pops the change notification off the queue
+ and processes it by way of the gtkSubjectChanged method that must be
+ overridden.
+ """
+ (subject, changeType, key, data) = self._observerQueue.get()
+ self.gtkSubjectChanged(subject, changeType, key, data)
+ self._observerQueue.task_done()
+
+ def gtkSubjectChanged(self, subject, changeType, key, data=None):
+ """Subject notification handler.
+
+ Runs on the UI thread, and must be overridden by subclasses.
+ """
+ raise NotImplementedError(
+ "%s did not override Observer#gtkSubjectChanged" % (type(self)))
diff --git a/g13gui/g13gui/observer.py b/g13gui/g13gui/observer/observer.py
similarity index 100%
rename from g13gui/g13gui/observer.py
rename to g13gui/g13gui/observer/observer.py
diff --git a/g13gui/g13gui/observer_tests.py b/g13gui/g13gui/observer/observer_tests.py
similarity index 100%
rename from g13gui/g13gui/observer_tests.py
rename to g13gui/g13gui/observer/observer_tests.py
diff --git a/g13gui/g13gui/ui/__init__.py b/g13gui/g13gui/ui/__init__.py
new file mode 100644
index 0000000..6e98c7a
--- /dev/null
+++ b/g13gui/g13gui/ui/__init__.py
@@ -0,0 +1,6 @@
+from g13gui.ui.profilecombobox import ProfileComboBox
+from g13gui.ui.g13button import G13Button
+from g13gui.ui.g13buttonpopover import G13ButtonPopover
+from g13gui.ui.mainwindow import MainWindow
+from g13gui.ui.profilepopover import ProfilePopover
+from g13gui.ui.profilepopover import ProfilePopoverMode
diff --git a/g13gui/g13gui/ui/g13button.py b/g13gui/g13gui/ui/g13button.py
new file mode 100644
index 0000000..b50f8ec
--- /dev/null
+++ b/g13gui/g13gui/ui/g13button.py
@@ -0,0 +1,71 @@
+import gi
+
+import g13gui.ui as ui
+from g13gui.observer import GtkObserver
+from g13gui.model.bindings import G13ToGDK
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+from gi.repository import Gtk, GObject, Gdk
+
+
+class G13Button(Gtk.MenuButton, GtkObserver):
+ def __init__(self, prefs, g13KeyName):
+ Gtk.MenuButton.__init__(self)
+ GtkObserver.__init__(self)
+
+ self._prefs = prefs
+ self._prefs.registerObserver(self, {'selectedProfile'})
+ self._keyName = g13KeyName
+ self._lastProfileName = None
+
+ self._popover = ui.G13ButtonPopover(self, self._prefs, self._keyName)
+ self.set_popover(self._popover)
+
+ _image = Gtk.Image.new_from_file(g13KeyName + '.png')
+ self.get_style_context().add_class('flat')
+
+ self.set_can_default(False)
+ self.updateProfileRegistration()
+ self.updateBindingDisplay()
+
+ def updateProfileRegistration(self):
+ if self._lastProfileName:
+ if self._lastProfileName in self._prefs.profileNames():
+ lastProfile = self._prefs.profiles(self._lastProfileName)
+ lastProfile.removeObserver(self)
+
+ self._prefs.selectedProfile().registerObserver(self, {self._keyName})
+
+ def _removeChild(self):
+ child = self.get_child()
+ if child:
+ self.remove(child)
+
+ def updateBindingDisplay(self):
+ self._removeChild()
+ bindings = self._prefs.selectedProfile().keyBinding(self._keyName)
+ print('[%s %s] %s: %s' % (self._prefs.selectedProfileName(),
+ self._prefs.selectedProfile(),
+ self._keyName, bindings))
+
+ if len(bindings) > 0:
+ keybinds = G13ToGDK(bindings)
+ accelerator = '+'.join(keybinds)
+ shortcut = Gtk.ShortcutsShortcut(
+ shortcut_type=Gtk.ShortcutType.ACCELERATOR,
+ accelerator=accelerator)
+ shortcut.set_halign(Gtk.Align.CENTER)
+ self.add(shortcut)
+ else:
+ label = Gtk.Label(self._keyName)
+ self.add(label)
+
+ self.show_all()
+
+ def gtkSubjectChanged(self, subject, changeType, key, data=None):
+ if key == 'selectedProfile':
+ self.updateProfileRegistration()
+ self.updateBindingDisplay()
+ elif key == self._keyName:
+ self.updateBindingDisplay()
diff --git a/g13gui/g13gui/buttonmenu.py b/g13gui/g13gui/ui/g13buttonpopover.py
similarity index 55%
rename from g13gui/g13gui/buttonmenu.py
rename to g13gui/g13gui/ui/g13buttonpopover.py
index e6abe5d..cd1c235 100644
--- a/g13gui/g13gui/buttonmenu.py
+++ b/g13gui/g13gui/ui/g13buttonpopover.py
@@ -1,43 +1,55 @@
-#!/usr/bin/python3
-
import gi
+from g13gui.observer import GtkObserver
+
+from g13gui.model.bindings import G13ToGDK
+from g13gui.model.bindings import GDKToG13
+from g13gui.model.bindings import G13DKeyIsModifier
+
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-
-from bindings import GDK_TO_G13D_KEYBINDS
-from bindings import G13D_TO_GDK_KEYBINDS
-from bindings import G13DKeyIsModifier
+from gi.repository import Gtk, GObject, Gdk
MAX_DELAY_BETWEEN_PRESSES_MILLIS = 250
-class ButtonMenu(Gtk.Popover):
- def __init__(self, profile, buttonName):
+class G13ButtonPopover(Gtk.Popover, GtkObserver):
+ def __init__(self, buttonOwner, prefs, keyName):
Gtk.Popover.__init__(self)
+ GtkObserver.__init__(self)
- self._profile = profile
- self._buttonName = buttonName
- self._currentBindings = self._profile.getBoundKey(buttonName)
- self._bindingBox = None
- self._modifiers = {}
+ self._prefs = prefs
+ self._prefs.registerObserver(self, {'selectedProfile'})
+ self._keyName = keyName
+
+ self._modifiers = set()
self._consonantKey = None
self._lastPressTime = 0
+ self.set_relative_to(buttonOwner)
+ self.updateBinding()
+ self.build()
+
+ def updateBinding(self):
+ selectedProfile = self._prefs.selectedProfile()
+ self._currentBindings = selectedProfile.keyBinding(self._keyName)
+
+ def gtkSubjectChanged(self, subject, changeType, key, data=None):
+ self.updateBinding()
+
+ def build(self):
self._box = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL)
+ self._box.set_border_width(6)
self.add(self._box)
label = Gtk.Label()
- label.set_markup("" + buttonName + "")
+ label.set_markup("" + self._keyName + "")
self._box.pack_start(label, True, True, 6)
button = Gtk.Button(label="Clear Binding")
+ button.set_can_focus(False)
self._box.pack_start(button, True, True, 6)
-
self._box.show_all()
self.connect("key-press-event", self.keypress)
@@ -46,21 +58,25 @@ class ButtonMenu(Gtk.Popover):
self.connect("closed", self.closed)
button.connect("pressed", self.clear)
- self.rebuildBindingDisplay()
+ self.buildBindingDisplay()
def shown(self, widget):
- Gdk.keyboard_grab(self.get_window(), False, Gdk.CURRENT_TIME)
+ self.grab_add()
def rebuildBindingDisplay(self):
if self._bindingBox:
self._box.remove(self._bindingBox)
- self._bindingBox = Gtk.Box(spacing=0, orientation=Gtk.Orientation.VERTICAL)
+ self.buildBindingDisplay()
+
+ def buildBindingDisplay(self):
+ self._bindingBox = Gtk.Box(spacing=0,
+ orientation=Gtk.Orientation.VERTICAL)
self._box.pack_start(self._bindingBox, True, True, 6)
self._box.reorder_child(self._bindingBox, 1)
if len(self._currentBindings) > 0:
- keybinds = [G13D_TO_GDK_KEYBINDS[binding] for binding in self._currentBindings]
+ keybinds = G13ToGDK(self._currentBindings)
accelerator = '+'.join(keybinds)
shortcut = Gtk.ShortcutsShortcut(
shortcut_type=Gtk.ShortcutType.ACCELERATOR,
@@ -75,43 +91,44 @@ class ButtonMenu(Gtk.Popover):
self._bindingBox.show_all()
def keypress(self, buttonMenu, eventKey):
- print("Keypressed! %s, %s" % (eventKey.keyval, Gdk.keyval_name(eventKey.keyval)))
-
- if eventKey.time - self._lastPressTime > MAX_DELAY_BETWEEN_PRESSES_MILLIS:
- self._modifiers = {}
+ pressDelta = eventKey.time - self._lastPressTime
+ if pressDelta > MAX_DELAY_BETWEEN_PRESSES_MILLIS:
+ self._modifiers = set()
self._consonantKey = None
binding = Gdk.keyval_name(eventKey.keyval)
if len(binding) == 1:
binding = binding.upper()
-
if binding == 'Meta_L':
binding = 'Alt_L'
if binding == 'Meta_R':
binding = 'Alt_R'
- binding = GDK_TO_G13D_KEYBINDS[binding]
- print('Binding is %s' % (binding))
+
+ binding = GDKToG13(binding)
if G13DKeyIsModifier(binding):
- self._modifiers[binding] = True
- print("Modifiers are now %s" % (repr(self._modifiers.keys())))
+ self._modifiers.add(binding)
else:
self._consonantKey = binding
self._lastPressTime = eventKey.time
+ return True
def keyrelease(self, buttonMenu, eventKey):
- self._currentBindings = [modifier for modifier in self._modifiers.keys()]
+ self._currentBindings = sorted(list(self._modifiers))
if self._consonantKey:
- self._currentBindings = self._currentBindings + [self._consonantKey]
-
+ self._currentBindings += [self._consonantKey]
self.rebuildBindingDisplay()
- print("Bindings are now %s" % (self._currentBindings))
+ self.hide()
+ return True
def clear(self, button):
self._currentBindings = []
self.rebuildBindingDisplay()
+ self.hide()
def closed(self, buttonMenu):
- self._profile.bindKey(self._buttonName, self._currentBindings)
+ self.grab_remove()
+ self._prefs.selectedProfile().bindKey(self._keyName,
+ self._currentBindings)
self.hide()
diff --git a/g13gui/g13gui/ui/mainwindow.py b/g13gui/g13gui/ui/mainwindow.py
new file mode 100644
index 0000000..9fdb167
--- /dev/null
+++ b/g13gui/g13gui/ui/mainwindow.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+
+import gi
+
+import g13gui.ui as ui
+
+from g13gui.g13d import SaveTask
+from g13gui.g13d import UploadTask
+from g13gui.observer import GtkObserver
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+from gi.repository import Gtk, Gdk, GObject
+
+
+class MainWindow(Gtk.Window, GtkObserver):
+ def __init__(self, workerQueue, prefs):
+ Gtk.Window.__init__(self)
+ GtkObserver.__init__(self)
+
+ self.set_default_size(640, 480)
+ geometry = Gdk.Geometry()
+ geometry.max_width = 640
+ geometry.max_height = 480
+ self.set_geometry_hints(None, geometry, Gdk.WindowHints.MAX_SIZE)
+
+ self._workerQueue = workerQueue
+ self._prefs = prefs
+ self._prefs.registerObserver(self, 'selectedProfile')
+ self._prefs.selectedProfile().registerObserver(self)
+ self._lastProfileName = self._prefs.selectedProfileName()
+
+ self.setupHeaderBar()
+
+ self._box = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL)
+ self._box.set_border_width(6)
+ self.add(self._box)
+
+ self._infoBar = Gtk.InfoBar()
+ self._infoBar.set_no_show_all(True)
+ self._infoBarLabel = Gtk.Label()
+ self._infoBar.get_content_area().add(self._infoBarLabel)
+ self._infoBarLabel.show()
+ self._box.add(self._infoBar)
+
+ self.setupG13ButtonGrid()
+
+ def gtkSubjectChanged(self, subject, changeType, key, data=None):
+ self._doUpload()
+ self._doSave()
+
+ def setupHeaderBar(self):
+ self._headerBar = Gtk.HeaderBar()
+ self._headerBar.set_title("G13 Configurator")
+ self._headerBar.set_show_close_button(True)
+
+ self._profileComboBox = ui.ProfileComboBox(self._prefs)
+ self._profileComboBox.connect('changed', self._profileChanged)
+ self._headerBar.add(self._profileComboBox)
+
+ addProfileButton = Gtk.MenuButton.new()
+ addProfileButton.add(Gtk.Image.new_from_icon_name(
+ "document-new-symbolic", 1))
+ addProfilePopover = ui.ProfilePopover(self._prefs,
+ mode=ui.ProfilePopoverMode.ADD)
+ addProfileButton.set_popover(addProfilePopover)
+ self._headerBar.add(addProfileButton)
+
+ editProfileButton = Gtk.MenuButton.new()
+ editProfileButton.add(
+ Gtk.Image.new_from_icon_name('document-edit-symbolic', 1))
+ editProfilePopover = ui.ProfilePopover(self._prefs,
+ mode=ui.ProfilePopoverMode.EDIT)
+ editProfileButton.set_popover(editProfilePopover)
+ self._headerBar.add(editProfileButton)
+
+ self._uploadButton = Gtk.Button.new_from_icon_name(
+ "document-send-symbolic", 1)
+ self._uploadButton.connect("clicked", self.uploadClicked)
+ self._headerBar.add(self._uploadButton)
+
+ Gtk.Window.set_titlebar(self, self._headerBar)
+
+ @GObject.Signal(name='daemon-connection-changed', arg_types=(bool,))
+ def daemonConnectionChanged(self, connected):
+ self._connected = connected
+ if connected:
+ self._uploadButton.set_state_flags(Gtk.StateFlags.NORMAL, True)
+ self._infoBar.hide()
+ self._doUpload()
+ else:
+ self._uploadButton.set_state_flags(Gtk.StateFlags.INSENSITIVE,
+ True)
+ self._infoBar.set_message_type(Gtk.MessageType.WARNING)
+ self._infoBarLabel.set_text(
+ 'The G13 user space driver is not running. '
+ 'Attempting to reconnect.')
+ self._infoBar.show()
+
+ @GObject.Signal(name='uploading', arg_types=(float,))
+ def uploadStatusChanged(self, percentage):
+ if percentage < 1.0:
+ self._infoBar.set_message_type(Gtk.MessageType.INFO)
+ self._infoBarLabel.set_text('Uploading to the G13...')
+ self._infoBar.show()
+ else:
+ self._infoBar.hide()
+
+ def _profileChanged(self, widget):
+ self._doUpload()
+
+ def _doUpload(self):
+ config = self._prefs.selectedProfile().toCommandString()
+ task = UploadTask(config)
+ self._workerQueue.put(task)
+
+ def _doSave(self):
+ task = SaveTask(self._prefs.saveToDict())
+ self._workerQueue.put(task)
+
+ def uploadClicked(self, widget):
+ self._doUpload()
+ self._doSave()
+
+ def setupG13ButtonGrid(self):
+ self._mButtons = Gtk.ButtonBox(
+ spacing=3,
+ orientation=Gtk.Orientation.HORIZONTAL,
+ baseline_position=Gtk.BaselinePosition.CENTER)
+ self._mButtons.set_layout(Gtk.ButtonBoxStyle.CENTER)
+ self._box.pack_start(self._mButtons, False, False, 6)
+
+ self._keyGrid = Gtk.Grid()
+ self._keyGrid.set_hexpand(False)
+ self._keyGrid.set_vexpand(False)
+ self._keyGrid.set_row_spacing(3)
+ self._keyGrid.set_column_spacing(3)
+ self._box.pack_start(self._keyGrid, False, False, 6)
+
+ self._stickGrid = Gtk.Grid()
+ self._stickGrid.set_row_spacing(3)
+ self._stickGrid.set_column_spacing(3)
+ self._box.pack_start(self._stickGrid, False, False, 6)
+
+ self._g13Buttons = {}
+
+ self._mButtons.pack_start(self.newG13Button('M1'), False, False, 6)
+ self._mButtons.pack_start(self.newG13Button('M2'), False, False, 6)
+ self._mButtons.pack_start(self.newG13Button('M3'), False, False, 6)
+
+ # G1 to G14
+ self._buttonNum = 1
+ for row in range(0, 2):
+ for col in range(0, 7):
+ self._keyGrid.attach(self.newG13NumberedButton(),
+ col, row, 1, 1)
+
+ # G15 to G19
+ self._keyGrid.attach(self.newG13NumberedButton(), 1, 3, 1, 1)
+ self._keyGrid.attach(self.newG13NumberedButton(), 2, 3, 1, 1)
+ self._keyGrid.attach(self.newG13NumberedButton(), 3, 3, 1, 1)
+ self._keyGrid.attach(self.newG13NumberedButton(), 4, 3, 1, 1)
+ self._keyGrid.attach(self.newG13NumberedButton(), 5, 3, 1, 1)
+
+ # G20 to G22
+ self._keyGrid.attach(self.newG13NumberedButton(), 2, 4, 1, 1)
+ self._keyGrid.attach(self.newG13NumberedButton(), 3, 4, 1, 1)
+ self._keyGrid.attach(self.newG13NumberedButton(), 4, 4, 1, 1)
+
+ self._stickGrid.attach(self.newG13Button("STICK_UP"), 4, 0, 1, 1)
+ self._stickGrid.attach(self.newG13Button("LEFT"), 2, 1, 1, 1)
+ self._stickGrid.attach(self.newG13Button("STICK_LEFT"), 3, 1, 1, 1)
+ self._stickGrid.attach(self.newG13Button("TOP"), 4, 1, 1, 1)
+ self._stickGrid.attach(self.newG13Button("STICK_RIGHT"), 5, 1, 1, 1)
+ self._stickGrid.attach(self.newG13Button("STICK_DOWN"), 4, 2, 1, 1)
+ self._stickGrid.attach(self.newG13Button("DOWN"), 4, 3, 1, 1)
+
+ def newG13NumberedButton(self):
+ button = self.newG13Button('G' + str(self._buttonNum))
+ self._buttonNum = self._buttonNum + 1
+ return button
+
+ def newG13Button(self, name):
+ button = ui.G13Button(self._prefs, name)
+ self._g13Buttons[name] = button
+ return button
diff --git a/g13gui/g13gui/ui/profilecombobox.py b/g13gui/g13gui/ui/profilecombobox.py
new file mode 100644
index 0000000..ac21ebd
--- /dev/null
+++ b/g13gui/g13gui/ui/profilecombobox.py
@@ -0,0 +1,59 @@
+import gi
+
+from g13gui.observer import GtkObserver
+from g13gui.observer import ChangeType
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+from gi.repository import Gtk, GObject, Gdk
+
+
+def AlphabeticalSort(model, a, b, userData):
+ print('AlphabeticalSort %s <=> %s' % (a, b))
+ if a == b:
+ return 0
+ if a < b:
+ return -1
+ return 1
+
+
+class ProfileComboBox(Gtk.ComboBoxText, GtkObserver):
+ def __init__(self, prefs):
+ Gtk.ComboBoxText.__init__(self)
+ GtkObserver.__init__(self)
+
+ self._prefs = prefs
+ self._prefs.registerObserver(self, {'profile'})
+ self._isUpdating = False
+
+ self._model = self.get_model()
+ self._model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
+ self._model.set_default_sort_func(AlphabeticalSort)
+ self.connect("changed", self._profileChanged)
+
+ self.update()
+
+ def _profileChanged(self, widget):
+ selectedProfile = self.get_active_text()
+ print('Profile changed to %s' % selectedProfile)
+ if selectedProfile:
+ self._prefs.setSelectedProfile(selectedProfile)
+
+ def update(self):
+ profiles = self._prefs.profileNames()
+ selected = self._prefs.selectedProfileName()
+
+ self._model.clear()
+ row = 0
+
+ for name in profiles:
+ self._model.append([name, name])
+ if name == selected:
+ self.set_active(row)
+ row = row + 1
+
+ def gtkSubjectChanged(self, subject, changeType, key, data=None):
+ name = list(data.keys())[0]
+
+ if changeType == ChangeType.ADD:
+ self._model.append([name, name])
diff --git a/g13gui/g13gui/ui/profilepopover.py b/g13gui/g13gui/ui/profilepopover.py
new file mode 100644
index 0000000..f88032e
--- /dev/null
+++ b/g13gui/g13gui/ui/profilepopover.py
@@ -0,0 +1,129 @@
+import gi
+import enum
+
+import g13gui.model.bindings as bindings
+from g13gui.observer import GtkObserver
+from g13gui.model import BindingProfile
+from g13gui.model.bindings import G13ToGDK
+from g13gui.model.bindings import GDKToG13
+from g13gui.model.bindings import G13DKeyIsModifier
+from g13gui.model.bindings import StickMode
+from g13gui.model.bindings import ALL_STICK_MODES
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+from gi.repository import Gtk, GObject, Gdk
+
+
+class ProfilePopoverMode(enum.Enum):
+ EDIT = 'edit'
+ ADD = 'add'
+
+
+class ProfilePopover(Gtk.Popover, GtkObserver):
+ def __init__(self, prefs, mode=ProfilePopoverMode.EDIT):
+ Gtk.Popover.__init__(self)
+ GtkObserver.__init__(self)
+
+ self._prefs = prefs
+ self._mode = mode
+ self._lastRow = 0
+
+ self.build()
+ self.connect('show', self.shown)
+
+ def updateFromPrefs(self):
+ self._profileName.set_text(self._prefs.selectedProfileName())
+
+ profile = self._prefs.selectedProfile()
+ lcdColor = profile.lcdColor()
+ self._lcdColorButton.set_rgba(Gdk.RGBA(*lcdColor, alpha=1.0))
+
+ stickMode = profile.stickMode()
+ activeIndex = sorted(list(ALL_STICK_MODES)).index(stickMode)
+ self._stickModeCombo.set_active(activeIndex)
+
+ def commitToPrefs(self):
+ pass
+
+ def addRow(self, widget, labelText=None):
+ if labelText:
+ label = Gtk.Label()
+ label.set_text(labelText)
+ self._grid.attach(label, 1, self._lastRow, 1, 1)
+ self._grid.attach(widget, 2, self._lastRow, 1, 1)
+ else:
+ self._grid.attach(widget, 1, self._lastRow, 2, 1)
+ self._lastRow += 1
+
+ def build(self):
+ self._grid = Gtk.Grid()
+ self._grid.set_row_spacing(6)
+ self._grid.set_column_spacing(10)
+ self._grid.set_border_width(6)
+ self.add(self._grid)
+
+ self._profileName = Gtk.Entry()
+ self._profileName.set_can_focus(True)
+ self._profileName.set_activates_default(True)
+ self.addRow(self._profileName, 'Profile Name')
+
+ self._lcdColorButton = Gtk.ColorButton()
+ self._lcdColorButton.set_use_alpha(False)
+ self._lcdColorButton.set_rgba(Gdk.RGBA(*bindings.DEFAULT_LCD_COLOR))
+ self._lcdColorButton.set_title('LCD Color')
+ self.addRow(self._lcdColorButton, 'LCD Color')
+
+ self._stickModeCombo = Gtk.ComboBoxText()
+ for mode in sorted(list(ALL_STICK_MODES)):
+ self._stickModeCombo.append_text(mode.capitalize())
+ self._stickModeCombo.set_active(1)
+ self.addRow(self._stickModeCombo, 'Joystick Mode')
+
+ commitButton = Gtk.Button()
+ commitButton.set_receives_default(True)
+ commitButton.set_can_default(True)
+ commitButton.connect('clicked', self.commitClicked)
+
+ if self._mode == ProfilePopoverMode.EDIT:
+ commitButton.set_label('Update')
+ commitButton.get_style_context().add_class('suggested-action')
+ self.addRow(commitButton)
+
+ removeButton = Gtk.Button()
+ removeButton.set_label('Remove')
+ removeButton.connect('clicked', self.removeClicked)
+ removeButton.get_style_context().add_class('destructive-action')
+ self.addRow(removeButton)
+ else:
+ commitButton.set_label('Add')
+ commitButton.get_style_context().add_class('suggested-action')
+ self.addRow(commitButton)
+
+ self._grid.show_all()
+
+ def commitClicked(self, widget):
+ lcdColor = self._lcdColorButton.get_rgba()
+ lcdColor = (lcdColor.red, lcdColor.green, lcdColor.blue)
+ profileName = self._profileName.get_text()
+ stickMode = self._stickModeCombo.get_active_text()
+
+ profile = None
+ if self._mode == ProfilePopoverMode.ADD:
+ profile = BindingProfile()
+ self._prefs.addProfile(profileName, profile)
+ else:
+ profile = self._prefs.selectedProfile()
+
+ profile.setLCDColor(*lcdColor)
+ profile.setStickMode(stickMode.upper())
+
+ self.hide()
+
+ def removeClicked(self, widget):
+ pass
+
+ def shown(self, widget):
+ self._profileName.grab_focus()
+ if self._mode == ProfilePopoverMode.EDIT:
+ self.updateFromPrefs()