513 lines
13 KiB
Python
513 lines
13 KiB
Python
from PySide6.QtCore import Qt, QRect, QPropertyAnimation, Property, Signal, QPoint, QSize
|
|
from PySide6.QtGui import QPainter, QColor, QFont
|
|
from PySide6.QtWidgets import QWidget, QCheckBox, QAbstractButton
|
|
|
|
|
|
class ToggleSwitch(QAbstractButton):
|
|
clicked = Signal(bool)
|
|
stateChanged = Signal(int) # Qt.Checked 또는 Qt.Unchecked 값을 전달하는 신호 추가
|
|
|
|
def __init__(self, parent=None, blue_theme=False):
|
|
super(ToggleSwitch, self).__init__(parent)
|
|
self.setFixedSize(70, 30)
|
|
self._checked = False
|
|
self._circle_color_checked = QColor('red')
|
|
self._circle_color_unchecked = QColor('gray')
|
|
self._background_color = QColor('white')
|
|
self._circle_pos = QPoint(0, 0)
|
|
self.animation = QPropertyAnimation(self, b"circle_pos")
|
|
self.animation.setDuration(250)
|
|
self.blue_theme = blue_theme
|
|
|
|
# 추가: 토글 상태에 따른 텍스트
|
|
self._on_text = "ON"
|
|
self._off_text = "OFF"
|
|
self._text_font = QFont("Arial", 10, QFont.Bold)
|
|
|
|
self._init_position()
|
|
# clicked 신호가 발생할 때 stateChanged 신호도 함께 발생시킴
|
|
self.clicked.connect(self._handle_clicked)
|
|
|
|
def _handle_clicked(self, checked):
|
|
"""
|
|
clicked 신호 처리 및 stateChanged 신호 발생
|
|
"""
|
|
self.stateChanged.emit(Qt.Checked if checked else Qt.Unchecked)
|
|
|
|
@Property(QPoint)
|
|
def circle_pos(self):
|
|
return self._circle_pos
|
|
|
|
@circle_pos.setter
|
|
def circle_pos(self, pos):
|
|
self._circle_pos = pos
|
|
self.update()
|
|
|
|
def _init_position(self):
|
|
if self._checked:
|
|
self._circle_pos.setX(30)
|
|
else:
|
|
self._circle_pos.setX(0)
|
|
|
|
def mousePressEvent(self, event):
|
|
if event.button() == Qt.LeftButton:
|
|
old_state = self._checked
|
|
self._checked = not self._checked
|
|
self.clicked.emit(self._checked)
|
|
if old_state != self._checked:
|
|
# 상태가 변경되면 stateChanged 신호 발생
|
|
self.stateChanged.emit(Qt.Checked if self._checked else Qt.Unchecked)
|
|
self._update_animation()
|
|
self.update()
|
|
super(ToggleSwitch, self).mousePressEvent(event)
|
|
|
|
def _update_animation(self):
|
|
if self._checked:
|
|
self.animation.setStartValue(QPoint(0, 0))
|
|
self.animation.setEndValue(QPoint(30, 0))
|
|
else:
|
|
self.animation.setStartValue(QPoint(30, 0))
|
|
self.animation.setEndValue(QPoint(0, 0))
|
|
self.animation.start()
|
|
|
|
def paintEvent(self, event):
|
|
painter = QPainter(self)
|
|
painter.setRenderHint(QPainter.Antialiasing)
|
|
painter.setPen(Qt.NoPen)
|
|
|
|
# 배경 색상 설정
|
|
if self.isChecked():
|
|
if self.blue_theme:
|
|
self._background_color = QColor(0, 120, 215) # 활성화 상태: 진한 파란색
|
|
else:
|
|
self._background_color = QColor(33, 150, 243) # 활성화 상태: 파란색
|
|
else:
|
|
self._background_color = QColor(180, 180, 180) # 비활성화 상태: 회색
|
|
|
|
# 3D 효과를 위한 그림자 그리기
|
|
shadow_color = QColor(0, 0, 0, 50) # 반투명 검정색
|
|
painter.setPen(Qt.NoPen)
|
|
painter.setBrush(shadow_color)
|
|
painter.drawRoundedRect(2, 2, self.width(), self.height(), 15, 15)
|
|
|
|
# 배경 그리기
|
|
painter.setPen(Qt.NoPen)
|
|
painter.setBrush(self._background_color)
|
|
painter.drawRoundedRect(0, 0, self.width(), self.height(), 15, 15)
|
|
|
|
# 토글 손잡이 그리기
|
|
# 손잡이 그림자
|
|
painter.setBrush(QColor(0, 0, 0, 30))
|
|
if self.isChecked():
|
|
painter.drawEllipse(self.width() - 27, 3, 26, 26)
|
|
else:
|
|
painter.drawEllipse(3, 3, 26, 26)
|
|
|
|
# 손잡이 본체
|
|
painter.setBrush(QColor(255, 255, 255))
|
|
if self.isChecked():
|
|
painter.drawEllipse(self.width() - 28, 2, 26, 26)
|
|
else:
|
|
painter.drawEllipse(2, 2, 26, 26)
|
|
|
|
# 텍스트 그리기: 토글 상태에 따라 왼쪽(ON) 또는 오른쪽(OFF) 정렬
|
|
painter.setFont(self._text_font)
|
|
painter.setPen(QColor("black"))
|
|
if self._checked:
|
|
text = self._on_text
|
|
alignment = Qt.AlignLeft | Qt.AlignVCenter
|
|
else:
|
|
text = self._off_text
|
|
alignment = Qt.AlignRight | Qt.AlignVCenter
|
|
painter.drawText(self.rect(), alignment, text)
|
|
|
|
def setChecked(self, checked):
|
|
"""
|
|
ToggleSwitch의 상태를 설정합니다.
|
|
|
|
Args:
|
|
checked (bool): True로 설정하면 스위치를 체크 상태로, False로 설정하면 언체크 상태로 변경합니다.
|
|
"""
|
|
if self._checked != checked:
|
|
self._checked = checked
|
|
# setChecked에서는 stateChanged 시그널만 발생시키고 clicked 시그널은 발생시키지 않음
|
|
self.stateChanged.emit(Qt.Checked if self._checked else Qt.Unchecked)
|
|
self._update_animation()
|
|
self.update()
|
|
|
|
def isChecked(self):
|
|
return self._checked
|
|
|
|
def sizeHint(self):
|
|
"""위젯 크기 힌트"""
|
|
return QSize(60, 30)
|
|
|
|
def setState(self, state):
|
|
"""ToggleSwitch의 상태를 설정합니다.
|
|
|
|
Args:
|
|
state (bool): True로 설정하면 스위치를 체크 상태로, False로 설정하면 언체크 상태로 변경합니다.
|
|
"""
|
|
if self._checked != state:
|
|
self._checked = state
|
|
self._update_animation()
|
|
# clicked 시그널을 발생시키지 않고 stateChanged 시그널만 발생
|
|
self.stateChanged.emit(Qt.Checked if self._checked else Qt.Unchecked)
|
|
self.update()
|
|
|
|
# 텍스트를 설정하는 메서드들
|
|
def setOnText(self, text):
|
|
self._on_text = text
|
|
self.update()
|
|
|
|
def setOffText(self, text):
|
|
self._off_text = text
|
|
self.update()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_toggle_style():
|
|
"""토글 다이얼로그의 스타일을 반환합니다."""
|
|
return """
|
|
/* 전체 다이얼로그 스타일 */
|
|
QDialog {
|
|
background-color: #f5f5f7;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
/* 탭 위젯 스타일 */
|
|
QTabWidget {
|
|
background-color: #ffffff;
|
|
border: none;
|
|
}
|
|
|
|
QTabWidget::pane {
|
|
background-color: #ffffff;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
QTabBar::tab {
|
|
background-color: #f0f0f0;
|
|
color: #505050;
|
|
padding: 10px 20px;
|
|
border: 1px solid #e0e0e0;
|
|
border-bottom: none;
|
|
border-top-left-radius: 4px;
|
|
border-top-right-radius: 4px;
|
|
margin-right: 2px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
QTabBar::tab:selected {
|
|
background-color: #ffffff;
|
|
color: #2196f3;
|
|
border-bottom: 2px solid #2196f3;
|
|
}
|
|
|
|
QTabBar::tab:hover:!selected {
|
|
background-color: #e6e6e6;
|
|
}
|
|
|
|
/* 토글 스위치 스타일 */
|
|
QLabel {
|
|
color: #404040;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* 설명 영역 스타일 */
|
|
QTextEdit, QTextBrowser {
|
|
background-color: #ffffff;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 5px;
|
|
padding: 5px;
|
|
color: #505050;
|
|
}
|
|
|
|
/* 버튼 스타일 */
|
|
QPushButton {
|
|
background-color: #2196f3;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
padding: 8px 16px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
QPushButton:hover {
|
|
background-color: #1e88e5;
|
|
}
|
|
|
|
QPushButton:pressed {
|
|
background-color: #1976d2;
|
|
}
|
|
|
|
QPushButton:disabled {
|
|
background-color: #90caf9;
|
|
}
|
|
|
|
/* 슬라이더 스타일 */
|
|
QSlider::groove:horizontal {
|
|
border: 1px solid #999999;
|
|
height: 8px;
|
|
background: #f0f0f0;
|
|
margin: 2px 0;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
QSlider::handle:horizontal {
|
|
background: #2196f3;
|
|
border: 1px solid #5c5c5c;
|
|
width: 18px;
|
|
margin: -5px 0;
|
|
border-radius: 9px;
|
|
}
|
|
|
|
/* 입력 필드 스타일 */
|
|
QLineEdit, QSpinBox {
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 5px;
|
|
background-color: #ffffff;
|
|
}
|
|
|
|
QLineEdit:focus, QSpinBox:focus {
|
|
border: 1px solid #2196f3;
|
|
}
|
|
|
|
/* 스크롤바 스타일 */
|
|
QScrollBar:vertical {
|
|
border: none;
|
|
background: #f0f0f0;
|
|
width: 10px;
|
|
margin: 0px;
|
|
}
|
|
|
|
QScrollBar::handle:vertical {
|
|
background: #c0c0c0;
|
|
min-height: 20px;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
|
|
border: none;
|
|
background: none;
|
|
}
|
|
|
|
QScrollBar:horizontal {
|
|
border: none;
|
|
background: #f0f0f0;
|
|
height: 10px;
|
|
margin: 0px;
|
|
}
|
|
|
|
QScrollBar::handle:horizontal {
|
|
background: #c0c0c0;
|
|
min-width: 20px;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
|
|
border: none;
|
|
background: none;
|
|
}
|
|
"""
|
|
|
|
def get_main_style():
|
|
"""메인 UI의 스타일을 반환합니다."""
|
|
return """
|
|
/* 메인 윈도우 스타일 */
|
|
QMainWindow {
|
|
background-color: #f5f5f7;
|
|
}
|
|
|
|
/* 중앙 위젯 스타일 */
|
|
QWidget {
|
|
background-color: #f5f5f7;
|
|
color: #404040;
|
|
}
|
|
|
|
/* 버튼 스타일 */
|
|
QPushButton {
|
|
background-color: #2196f3;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
padding: 10px 20px;
|
|
font-weight: bold;
|
|
font-size: 13px;
|
|
min-width: 100px;
|
|
}
|
|
|
|
QPushButton:hover {
|
|
background-color: #1e88e5;
|
|
}
|
|
|
|
QPushButton:pressed {
|
|
background-color: #1976d2;
|
|
}
|
|
|
|
QPushButton:disabled {
|
|
background-color: #90caf9;
|
|
}
|
|
|
|
/* 메인 버튼 스타일 - 크고 강조된 버튼 */
|
|
#start_chrome_button, #PercentyJob_button {
|
|
background-color: #4caf50;
|
|
font-size: 14px;
|
|
padding: 12px 24px;
|
|
min-width: 150px;
|
|
}
|
|
|
|
#start_chrome_button:hover, #PercentyJob_button:hover {
|
|
background-color: #43a047;
|
|
}
|
|
|
|
#start_chrome_button:pressed, #PercentyJob_button:pressed {
|
|
background-color: #388e3c;
|
|
}
|
|
|
|
/* 그룹 박스 스타일 */
|
|
QGroupBox {
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 5px;
|
|
margin-top: 1ex;
|
|
font-weight: bold;
|
|
color: #404040;
|
|
}
|
|
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
subcontrol-position: top center;
|
|
padding: 0 3px;
|
|
}
|
|
|
|
/* 레이블 스타일 */
|
|
QLabel {
|
|
color: #505050;
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* 입력 필드 스타일 */
|
|
QLineEdit, QSpinBox, QComboBox {
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
background-color: #ffffff;
|
|
}
|
|
|
|
QLineEdit:focus, QSpinBox:focus, QComboBox:focus {
|
|
border: 1px solid #2196f3;
|
|
}
|
|
|
|
/* 프로그래스 바 스타일 */
|
|
QProgressBar {
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
background-color: #f0f0f0;
|
|
text-align: center;
|
|
padding: 2px;
|
|
height: 25px;
|
|
}
|
|
|
|
QProgressBar::chunk {
|
|
background-color: #2196f3;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
/* 스크롤 영역 스타일 */
|
|
QScrollArea {
|
|
border: none;
|
|
}
|
|
|
|
/* 콤보 박스 스타일 */
|
|
QComboBox {
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
background-color: #ffffff;
|
|
min-width: 100px;
|
|
}
|
|
|
|
QComboBox::drop-down {
|
|
subcontrol-origin: padding;
|
|
subcontrol-position: top right;
|
|
width: 20px;
|
|
border-left-width: 1px;
|
|
border-left-color: #e0e0e0;
|
|
border-left-style: solid;
|
|
}
|
|
|
|
QComboBox QAbstractItemView {
|
|
border: 1px solid #e0e0e0;
|
|
background-color: white;
|
|
selection-background-color: #2196f3;
|
|
selection-color: white;
|
|
}
|
|
|
|
/* 체크박스 스타일 */
|
|
QCheckBox {
|
|
spacing: 5px;
|
|
color: #505050;
|
|
}
|
|
|
|
QCheckBox::indicator {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
QCheckBox::indicator:unchecked {
|
|
border: 2px solid #e0e0e0;
|
|
background-color: white;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
QCheckBox::indicator:checked {
|
|
border: 2px solid #2196f3;
|
|
background-color: #2196f3;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
/* 메뉴 바 스타일 */
|
|
QMenuBar {
|
|
background-color: #ffffff;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
|
|
QMenuBar::item {
|
|
spacing: 3px;
|
|
padding: 5px 15px;
|
|
background: transparent;
|
|
color: #505050;
|
|
}
|
|
|
|
QMenuBar::item:selected {
|
|
background: #e0e0e0;
|
|
color: #2196f3;
|
|
}
|
|
|
|
QMenu {
|
|
background-color: #ffffff;
|
|
border: 1px solid #e0e0e0;
|
|
}
|
|
|
|
QMenu::item {
|
|
padding: 5px 20px 5px 20px;
|
|
}
|
|
|
|
QMenu::item:selected {
|
|
background-color: #e0e0e0;
|
|
color: #2196f3;
|
|
}
|
|
|
|
/* 상태 바 스타일 */
|
|
QStatusBar {
|
|
background-color: #f0f0f0;
|
|
color: #505050;
|
|
}
|
|
"""
|