1
0
Fork 0
mirror of synced 2024-06-28 03:00:49 +12:00
Rare/rare/components/tabs/settings/widgets/wrapper.py
loathingKernel 95fba89277
Wrapper: Truncate text on widgets to show only the command
Truncate the widget's text down to the command only without the arguments.
Show the full command with the arguments in the tooltip instead.

Update some text.
2023-11-28 16:38:00 +02:00

306 lines
11 KiB
Python

import re
import shutil
from logging import getLogger
from typing import Dict, List
from PyQt5.QtCore import pyqtSignal, QSettings, QSize, Qt, QMimeData, pyqtSlot, QCoreApplication
from PyQt5.QtGui import QDrag, QDropEvent, QDragEnterEvent, QDragMoveEvent, QFont, QMouseEvent
from PyQt5.QtWidgets import (
QHBoxLayout,
QLabel,
QPushButton,
QInputDialog,
QFrame,
QMessageBox,
QSizePolicy,
QWidget,
QScrollArea,
)
from rare.shared import RareCore
from rare.ui.components.tabs.settings.widgets.wrapper import Ui_WrapperSettings
from rare.utils import config_helper
from rare.utils.misc import icon
logger = getLogger("WrapperSettings")
extra_wrapper_regex = {
"proton": "\".*proton\" run", # proton
"mangohud": "mangohud" # mangohud
}
class Wrapper:
pass
class WrapperWidget(QFrame):
delete_wrapper = pyqtSignal(str)
def __init__(self, text: str, show_text=None, parent=None):
super(WrapperWidget, self).__init__(parent=parent)
if not show_text:
show_text = text.split(" ")[0]
self.setFrameShape(QFrame.StyledPanel)
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
self.text = text
self.text_lbl = QLabel(show_text, parent=self)
self.text_lbl.setFont(QFont("monospace"))
self.image_lbl = QLabel(parent=self)
self.image_lbl.setPixmap(icon("mdi.drag-vertical").pixmap(QSize(20, 20)))
self.setToolTip(text)
self.delete_button = QPushButton(icon("ei.remove"), "", parent=self)
if show_text in extra_wrapper_regex.keys():
self.delete_button.setDisabled(True)
self.delete_button.setToolTip(self.tr("Disable in settings"))
else:
self.delete_button.setToolTip(self.tr("Remove"))
self.delete_button.clicked.connect(self.delete)
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.image_lbl)
layout.addWidget(self.text_lbl)
layout.addWidget(self.delete_button)
self.setLayout(layout)
# lk: set object names for the stylesheet
self.setObjectName(type(self).__name__)
self.delete_button.setObjectName(f"{self.objectName()}Button")
def delete(self):
self.delete_wrapper.emit(self.text)
def mouseMoveEvent(self, e):
if e.buttons() == Qt.LeftButton:
drag = QDrag(self)
mime = QMimeData()
drag.setMimeData(mime)
drag.exec_(Qt.MoveAction)
def mouseDoubleClickEvent(self, a0: QMouseEvent) -> None:
if a0.button() == Qt.LeftButton:
a0.accept()
pass # self.edit
class WrapperSettings(QWidget, Ui_WrapperSettings):
def __init__(self):
super(WrapperSettings, self).__init__()
self.setupUi(self)
self.wrappers: Dict[str, WrapperWidget] = {}
self.app_name: str
self.wrapper_scroll = QScrollArea(self.widget_stack)
self.wrapper_scroll.setWidgetResizable(True)
self.wrapper_scroll.setSizeAdjustPolicy(QScrollArea.AdjustToContents)
self.wrapper_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.wrapper_scroll.setProperty("no_kinetic_scroll", True)
self.scroll_content = WrapperContainer(
save_cb=self.save, parent=self.wrapper_scroll
)
self.wrapper_scroll.setWidget(self.scroll_content)
self.widget_stack.insertWidget(0, self.wrapper_scroll)
self.core = RareCore.instance().core()
self.add_button.clicked.connect(self.add_button_pressed)
self.settings = QSettings()
self.wrapper_scroll.horizontalScrollBar().rangeChanged.connect(self.adjust_scrollarea)
# lk: set object names for the stylesheet
self.setObjectName(type(self).__name__)
self.no_wrapper_label.setObjectName(f"{self.objectName()}Label")
self.wrapper_scroll.setObjectName(f"{self.objectName()}Scroll")
self.wrapper_scroll.horizontalScrollBar().setObjectName(
f"{self.wrapper_scroll.objectName()}Bar")
self.wrapper_scroll.verticalScrollBar().setObjectName(
f"{self.wrapper_scroll.objectName()}Bar")
@pyqtSlot(int, int)
def adjust_scrollarea(self, min: int, max: int):
wrapper_widget = self.scroll_content.findChild(WrapperWidget)
if not wrapper_widget:
return
# lk: when the scrollbar is not visible, min and max are 0
if max > min:
self.wrapper_scroll.setMaximumHeight(
wrapper_widget.sizeHint().height()
+ self.wrapper_scroll.rect().height() // 2
- self.wrapper_scroll.contentsRect().height() // 2
+ self.scroll_content.layout().spacing()
+ self.wrapper_scroll.horizontalScrollBar().sizeHint().height()
)
else:
self.wrapper_scroll.setMaximumHeight(
wrapper_widget.sizeHint().height()
+ self.wrapper_scroll.rect().height()
- self.wrapper_scroll.contentsRect().height()
)
def get_wrapper_string(self):
return " ".join(self.get_wrapper_list())
def get_wrapper_list(self):
wrappers = list(self.wrappers.values())
wrappers.sort(key=lambda x: self.scroll_content.layout().indexOf(x))
return [w.text for w in wrappers]
def add_button_pressed(self):
header = self.tr("Add wrapper")
wrapper, done = QInputDialog.getText(
self, f"{header} - {QCoreApplication.instance().applicationName()}", self.tr("Insert wrapper executable")
)
if not done:
return
self.add_wrapper(wrapper)
def add_wrapper(self, text: str, from_load=False):
if text == "mangohud" and self.wrappers.get("mangohud"):
return
show_text = ""
for key, extra_wrapper in extra_wrapper_regex.items():
if re.match(extra_wrapper, text):
show_text = key
if not show_text:
show_text = text.split()[0]
# validate
if not text.strip(): # is empty
return
if not from_load:
if self.wrappers.get(text):
QMessageBox.warning(
self, self.tr("Warning"), self.tr("Wrapper is already in the list")
)
return
if show_text != "proton" and not shutil.which(text.split()[0]):
if QMessageBox.question(
self, self.tr("Warning"), self.tr("Wrapper is not in $PATH. Ignore? "),
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
) == QMessageBox.No:
return
if text == "proton":
QMessageBox.warning(
self, self.tr("Warning"), self.tr("Do not insert proton manually. Add it through Proton settings")
)
return
self.widget_stack.setCurrentIndex(0)
if widget := self.wrappers.get(show_text, None):
widget.deleteLater()
widget = WrapperWidget(text, show_text, self.scroll_content)
self.scroll_content.layout().addWidget(widget)
self.adjust_scrollarea(
self.wrapper_scroll.horizontalScrollBar().minimum(),
self.wrapper_scroll.horizontalScrollBar().maximum()
)
widget.delete_wrapper.connect(self.delete_wrapper)
self.wrappers[show_text] = widget
if not from_load:
self.save()
def delete_wrapper(self, text: str):
widget = self.wrappers.get(text, None)
if widget:
self.wrappers.pop(text)
widget.deleteLater()
if not self.wrappers:
self.wrapper_scroll.setMaximumHeight(self.label_page.sizeHint().height())
self.widget_stack.setCurrentIndex(1)
self.save()
def save(self):
# save wrappers twice, to support wrappers with spaces
if len(self.wrappers) == 0:
config_helper.remove_option(self.app_name, "wrapper")
self.settings.remove(f"{self.app_name}/wrapper")
else:
config_helper.add_option(self.app_name, "wrapper", self.get_wrapper_string())
self.settings.setValue(f"{self.app_name}/wrapper", self.get_wrapper_list())
def load_settings(self, app_name: str):
self.app_name = app_name
for i in self.wrappers.values():
i.deleteLater()
self.wrappers.clear()
wrappers = self.settings.value(f"{self.app_name}/wrapper", [], str)
if not wrappers and (cfg := self.core.lgd.config.get(self.app_name, "wrapper", fallback="")):
logger.info("Loading wrappers from legendary config")
# no qt wrapper, but legendary wrapper, to have backward compatibility
pattern = re.compile(r'''((?:[^ "']|"[^"]*"|'[^']*')+)''')
wrappers = pattern.split(cfg)[1::2]
for wrapper in wrappers:
self.add_wrapper(wrapper, True)
if not self.wrappers:
self.wrapper_scroll.setMaximumHeight(self.label_page.sizeHint().height())
self.widget_stack.setCurrentIndex(1)
else:
self.widget_stack.setCurrentIndex(0)
self.save()
class WrapperContainer(QWidget):
drag_widget: QWidget
def __init__(self, save_cb, parent=None):
super(WrapperContainer, self).__init__(parent=parent)
self.setAcceptDrops(True)
self.save = save_cb
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
self.setLayout(layout)
# lk: set object names for the stylesheet
self.setObjectName(type(self).__name__)
def dragEnterEvent(self, e: QDragEnterEvent):
widget = e.source()
self.drag_widget = widget
e.accept()
def _get_drop_index(self, x):
drag_idx = self.layout().indexOf(self.drag_widget)
if drag_idx > 0:
prev_widget = self.layout().itemAt(drag_idx - 1).widget()
if x < self.drag_widget.x() - prev_widget.width() // 2:
return drag_idx - 1
if drag_idx < self.layout().count() - 1:
next_widget = self.layout().itemAt(drag_idx + 1).widget()
if x > self.drag_widget.x() + self.drag_widget.width() + next_widget.width() // 2:
return drag_idx + 1
return drag_idx
def dragMoveEvent(self, e: QDragMoveEvent) -> None:
i = self._get_drop_index(e.pos().x())
self.layout().insertWidget(i, self.drag_widget)
def dropEvent(self, e: QDropEvent):
pos = e.pos()
widget = e.source()
index = self._get_drop_index(pos.x())
self.layout().insertWidget(index, widget)
self.drag_widget = None
e.accept()
self.save()