barotrauma-sunken-tapes/gui/widgets.py

227 lines
7.3 KiB
Python

from PySide6.QtCore import QSize, Qt, Slot, QRect, QFileSystemWatcher, Signal, QTime
from PySide6.QtGui import QIcon, QAction, QShortcut, QKeySequence, QPainter, QColor, QTextFormat, QPixmap, QFontMetrics
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QWidget, QLabel, QVBoxLayout, QHBoxLayout, \
QGroupBox, QLineEdit, QCheckBox, QSpinBox, QPlainTextEdit, QSizePolicy, QGridLayout, QTextEdit, QScrollArea, QFrame, \
QDoubleSpinBox, QFormLayout, QTimeEdit
import webbrowser
import validators
import datetime
from pathlib import Path
class LineNumberArea(QWidget):
def __init__(self, editor):
QWidget.__init__(self, editor)
self._code_editor = editor
def sizeHint(self):
return QSize(self._code_editor.line_number_area_width(), 0)
def paintEvent(self, event):
self._code_editor.line_number_area_paint_event(event)
class CodeEditor(QPlainTextEdit):
def __init__(self):
super().__init__()
self.line_number_area = LineNumberArea(self)
self.blockCountChanged[int].connect(self.update_line_number_area_width)
self.updateRequest[QRect, int].connect(self.update_line_number_area)
self.cursorPositionChanged.connect(self.highlight_current_line)
def line_number_area_width(self):
digits = len(str(self.blockCount()))
space = 3 + self.fontMetrics().horizontalAdvance('9') * digits
return space
def resizeEvent(self, e):
super().resizeEvent(e)
cr = self.contentsRect()
self.line_number_area.setGeometry(QRect(cr.left(),
cr.top(),
self.line_number_area_width(),
cr.height()))
def line_number_area_paint_event(self, event):
with QPainter(self.line_number_area) as painter:
painter.fillRect(event.rect(), Qt.white)
painter.setFont("Consolas")
block = self.firstVisibleBlock()
block_number = block.blockNumber()
top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
bottom = top + self.blockBoundingRect(block).height()
while block.isValid() and top <= event.rect().bottom():
if block.isVisible() and bottom >= event.rect().top():
painter.setPen(Qt.gray)
painter.drawText(-2, top,
self.line_number_area.width(),
self.fontMetrics().height(),
Qt.AlignmentFlag.AlignRight,
f"{block_number + 1}")
block = block.next()
top = bottom
bottom = top + self.blockBoundingRect(block).height()
block_number += 1
def update_line_number_area_width(self):
self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)
def update_line_number_area(self, rect, dy):
if dy:
self.line_number_area.scroll(0, dy)
else:
self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height())
if rect.contains(self.viewport().rect()):
self.update_line_number_area_width()
def highlight_current_line(self):
extra_selections = []
if not self.isReadOnly():
selection = QTextEdit.ExtraSelection()
selection.format.setBackground(QColor(Qt.lightGray))
selection.format.setProperty(QTextFormat.FullWidthSelection, True)
selection.cursor = self.textCursor()
selection.cursor.clearSelection()
extra_selections.append(selection)
self.setExtraSelections(extra_selections)
class SpinBoxWithoutWheel(QSpinBox):
def wheelEvent(self, event):
event.ignore()
class DoubleSpinBoxWithoutWheel(QDoubleSpinBox):
def wheelEvent(self, event):
event.ignore()
class ProbabilitySpinBox(DoubleSpinBoxWithoutWheel):
def __init__(self):
super().__init__()
self.setSingleStep(0.01)
self.valueChanged.connect(self.update_color)
self.update_color()
def update_color(self):
if self.value() > 0:
self.setStyleSheet("color: black;")
else:
self.setStyleSheet("color: gray;")
class TimeEditWithoutWheel(QTimeEdit):
def wheelEvent(self, event):
event.ignore()
class DirWidget(QWidget):
def __init__(self, default_dir: Path | str | None = None):
super().__init__()
layout = QHBoxLayout()
layout.setSpacing(2)
layout.setContentsMargins(0, 0, 0, 0)
self.dir_lineedit = QLineEdit()
self.dir_lineedit.setFont("Consolas")
self.dir_lineedit.setText(default_dir)
self.dir_set_btn = QPushButton()
self.dir_set_btn.setIcon(QIcon("./icons/ic_folder_open_48px.svg"))
self.dir_set_btn.setIconSize(QSize(16, 16))
self.dir_set_btn.setToolTip("Set directory with selection dialog")
self.dir_open_btn = QPushButton()
self.dir_open_btn.setIcon(QIcon("./icons/ic_open_in_new_24px.svg"))
self.dir_open_btn.setIconSize(QSize(16, 16))
self.dir_open_btn.setToolTip("Open directory")
layout.addWidget(self.dir_set_btn)
layout.addWidget(self.dir_lineedit)
layout.addWidget(self.dir_open_btn)
self.setLayout(layout)
class UrlLineEdit(QLineEdit):
def __init__(self, initial_text: str | None = None):
super().__init__()
self.setFont("Consolas")
self.textChanged.connect(self.color_text)
self.setText(initial_text)
def mousePressEvent(self, event):
modifiers = QApplication.keyboardModifiers()
if modifiers == Qt.ControlModifier:
self.open_url()
def open_url(self):
if validators.url(self.text()):
webbrowser.open(self.text())
def color_text(self):
if validators.url(self.text()):
self.setStyleSheet("color: blue;")
else:
self.setStyleSheet("color: black;")
class UrlWidget(QWidget):
def __init__(self, initial_url: str | None = None):
super().__init__()
layout = QHBoxLayout()
layout.setSpacing(2)
layout.setContentsMargins(0, 0, 0, 0)
self.url_lineedit = UrlLineEdit(initial_url)
self.url_lineedit.textChanged.connect(self.toggle_url_open_btn)
self.url_open_btn = QPushButton()
self.url_open_btn.setIcon(QIcon("./icons/ic_open_in_new_24px.svg"))
self.url_open_btn.setIconSize(QSize(16, 16))
self.url_open_btn.setToolTip("Open in browser")
self.url_open_btn.clicked.connect(self.url_lineedit.open_url)
self.toggle_url_open_btn()
layout.addWidget(self.url_lineedit)
layout.addWidget(self.url_open_btn)
self.setLayout(layout)
def toggle_url_open_btn(self):
url_valid = bool(validators.url(self.url_lineedit.text()))
self.url_open_btn.setEnabled(url_valid)
class TelegraphingCheckbox(QCheckBox):
hover = Signal(str)
def __init__(self, text: str, description_text: str | None = None):
super().__init__(text)
self.description_text = description_text
def enterEvent(self, event):
self.hover.emit(self.description_text)
def leaveEvent(self, event):
self.hover.emit("")