1036 lines
35 KiB
Python
1036 lines
35 KiB
Python
#!/usr/bin/env python3
|
||
import os
|
||
import platform
|
||
import sys
|
||
|
||
import yaml
|
||
|
||
try:
|
||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||
except:
|
||
print('alacritty-config-gui depends on PyQt5. I couldn’t locate PyQt5.')
|
||
print('Sorry about that.')
|
||
print('\nRunning `pip3 install PyQt5` could help.')
|
||
sys.exit(1)
|
||
|
||
|
||
ALACRITTY_CONFIG = os.path.expanduser('~/.config/alacritty/alacritty.yml')
|
||
NAME = 'Alacritty Config'
|
||
|
||
MODIFIERS = ['None', 'Command', 'Control', 'Option', 'Super', 'Shift', 'Alt']
|
||
|
||
|
||
def delete_layout_items(layout):
|
||
while layout.count():
|
||
item = layout.takeAt(0)
|
||
widget = item.widget()
|
||
if widget is not None:
|
||
widget.setParent(None)
|
||
else:
|
||
delete_layout_items(item.layout())
|
||
|
||
|
||
class ColorSelect(QtWidgets.QPushButton):
|
||
_value = None
|
||
|
||
def __init__(self, value):
|
||
super().__init__()
|
||
if value:
|
||
self.set_value(value.replace('0x', '#'))
|
||
self.clicked.connect(self.handle_clicked)
|
||
|
||
def handle_clicked(self):
|
||
color = QtWidgets.QColorDialog.getColor(QtGui.QColor(self._value))
|
||
|
||
if color.isValid():
|
||
self.set_value(color.name())
|
||
|
||
def set_value(self, value):
|
||
self._value = value
|
||
color = QtGui.QColor(self._value)
|
||
self.setFlat(True)
|
||
self.setAutoFillBackground(True)
|
||
self.setStyleSheet('''
|
||
QPushButton {{
|
||
color: {0};
|
||
background-color: {0};
|
||
border-style: outset;
|
||
}}
|
||
QPushButton:checked{{
|
||
color: {0};
|
||
background-color: {0};
|
||
border-style: outset;
|
||
}}
|
||
QPushButton:hover{{
|
||
background-color: {0};
|
||
border-style: outset;
|
||
}}
|
||
'''.format(color.name()))
|
||
self.update()
|
||
|
||
def value(self):
|
||
if self._value:
|
||
return self._value.replace('#', '0x')
|
||
|
||
|
||
class FontSelect(QtWidgets.QPushButton):
|
||
def __init__(self, family, style):
|
||
super().__init__()
|
||
self.set_value(family, style)
|
||
self.clicked.connect(self.handle_clicked)
|
||
|
||
def handle_clicked(self):
|
||
font, valid = QtWidgets.QFontDialog.getFont()
|
||
|
||
if valid:
|
||
self.set_value(font.family(), font.styleName())
|
||
|
||
def set_value(self, family, style):
|
||
self.family = family
|
||
self.style = style
|
||
self.setStyleSheet('''
|
||
QPushButton {{
|
||
background-color: white;
|
||
font-family: {0};
|
||
}}
|
||
QPushButton:checked{{
|
||
background-color: white;
|
||
}}
|
||
QPushButton:hover{{
|
||
background-color: white;
|
||
}}
|
||
'''.format(family))
|
||
self.setText(self.family)
|
||
self.update()
|
||
|
||
def value(self):
|
||
return {
|
||
'family': self.family,
|
||
'style': self.style
|
||
}
|
||
|
||
|
||
class Spoiler(QtWidgets.QWidget):
|
||
def __init__(self, title):
|
||
super().__init__()
|
||
self.toggle = QtWidgets.QToolButton()
|
||
self.toggle.setStyleSheet('QToolButton { border: none; }')
|
||
self.toggle.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
|
||
self.toggle.setArrowType(QtCore.Qt.ArrowType.RightArrow)
|
||
self.toggle.setText(title)
|
||
self.toggle.setCheckable(True)
|
||
self.toggle.setChecked(False)
|
||
|
||
self.header = QtWidgets.QFrame()
|
||
self.header.setFrameShape(QtWidgets.QFrame.HLine)
|
||
self.header.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||
self.header.setSizePolicy(
|
||
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum
|
||
)
|
||
|
||
self.content = QtWidgets.QScrollArea()
|
||
self.content.setStyleSheet('''
|
||
QScrollArea { border: none; background-color: transparent; }
|
||
''')
|
||
self.content.setSizePolicy(
|
||
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
|
||
)
|
||
self.content.setMaximumHeight(0)
|
||
self.content.setMinimumHeight(0)
|
||
self.animation = QtCore.QParallelAnimationGroup()
|
||
self.animation.addAnimation(
|
||
QtCore.QPropertyAnimation(self, b'minimumHeight')
|
||
)
|
||
self.animation.addAnimation(
|
||
QtCore.QPropertyAnimation(self, b'maximumHeight')
|
||
)
|
||
self.animation.addAnimation(
|
||
QtCore.QPropertyAnimation(self.content, b'maximumHeight')
|
||
)
|
||
self.layout = QtWidgets.QGridLayout()
|
||
self.layout.addWidget(self.toggle, 0, 0, 1, 1, QtCore.Qt.AlignLeft)
|
||
self.layout.addWidget(self.header, 0, 2, 1, 1)
|
||
self.layout.addWidget(self.content, 1, 0, 1, 3)
|
||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||
self.setLayout(self.layout)
|
||
self.toggle.clicked.connect(self.update_state)
|
||
|
||
def update_state(self, checked):
|
||
self.toggle.setArrowType(
|
||
QtCore.Qt.ArrowType.DownArrow if checked
|
||
else QtCore.Qt.ArrowType.RightArrow
|
||
)
|
||
self.animation.setDirection(
|
||
QtCore.QAbstractAnimation.Forward if checked
|
||
else QtCore.QAbstractAnimation.Backward
|
||
)
|
||
self.animation.start()
|
||
|
||
def set_layout(self, layout):
|
||
self.content.setLayout(layout)
|
||
collapsed_height = self.sizeHint().height() - self.content.maximumHeight()
|
||
content_height = layout.sizeHint().height()
|
||
for i in range(self.animation.animationCount()-1):
|
||
animation = self.animation.animationAt(i)
|
||
animation.setDuration(300)
|
||
animation.setStartValue(collapsed_height)
|
||
animation.setEndValue(collapsed_height + content_height)
|
||
last = self.animation.animationAt(self.animation.animationCount() - 1)
|
||
last.setDuration(30)
|
||
last.setStartValue(0)
|
||
last.setEndValue(content_height)
|
||
|
||
|
||
class CommandWidget(QtWidgets.QLineEdit):
|
||
def __init__(self, value):
|
||
super().__init__()
|
||
if value:
|
||
self.setText('{}{}'.format(value['program'], ''.join(value['args'])))
|
||
|
||
def value(self):
|
||
res = self.text().split(' ')
|
||
return {'program': res[0], 'args': res[1:]}
|
||
|
||
|
||
class KeyBindingDialog(QtWidgets.QDialog):
|
||
action_options = ([
|
||
'', 'Copy', 'Paste', 'PasteSelection', 'IncreaseFontSize', 'DecreaseFontSize',
|
||
'ResetFontSize', 'ScrollPageUp', 'ScrollPageDown', 'ScrollLineUp',
|
||
'ScrollLineDown', 'ScrollToTop', 'ScrollToBottom', 'ClearHistory', 'Hide',
|
||
'Quit', 'ToggleFullscreen', 'SpawnNewInstance', 'ClearLogNotice', 'None',
|
||
] + (['ToggleSimpleFullscreen'] if platform.system() == 'Darwin' else []))
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.result = {}
|
||
self.layout = QtWidgets.QFormLayout()
|
||
self.layout.setFieldGrowthPolicy(
|
||
QtWidgets.QFormLayout.AllNonFixedFieldsGrow
|
||
)
|
||
|
||
self.key = QtWidgets.QLineEdit()
|
||
self.layout.addRow(QtWidgets.QLabel('Key'), self.key)
|
||
|
||
self.mods = QtWidgets.QListWidget()
|
||
for option in MODIFIERS:
|
||
self.mods.addItem(option)
|
||
self.mods.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||
self.layout.addRow(QtWidgets.QLabel('Mods'), self.mods)
|
||
|
||
self.tabs = QtWidgets.QTabWidget()
|
||
|
||
self.action = QtWidgets.QComboBox()
|
||
for option in self.action_options:
|
||
self.action.addItem(option)
|
||
self.tabs.addTab(self.action, 'Action')
|
||
|
||
self.chars = QtWidgets.QLineEdit()
|
||
self.tabs.addTab(self.chars, 'Chars')
|
||
|
||
self.command = CommandWidget(None)
|
||
self.tabs.addTab(self.command, 'Command')
|
||
|
||
self.layout.addWidget(self.tabs)
|
||
|
||
buttons = QtWidgets.QDialogButtonBox()
|
||
buttons.addButton(buttons.Ok)
|
||
buttons.addButton(buttons.Cancel)
|
||
buttons.accepted.connect(self.do_accept)
|
||
buttons.rejected.connect(self.reject)
|
||
self.layout.addWidget(buttons)
|
||
|
||
self.setLayout(self.layout)
|
||
|
||
def do_accept(self):
|
||
key = self.key.text()
|
||
# TODO: this is an ugly heuristic. should we parse yaml here?
|
||
self.result['key'] = int(key) if key.isdigit() else key
|
||
|
||
mods = self.mods.selectedItems()
|
||
if len(mods):
|
||
self.result['mods'] = '|'.join(mod.text() for mod in mods)
|
||
|
||
idx = self.tabs.currentIndex()
|
||
title = self.tabs.tabText(idx).lower()
|
||
|
||
if title == 'action':
|
||
self.result['action'] = self.action.text()
|
||
elif title == 'chars':
|
||
self.result['chars'] = self.chars.text()
|
||
elif title == 'command':
|
||
self.result['command'] = self.command.value()
|
||
|
||
self.accept()
|
||
|
||
|
||
class MouseBindingDialog(QtWidgets.QDialog):
|
||
action_options = ([
|
||
'Copy', 'Paste', 'PasteSelection', 'IncreaseFontSize', 'DecreaseFontSize',
|
||
'ResetFontSize', 'ScrollPageUp', 'ScrollPageDown', 'ScrollLineUp',
|
||
'ScrollLineDown', 'ScrollToTop', 'ScrollToBottom', 'ClearHistory', 'Hide',
|
||
'Quit', 'ToggleFullscreen', 'SpawnNewInstance', 'ClearLogNotice', 'None',
|
||
] + (['ToggleSimpleFullscreen'] if platform.system() == 'Darwin' else []))
|
||
|
||
|
||
mouse_options = [
|
||
'Middle', 'Left', 'Right', '1', '2', '3', '4', '5', '6', '7', '8', '9'
|
||
]
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.result = {}
|
||
self.layout = QtWidgets.QFormLayout()
|
||
self.layout.setFieldGrowthPolicy(
|
||
QtWidgets.QFormLayout.AllNonFixedFieldsGrow
|
||
)
|
||
|
||
self.mouse = QtWidgets.QComboBox()
|
||
for option in self.mouse_options:
|
||
self.mouse.addItem(option)
|
||
self.layout.addRow(QtWidgets.QLabel('Mouse'), self.mouse)
|
||
|
||
self.mods = QtWidgets.QListWidget()
|
||
for option in MODIFIERS:
|
||
self.mods.addItem(option)
|
||
self.mods.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||
self.layout.addRow(QtWidgets.QLabel('Mods'), self.mods)
|
||
|
||
self.action = QtWidgets.QComboBox()
|
||
for option in self.action_options:
|
||
self.action.addItem(option)
|
||
self.layout.addRow(QtWidgets.QLabel('Action'), self.action)
|
||
|
||
buttons = QtWidgets.QDialogButtonBox()
|
||
buttons.addButton(buttons.Ok)
|
||
buttons.addButton(buttons.Cancel)
|
||
buttons.accepted.connect(self.do_accept)
|
||
buttons.rejected.connect(self.reject)
|
||
self.layout.addWidget(buttons)
|
||
|
||
self.setLayout(self.layout)
|
||
|
||
def do_accept(self):
|
||
mouse = self.mouse.currentText()
|
||
# TODO: this is an ugly heuristic. should we parse yaml here?
|
||
self.result['mouse'] = int(mouse) if mouse.isdigit() else mouse
|
||
|
||
mods = self.mods.selectedItems()
|
||
if len(mods):
|
||
self.result['mods'] = '|'.join(mod.text() for mod in mods)
|
||
|
||
self.result['action'] = self.action.currentText()
|
||
|
||
self.accept()
|
||
|
||
|
||
class MultiListWidget(QtWidgets.QWidget):
|
||
def __init__(self, elems):
|
||
super().__init__()
|
||
self.lst = None
|
||
self.elems = elems
|
||
self.init()
|
||
|
||
def init(self):
|
||
self.layout = QtWidgets.QVBoxLayout()
|
||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||
self.update()
|
||
btn_group = QtWidgets.QHBoxLayout()
|
||
btn_group.setAlignment(QtCore.Qt.AlignLeft)
|
||
self.layout.addLayout(btn_group)
|
||
btn = QtWidgets.QPushButton('+')
|
||
btn.setMaximumSize(40, 40)
|
||
btn.clicked.connect(self.open_dialog)
|
||
btn_group.addWidget(btn)
|
||
btn = QtWidgets.QPushButton('-')
|
||
btn.setMaximumSize(40, 40)
|
||
btn.clicked.connect(self.delete_selected)
|
||
btn_group.addWidget(btn)
|
||
self.setLayout(self.layout)
|
||
|
||
def update(self):
|
||
if self.lst:
|
||
self.lst.setParent(None)
|
||
self.lst = QtWidgets.QTableWidget(len(self.elems), 2)
|
||
self.lst.setShowGrid(False)
|
||
horizontal_header = self.lst.horizontalHeader()
|
||
horizontal_header.setVisible(False)
|
||
# TODO: why do i need this?
|
||
horizontal_header.resizeSection(0, 200)
|
||
horizontal_header.resizeSection(1, 200)
|
||
self.lst.verticalHeader().setVisible(False)
|
||
self.lst.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||
self.lst.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||
for i, binding in enumerate(self.elems):
|
||
key, action = self.itemize(binding)
|
||
self.lst.setItem(i, 0, QtWidgets.QTableWidgetItem(key))
|
||
self.lst.setItem(i, 1, QtWidgets.QTableWidgetItem(action))
|
||
self.layout.insertWidget(0, self.lst)
|
||
|
||
def open_dialog(self):
|
||
dialog = self.dialog()
|
||
|
||
if dialog.exec() == QtWidgets.QDialog.Accepted:
|
||
key_binding = dialog.result
|
||
self.elems.append(key_binding)
|
||
self.update()
|
||
|
||
def delete_selected(self):
|
||
to_delete = set()
|
||
for i in self.lst.selectedIndexes():
|
||
to_delete.add(i.row())
|
||
|
||
for i in sorted(to_delete, reverse=True):
|
||
del self.elems[i]
|
||
self.update()
|
||
|
||
def value(self):
|
||
return self.elems
|
||
|
||
|
||
class KeyBindingsWidget(MultiListWidget):
|
||
def __init__(self, *args):
|
||
super().__init__(*args)
|
||
self.dialog = KeyBindingDialog
|
||
|
||
def itemize(self, binding):
|
||
key = binding['key']
|
||
|
||
mods = binding.get('mods')
|
||
if mods:
|
||
key = '{} {}'.format(' '.join(mods.split('|')), key)
|
||
|
||
action = binding.get('action')
|
||
|
||
command = binding.get('command')
|
||
if command:
|
||
action = 'cmd({} {})'.format(command['program'], ' '.join(command['args']))
|
||
|
||
chars = binding.get('chars')
|
||
if chars:
|
||
action = 'chars({})'.format(chars)
|
||
return key, action
|
||
|
||
|
||
class MouseBindingsWidget(MultiListWidget):
|
||
def __init__(self, *args):
|
||
super().__init__(*args)
|
||
self.dialog = MouseBindingDialog
|
||
|
||
def itemize(self, binding):
|
||
key = binding['mouse']
|
||
|
||
mods = binding.get('mods')
|
||
if mods:
|
||
key = '{} {}'.format(' '.join(mods.split('|')), key)
|
||
|
||
action = binding.get('action')
|
||
|
||
return key, action
|
||
|
||
|
||
class ConfigWidget(QtWidgets.QWidget):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.widgets = {}
|
||
|
||
def prettify(self, s):
|
||
return ' '.join(x.capitalize() for x in s.split('_'))
|
||
|
||
|
||
def render_section(self, widgets, sup=None, name=None):
|
||
layout = QtWidgets.QFormLayout()
|
||
layout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
|
||
for wname, widget in widgets.items():
|
||
if type(widget) is dict:
|
||
self.render_section(widget, layout, wname)
|
||
else:
|
||
layout.addRow(QtWidgets.QLabel(self.prettify(wname)), widget)
|
||
|
||
if name and sup is not None:
|
||
layout.setContentsMargins(10, 0, 0, 0)
|
||
spoiler = Spoiler(self.prettify(name))
|
||
spoiler.set_layout(layout)
|
||
sup.addWidget(spoiler)
|
||
|
||
layout.setAlignment(QtCore.Qt.AlignTop)
|
||
return layout
|
||
|
||
def render_state(self):
|
||
self.layout = self.render_section(self.widgets)
|
||
self.layout.setContentsMargins(100, 10, 100, 10)
|
||
self.setLayout(self.layout)
|
||
|
||
def widget_value(self, widget):
|
||
typ = type(widget)
|
||
if typ is QtWidgets.QComboBox:
|
||
return widget.currentText()
|
||
if typ is QtWidgets.QCheckBox:
|
||
return widget.isChecked()
|
||
if typ is QtWidgets.QLineEdit:
|
||
return widget.text()
|
||
if typ is QtWidgets.QListWidget:
|
||
res = []
|
||
for i in range(widget.count()):
|
||
item = widget.item(i).text()
|
||
if item:
|
||
res.append(item)
|
||
return res
|
||
return widget.value()
|
||
|
||
def widgets_to_state(self, widgets):
|
||
state = {}
|
||
for name, widget in widgets.items():
|
||
if not name:
|
||
return self.widget_value(widget)
|
||
elif type(widget) is dict:
|
||
state[name] = self.widgets_to_state(widget)
|
||
else:
|
||
val = self.widget_value(widget)
|
||
if val is not None:
|
||
state[name] = val
|
||
return state
|
||
|
||
def gather_state(self):
|
||
return self.widgets_to_state(self.widgets)
|
||
|
||
|
||
class General(ConfigWidget):
|
||
cursor_style_options = ['Block', 'Underline', 'Beam']
|
||
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
self.widgets['background_opacity'] = QtWidgets.QDoubleSpinBox()
|
||
self.widgets['background_opacity'].setValue(config.get('background_opacity'))
|
||
self.widgets['cursor_style'] = QtWidgets.QComboBox()
|
||
for option in self.cursor_style_options:
|
||
self.widgets['cursor_style'].addItem(option)
|
||
cur = config.get('cursor_style')
|
||
if cur in self.cursor_style_options:
|
||
self.widgets['cursor_style'].setCurrentIndex(
|
||
self.cursor_style_options.index(cur)
|
||
)
|
||
self.widgets['custom_cursor_colors'] = QtWidgets.QCheckBox()
|
||
self.widgets['custom_cursor_colors'].setChecked(
|
||
config.get('custom_cursor_colors', False)
|
||
)
|
||
self.widgets['draw_bold_text_with_bright_colors'] = QtWidgets.QCheckBox()
|
||
self.widgets['draw_bold_text_with_bright_colors'].setChecked(
|
||
config.get('draw_bold_text_with_bright_colors', False)
|
||
)
|
||
self.widgets['dynamic_title'] = QtWidgets.QCheckBox()
|
||
self.widgets['dynamic_title'].setChecked(
|
||
config.get('dynamic_title', False)
|
||
)
|
||
self.widgets['hide_cursor_when_typing'] = QtWidgets.QCheckBox()
|
||
self.widgets['hide_cursor_when_typing'].setChecked(
|
||
config.get('hide_cursor_when_typing', False)
|
||
)
|
||
self.widgets['live_config_reload'] = QtWidgets.QCheckBox()
|
||
self.widgets['live_config_reload'].setChecked(
|
||
config.get('live_config_reload', False)
|
||
)
|
||
self.widgets['tabspaces'] = QtWidgets.QSpinBox()
|
||
self.widgets['tabspaces'].setValue(config.get('tabspaces'))
|
||
self.widgets['unfocused_hollow_cursor'] = QtWidgets.QCheckBox()
|
||
self.widgets['unfocused_hollow_cursor'].setChecked(
|
||
config.get('unfocused_hollow_cursor', False)
|
||
)
|
||
self.render_state()
|
||
|
||
|
||
class Debug(ConfigWidget):
|
||
log_levels = ['None', 'Error', 'Warn', 'Info', 'Debug', 'Trace']
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
self.widgets['render_timer'] = QtWidgets.QCheckBox()
|
||
self.widgets['render_timer'].setChecked(
|
||
config.get('render_timer', False)
|
||
)
|
||
self.widgets['persistent_logging'] = QtWidgets.QCheckBox()
|
||
self.widgets['persistent_logging'].setChecked(
|
||
config.get('persistent_logging', False)
|
||
)
|
||
|
||
self.widgets['log_level'] = QtWidgets.QComboBox()
|
||
for option in self.log_levels:
|
||
self.widgets['log_level'].addItem(option)
|
||
dec = config.get('log_level')
|
||
if dec in self.log_levels:
|
||
self.widgets['log_level'].setCurrentIndex(
|
||
self.log_levels.index(dec)
|
||
)
|
||
|
||
self.widgets['print_events'] = QtWidgets.QCheckBox()
|
||
self.widgets['print_events'].setChecked(
|
||
config.get('print_events', False)
|
||
)
|
||
self.widgets['ref_test'] = QtWidgets.QCheckBox()
|
||
self.widgets['ref_test'].setChecked(config.get('ref_test', False))
|
||
self.render_state()
|
||
|
||
|
||
class Env(ConfigWidget):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
self.widgets['TERM'] = QtWidgets.QLineEdit(config.get('TERM'))
|
||
self.render_state()
|
||
|
||
|
||
class Selection(ConfigWidget):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
self.widgets['semantic_escape_chars'] = QtWidgets.QLineEdit(
|
||
config.get('semantic_escape_chars')
|
||
)
|
||
self.widgets['save_to_clipboard'] = QtWidgets.QCheckBox()
|
||
self.widgets['save_to_clipboard'].setChecked(
|
||
config.get('save_to_clipboard', False)
|
||
)
|
||
self.render_state()
|
||
|
||
|
||
class Shell(ConfigWidget):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
self.widgets['program'] = QtWidgets.QLineEdit(config.get('program'))
|
||
self.widgets['args'] = QtWidgets.QListWidget()
|
||
for elem in config.get('args', []):
|
||
self.widgets['args'].addItem(elem)
|
||
for _ in range(3):
|
||
widget = QtWidgets.QListWidgetItem('')
|
||
widget.setFlags(widget.flags() | QtCore.Qt.ItemIsEditable)
|
||
self.widgets['args'].addItem(widget)
|
||
self.render_state()
|
||
|
||
|
||
class Font(ConfigWidget):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
glyph_offset_x = QtWidgets.QSpinBox()
|
||
glyph_offset_x.setValue(config.get('glyph_offset', {}).get('x'))
|
||
glyph_offset_y = QtWidgets.QSpinBox()
|
||
glyph_offset_y.setValue(config.get('glyph_offset', {}).get('y'))
|
||
offset_x = QtWidgets.QSpinBox()
|
||
offset_x.setValue(config.get('offset', {}).get('x'))
|
||
offset_y = QtWidgets.QSpinBox()
|
||
offset_y.setValue(config.get('offset', {}).get('y'))
|
||
scale_with_dpi = QtWidgets.QCheckBox()
|
||
scale_with_dpi.setChecked(config.get('scale_with_dpi', False))
|
||
size = QtWidgets.QDoubleSpinBox()
|
||
size.setValue(config.get('size'))
|
||
use_thin_strokes = QtWidgets.QCheckBox()
|
||
use_thin_strokes.setChecked(config.get('use_thin_strokes', False))
|
||
|
||
normal_config = config.get('normal', {})
|
||
normal = FontSelect(normal_config.get('family'), normal_config.get('style'))
|
||
|
||
italic_config = config.get('italic', {})
|
||
italic = FontSelect(italic_config.get('family'), italic_config.get('style'))
|
||
|
||
bold_config = config.get('bold', {})
|
||
bold = FontSelect(bold_config.get('family'), bold_config.get('style'))
|
||
|
||
self.widgets = {
|
||
'scale_with_dpi': scale_with_dpi,
|
||
'size': size,
|
||
'use_thin_strokes': use_thin_strokes,
|
||
'glyph_offset': {
|
||
'x': glyph_offset_x,
|
||
'y': glyph_offset_y,
|
||
},
|
||
'offset': {
|
||
'x': offset_x,
|
||
'y': offset_y,
|
||
},
|
||
'normal': normal,
|
||
'italic': italic,
|
||
'bold': bold,
|
||
}
|
||
|
||
self.render_state()
|
||
|
||
|
||
class Colors(ConfigWidget):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
|
||
primary_background = ColorSelect(
|
||
config.get('primary', {}).get('background')
|
||
)
|
||
primary_foreground = ColorSelect(
|
||
config.get('primary', {}).get('foreground')
|
||
)
|
||
|
||
cursor_background = ColorSelect(
|
||
config.get('cursor', {}).get('cursor')
|
||
)
|
||
cursor_foreground = ColorSelect(
|
||
config.get('cursor', {}).get('text')
|
||
)
|
||
|
||
normal_black = ColorSelect(config.get('normal', {}).get('black'))
|
||
normal_blue = ColorSelect(config.get('normal', {}).get('blue'))
|
||
normal_cyan = ColorSelect(config.get('normal', {}).get('cyan'))
|
||
normal_green = ColorSelect(config.get('normal', {}).get('green'))
|
||
normal_magenta = ColorSelect(config.get('normal', {}).get('magenta'))
|
||
normal_red = ColorSelect(config.get('normal', {}).get('red'))
|
||
normal_white = ColorSelect(config.get('normal', {}).get('white'))
|
||
normal_yellow = ColorSelect(config.get('normal', {}).get('yellow'))
|
||
|
||
bright_black = ColorSelect(config.get('bright', {}).get('black'))
|
||
bright_blue = ColorSelect(config.get('bright', {}).get('blue'))
|
||
bright_cyan = ColorSelect(config.get('bright', {}).get('cyan'))
|
||
bright_green = ColorSelect(config.get('bright', {}).get('green'))
|
||
bright_magenta = ColorSelect(config.get('bright', {}).get('magenta'))
|
||
bright_red = ColorSelect(config.get('bright', {}).get('red'))
|
||
bright_white = ColorSelect(config.get('bright', {}).get('white'))
|
||
bright_yellow = ColorSelect(config.get('bright', {}).get('yellow'))
|
||
|
||
dim_black = ColorSelect(config.get('dim', {}).get('black'))
|
||
dim_blue = ColorSelect(config.get('dim', {}).get('blue'))
|
||
dim_cyan = ColorSelect(config.get('dim', {}).get('cyan'))
|
||
dim_green = ColorSelect(config.get('dim', {}).get('green'))
|
||
dim_magenta = ColorSelect(config.get('dim', {}).get('magenta'))
|
||
dim_red = ColorSelect(config.get('dim', {}).get('red'))
|
||
dim_white = ColorSelect(config.get('dim', {}).get('white'))
|
||
dim_yellow = ColorSelect(config.get('dim', {}).get('yellow'))
|
||
|
||
self.widgets = {
|
||
'primary': {
|
||
'background': primary_background,
|
||
'foreground': primary_foreground,
|
||
},
|
||
'cursor': {
|
||
'cursor': cursor_background,
|
||
'text': cursor_foreground,
|
||
},
|
||
'normal': {
|
||
'black': normal_black,
|
||
'blue': normal_blue,
|
||
'cyan': normal_cyan,
|
||
'green': normal_green,
|
||
'magenta': normal_magenta,
|
||
'red': normal_red,
|
||
'white': normal_white,
|
||
'yellow': normal_yellow,
|
||
},
|
||
'bright': {
|
||
'black': bright_black,
|
||
'blue': bright_blue,
|
||
'cyan': bright_cyan,
|
||
'green': bright_green,
|
||
'magenta': bright_magenta,
|
||
'red': bright_red,
|
||
'white': bright_white,
|
||
'yellow': bright_yellow,
|
||
},
|
||
'dim': {
|
||
'black': dim_black,
|
||
'blue': dim_blue,
|
||
'cyan': dim_cyan,
|
||
'green': dim_green,
|
||
'magenta': dim_magenta,
|
||
'red': dim_red,
|
||
'white': dim_white,
|
||
'yellow': dim_yellow,
|
||
},
|
||
}
|
||
|
||
self.render_state()
|
||
|
||
|
||
class Window(ConfigWidget):
|
||
decoration_options = (
|
||
['full', 'none'] +
|
||
(['transparent', 'buttonless'] if platform.system() == 'Darwin' else [])
|
||
)
|
||
|
||
startup_options = (
|
||
['Windowed', 'FullScreen', 'Maximized'] +
|
||
(['SimpleFullScreen'] if platform.system() == 'Darwin' else [])
|
||
)
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
decorations = QtWidgets.QComboBox()
|
||
for option in self.decoration_options:
|
||
decorations.addItem(option)
|
||
dec = config.get('decorations')
|
||
if dec in self.decoration_options:
|
||
decorations.setCurrentIndex(self.decoration_options.index(dec))
|
||
|
||
startup_mode = QtWidgets.QComboBox()
|
||
for mode in self.startup_options:
|
||
startup_mode.addItem(mode)
|
||
dec = config.get('startup_mode')
|
||
if dec in self.startup_options:
|
||
startup_mode.setCurrentIndex(self.startup_options.index(dec))
|
||
|
||
columns = QtWidgets.QSpinBox()
|
||
columns.setMaximum(300)
|
||
columns.setValue(config.get('dimensions', {}).get('columns', 80))
|
||
lines = QtWidgets.QSpinBox()
|
||
columns.setMaximum(200)
|
||
lines.setValue(config.get('dimensions', {}).get('lines', 24))
|
||
|
||
padding_x = QtWidgets.QSpinBox()
|
||
padding_x.setValue(config.get('padding', {}).get('x', 0))
|
||
padding_y = QtWidgets.QSpinBox()
|
||
padding_y.setValue(config.get('padding', {}).get('y', 0))
|
||
|
||
dynamic_padding = QtWidgets.QCheckBox()
|
||
dynamic_padding.setChecked(config.get('dynamic_padding', False))
|
||
|
||
title = QtWidgets.QLineEdit(config.get('title', 'Alacritty'))
|
||
|
||
self.widgets = {
|
||
'title': title,
|
||
'decorations': decorations,
|
||
'startup_mode': startup_mode,
|
||
'dynamic_padding': dynamic_padding,
|
||
'padding': {
|
||
'x': padding_x,
|
||
'y': padding_y,
|
||
},
|
||
'dimensions': {
|
||
'columns': columns,
|
||
'lines': lines,
|
||
}
|
||
}
|
||
|
||
if platform.system() == 'Linux':
|
||
self.widgets['class'] = QtWidgets.QLineEdit(
|
||
config.get('class', 'Alacritty')
|
||
)
|
||
|
||
self.render_state()
|
||
|
||
|
||
class Scrolling(ConfigWidget):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
history = QtWidgets.QSpinBox()
|
||
history.setMaximum(2**30)
|
||
history.setValue(config.get('history', 10000))
|
||
multiplier = QtWidgets.QSpinBox()
|
||
multiplier.setValue(config.get('multiplier', 3))
|
||
faux_multiplier = QtWidgets.QSpinBox()
|
||
faux_multiplier.setValue(config.get('faux_multiplier', 3))
|
||
autoscroll = QtWidgets.QCheckBox()
|
||
autoscroll.setChecked(config.get('autoscroll', False))
|
||
|
||
self.widgets = {
|
||
'history': history,
|
||
'multiplier': multiplier,
|
||
'faux_multiplier': faux_multiplier,
|
||
'faux_multiplier': faux_multiplier,
|
||
'autoscroll': autoscroll,
|
||
}
|
||
|
||
self.render_state()
|
||
|
||
|
||
class KeyBindings(ConfigWidget):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
|
||
key_bindings = KeyBindingsWidget(config)
|
||
|
||
self.widgets = {
|
||
'': key_bindings,
|
||
}
|
||
|
||
self.render_state()
|
||
|
||
|
||
class MouseBindings(ConfigWidget):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
|
||
mouse_bindings = MouseBindingsWidget(config)
|
||
|
||
self.widgets = {
|
||
'': mouse_bindings,
|
||
}
|
||
|
||
self.render_state()
|
||
|
||
|
||
class Mouse(ConfigWidget):
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
double_threshold = QtWidgets.QSpinBox()
|
||
double_threshold.setMaximum(10000)
|
||
double_threshold.setValue(config.get('double_click', {}).get('threshold'))
|
||
triple_threshold = QtWidgets.QSpinBox()
|
||
triple_threshold.setMaximum(10000)
|
||
triple_threshold.setValue(config.get('triple_click', {}).get('threshold'))
|
||
|
||
hide_when_typing = QtWidgets.QCheckBox()
|
||
hide_when_typing.setChecked(config.get('hide_when_typing', False))
|
||
|
||
launcher = CommandWidget(config.get('url', {}).get('launcher'))
|
||
|
||
modifiers = QtWidgets.QComboBox()
|
||
for modifier in MODIFIERS:
|
||
modifiers.addItem(modifier)
|
||
dec = config.get('modifiers')
|
||
if dec in MODIFIERS:
|
||
modifiers.setCurrentIndex(MODIFIERS.index(dec))
|
||
|
||
self.widgets = {
|
||
'hide_when_typing': hide_when_typing,
|
||
'url': {
|
||
'launcher': launcher,
|
||
'modifiers': modifiers,
|
||
},
|
||
'double_click': {
|
||
'threshold': double_threshold,
|
||
},
|
||
'triple_click': {
|
||
'threshold': triple_threshold,
|
||
},
|
||
}
|
||
|
||
self.render_state()
|
||
|
||
|
||
class VisualBell(ConfigWidget):
|
||
animation_options = [
|
||
'Ease', 'EaseOut', 'EaseOutSine', 'EaseOutQuad', 'EaseOutCubic',
|
||
'EaseOutQuart', 'EaseOutQuint', 'EaseOutExpo', 'EaseOutCirc', 'Linear',
|
||
]
|
||
def __init__(self, config):
|
||
super().__init__()
|
||
duration = QtWidgets.QSpinBox()
|
||
duration.setMaximum(10000)
|
||
duration.setValue(config.get('duration'))
|
||
|
||
animation = QtWidgets.QComboBox()
|
||
for a in self.animation_options:
|
||
animation.addItem(a)
|
||
dec = config.get('animation')
|
||
if dec in self.animation_options:
|
||
animation.setCurrentIndex(self.animation_options.index(dec))
|
||
|
||
self.widgets = {
|
||
'duration': duration,
|
||
'animation': animation,
|
||
}
|
||
|
||
self.render_state()
|
||
|
||
|
||
class Config(QtWidgets.QWidget):
|
||
def __init__(self, config, dry=False):
|
||
super().__init__()
|
||
self.config = config
|
||
self.dry = dry
|
||
|
||
self.layout = QtWidgets.QVBoxLayout()
|
||
self.setLayout(self.layout)
|
||
|
||
self.add_tabs()
|
||
self.add_buttons()
|
||
|
||
def add_tabs(self):
|
||
self.tabs = QtWidgets.QTabWidget()
|
||
|
||
self.tabs.addTab(General(self.config), 'General')
|
||
self.tabs.setTabToolTip(0, 'General')
|
||
self.tabs.addTab(Window(self.config.get('window', {})), 'Window')
|
||
self.tabs.setTabToolTip(1, 'Window')
|
||
self.tabs.addTab(Font(self.config.get('font', {})), 'Font')
|
||
self.tabs.setTabToolTip(2, 'Font')
|
||
self.tabs.addTab(Debug(self.config.get('debug', {})), 'Debug')
|
||
self.tabs.setTabToolTip(3, 'Debug')
|
||
self.tabs.addTab(Env(self.config.get('env', {})), 'Env')
|
||
self.tabs.setTabToolTip(4, 'Env')
|
||
self.tabs.addTab(Selection(self.config.get('selection', {})), 'Selection')
|
||
self.tabs.setTabToolTip(5, 'Selection')
|
||
self.tabs.addTab(Shell(self.config.get('shell', {})), 'Shell')
|
||
self.tabs.setTabToolTip(6, 'Shell')
|
||
self.tabs.addTab(Colors(self.config.get('colors', {})), 'Colors')
|
||
self.tabs.setTabToolTip(7, 'Colors')
|
||
self.tabs.addTab(Scrolling(self.config.get('scrolling', {})), 'Scrolling')
|
||
self.tabs.setTabToolTip(8, 'Scrolling')
|
||
self.tabs.addTab(
|
||
KeyBindings(self.config.get('key_bindings', [])), 'Key Bindings'
|
||
)
|
||
self.tabs.setTabToolTip(9, 'Key Bindings')
|
||
self.tabs.addTab(
|
||
MouseBindings(self.config.get('mouse_bindings', [])), 'Mouse Bindings'
|
||
)
|
||
self.tabs.setTabToolTip(10, 'Mouse Bindings')
|
||
self.tabs.addTab(Mouse(self.config.get('mouse', {})), 'Mouse')
|
||
self.tabs.setTabToolTip(11, 'Mouse')
|
||
self.tabs.addTab(VisualBell(self.config.get('visual_bell', {})), 'Visual Bell')
|
||
self.tabs.setTabToolTip(12, 'Visual Bell')
|
||
|
||
self.layout.addWidget(self.tabs)
|
||
|
||
def add_buttons(self):
|
||
self.buttons = QtWidgets.QDialogButtonBox()
|
||
ok_button = self.buttons.addButton(self.buttons.Ok)
|
||
self.buttons.addButton(self.buttons.Cancel)
|
||
self.buttons.accepted.connect(self.save)
|
||
self.buttons.rejected.connect(sys.exit)
|
||
self.layout.addWidget(self.buttons)
|
||
|
||
def gather_state(self):
|
||
state = self.config
|
||
|
||
for idx in range(self.tabs.count()):
|
||
tab = self.tabs.widget(idx)
|
||
title = '_'.join(self.tabs.tabText(idx).lower().split(' '))
|
||
old = state.get(title, {})
|
||
if type(old) == list:
|
||
state[title].extend(e for e in tab.gather_state() if e not in old)
|
||
else:
|
||
if title == 'general':
|
||
state = {**state, **tab.gather_state()}
|
||
else:
|
||
state[title] = {**state.get(title, {}), **tab.gather_state()}
|
||
|
||
return state
|
||
|
||
def save(self):
|
||
state = yaml.dump(self.gather_state())
|
||
if self.dry:
|
||
print(state)
|
||
else:
|
||
with open(ALACRITTY_CONFIG, 'w+') as f:
|
||
f.write(state)
|
||
sys.exit()
|
||
|
||
|
||
def main():
|
||
dry = False
|
||
if len(sys.argv) == 2 and sys.argv[1] in ['--dry', '-d']:
|
||
dry = True
|
||
|
||
app = QtWidgets.QApplication([NAME])
|
||
app.setApplicationName(NAME)
|
||
|
||
window = QtWidgets.QMainWindow()
|
||
with open(ALACRITTY_CONFIG) as f:
|
||
config = yaml.safe_load(f.read())
|
||
window.setCentralWidget(Config(config, dry=dry))
|
||
window.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||
window.setWindowTitle(NAME)
|
||
window.setUnifiedTitleAndToolBarOnMac(True)
|
||
window.setGeometry(
|
||
QtWidgets.QStyle.alignedRect(
|
||
QtCore.Qt.LeftToRight,
|
||
QtCore.Qt.AlignCenter,
|
||
QtCore.QSize(500, 300),
|
||
app.desktop().availableGeometry()
|
||
)
|
||
)
|
||
window.show()
|
||
|
||
app.exec_()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|