diff --git a/camera_thread.py b/camera_thread.py index f9dcd62..ec96320 100644 --- a/camera_thread.py +++ b/camera_thread.py @@ -206,6 +206,13 @@ def __init__( self.fps_alpha = 0.1 # Smoothing factor self.updateOnChange = True self.crop = FrameCropAndRotation() + self.speed = 1 + + def getSpeed(self): + return self.speed + + def setSpeed(self, speed): + self.speed = speed def setUpdateFrameInterval(self, cadence): self.update_frame_interval = 1000 / cadence @@ -303,6 +310,13 @@ def run(self): self.sleep_fps_target() continue + if self.camera_info.type == CameraInfo.CameraType.FILE: + if self.speed != 1: + self.video_capture.set( + cv2.CAP_PROP_POS_FRAMES, + self.video_capture.get(cv2.CAP_PROP_POS_FRAMES) + self.speed, + ) + if not ret: self.retry_count += 1 if self.camera_info.type == CameraInfo.CameraType.FILE: diff --git a/mainwindow.py b/mainwindow.py index a4497be..89092bc 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -407,10 +407,34 @@ def __init__(self, translator: QTranslator, parent: QObject): fetch_data("scoresight.json", "save_ocr_training_data", False) ) + self.ui.toolButton_speed.clicked.connect(self.toggleSpeed) + self.update_sources.connect(self.updateSources) self.get_sources.connect(self.getSources) self.get_sources.emit() + def toggleSpeed(self): + # check the current speed and toggle it + # possible speeds are x2, x4, x8, x16, x32 and back to x1 + # change the button text to the current speed + # change the speed of the image viewer + if self.image_viewer: + speed = self.image_viewer.timerThread.getSpeed() + if speed == 1: + speed = 2 + elif speed == 2: + speed = 4 + elif speed == 4: + speed = 8 + elif speed == 8: + speed = 16 + elif speed == 16: + speed = 32 + else: + speed = 1 + self.image_viewer.timerThread.setSpeed(speed) + self.ui.toolButton_speed.setText(f"x{speed}") + def saveOCRTrainingData(self): self.globalSettingsChanged( "save_ocr_training_data", self.ui.pushButton_saveOCRTrainingData.isChecked() diff --git a/mainwindow.ui b/mainwindow.ui index 7668999..647e46c 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -2058,6 +2058,20 @@ + + + + Speed + + + + + + + x1 + + + diff --git a/ocr_training_data.py b/ocr_training_data.py index 40308dd..760748f 100644 --- a/ocr_training_data.py +++ b/ocr_training_data.py @@ -135,6 +135,18 @@ def __init__(self): ) self.ui.pushButton_openFolder.clicked.connect(self.open_folder) self.ui.pushButton_saveZipFile.clicked.connect(self.save_zip_file) + self.ui.pushButton_openTrainingDojo.clicked.connect(self.open_training_dojo) + + def open_training_dojo(self): + logger.debug("Opening OCR training dojo") + folder = self.ui.lineEdit_saveFolder.text() + if folder: + from training_dojo import TrainingDojo + + training_dojo = TrainingDojo(folder) + training_dojo.exec_() + else: + logger.error("No OCR training data folder set") def save_zip_file(self): logger.debug("Saving OCR training data zip file") diff --git a/ocr_training_data_dialog.ui b/ocr_training_data_dialog.ui index 8781bc2..6518c30 100644 --- a/ocr_training_data_dialog.ui +++ b/ocr_training_data_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 267 - 132 + 377 + 164 @@ -19,25 +19,21 @@ 650 - 16777215 + 300 Dialog - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - + + + 0 + 0 + + @@ -132,9 +128,39 @@ + + + + Open OCR Training Dojo + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/scoresight.spec b/scoresight.spec index baa404b..c0f709e 100644 --- a/scoresight.spec +++ b/scoresight.spec @@ -71,6 +71,7 @@ sources = [ 'storage.py', 'tesseract.py', 'text_detection_target.py', + 'training_dojo.py', 'video_settings.py', 'ui_about.py', 'ui_connect_obs.py', @@ -78,6 +79,7 @@ sources = [ 'ui_mainwindow.py', 'ui_ocr_training_data_dialog.py', 'ui_screen_capture.py', + 'ui_training_dojo.py', 'ui_update_available.py', 'ui_url_source.py', 'ui_video_settings.py', diff --git a/training_dojo.py b/training_dojo.py new file mode 100644 index 0000000..5ae2b7f --- /dev/null +++ b/training_dojo.py @@ -0,0 +1,339 @@ +import os +import json +from PySide6.QtWidgets import QDialog, QListWidgetItem, QLabel, QVBoxLayout, QLineEdit +from PySide6.QtGui import QPixmap, QKeyEvent, QColor, QBrush, QPainter +from PySide6.QtCore import Qt, QSize +from ui_training_dojo import Ui_TrainingDojo + + +class AspectRatioPixmapLabel(QLabel): + def __init__(self, parent=None): + super().__init__(parent) + self.setScaledContents(False) + + def setPixmap(self, pixmap): + self.pix = pixmap + self.update() + + def paintEvent(self, event): + if hasattr(self, "pix"): + painter = QPainter(self) + painter.setRenderHint(QPainter.SmoothPixmapTransform) + scaled_pixmap = self.pix.scaled( + self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation + ) + x = (self.width() - scaled_pixmap.width()) // 2 + y = (self.height() - scaled_pixmap.height()) // 2 + painter.drawPixmap(x, y, scaled_pixmap) + + +class CustomLineEdit(QLineEdit): + def __init__(self, parent=None): + super().__init__(parent) + self.parent_dialog = parent + + def keyPressEvent(self, event: QKeyEvent): + if self.parent_dialog.handle_key_event(event): + return + super().keyPressEvent(event) + + +class TrainingDojo(QDialog): + def __init__(self, folder_path): + super().__init__() + self.ui = Ui_TrainingDojo() + self.ui.setupUi(self) + + self.folder_path = folder_path + self.image_files = [] + self.current_index = -1 + self.approved_annotations = self.load_approved_annotations() + self.show_only_undone = False + + # Replace the default QLineEdit with our CustomLineEdit + self.custom_line_edit = CustomLineEdit(self) + self.custom_line_edit.setObjectName("lineEdit_text") + self.ui.lineEdit_text.setParent(None) + self.ui.lineEdit_text = self.custom_line_edit + self.ui.verticalLayout.insertWidget(1, self.custom_line_edit) + + # Create an AspectRatioPixmapLabel for displaying the image + self.image_label = AspectRatioPixmapLabel() + self.image_label.setAlignment(Qt.AlignCenter) + + # Set up layout for the image widget + layout = QVBoxLayout(self.ui.widget_image) + layout.addWidget(self.image_label) + self.ui.widget_image.setLayout(layout) + + self.load_files() + self.setup_connections() + + def load_approved_annotations(self): + tracking_file = os.path.join(self.folder_path, "approved_annotations.json") + if os.path.exists(tracking_file): + with open(tracking_file, "r") as f: + return json.load(f) + return {} + + def save_approved_annotations(self): + tracking_file = os.path.join(self.folder_path, "approved_annotations.json") + with open(tracking_file, "w") as f: + json.dump(self.approved_annotations, f) + + def load_files(self): + self.ui.listWidget_files.clear() # Clear existing items + approved_items = [] + unapproved_items = [] + for file in os.listdir(self.folder_path): + if file.endswith(".png"): + base_name = os.path.splitext(file)[0] + if os.path.exists(os.path.join(self.folder_path, f"{base_name}.txt")): + self.image_files.append(base_name) + item = QListWidgetItem(base_name) + if base_name in self.approved_annotations: + item.setForeground(QBrush(QColor("green"))) + item.setText(f"✓ {base_name}") + approved_items.append(item) + else: + unapproved_items.append(item) + + # Add approved items first, then unapproved items + for item in approved_items: + self.ui.listWidget_files.addItem(item) + for item in unapproved_items: + self.ui.listWidget_files.addItem(item) + + if self.image_files: + self.current_index = 0 + self.load_current_item() + + def setup_connections(self): + self.ui.listWidget_files.itemClicked.connect(self.load_selected_item) + self.ui.pushButton_next.clicked.connect(self.next_image) + self.ui.pushButton_prev.clicked.connect(self.prev_image) + self.ui.lineEdit_text.editingFinished.connect(self.save_text) + self.ui.pushButton_onlyUndone.clicked.connect(self.toggle_undone_filter) + + def load_current_item(self): + if 0 <= self.current_index < len(self.image_files): + base_name = self.image_files[self.current_index] + + # Load image + image_path = os.path.join(self.folder_path, f"{base_name}.png") + pixmap = QPixmap(image_path) + self.image_label.setPixmap(pixmap) + + # Load text + text_path = os.path.join(self.folder_path, f"{base_name}.txt") + with open(text_path, "r") as f: + text = f.read().strip() + self.ui.lineEdit_text.setText(text) + + # Highlight if approved + self.highlight_approved() + + # Highlight and scroll to current item in list + list_index = self.ui.listWidget_files.findItems( + base_name, Qt.MatchContains + )[0] + self.ui.listWidget_files.setCurrentItem(list_index) + self.ui.listWidget_files.scrollToItem(list_index) + + # Focus on lineEdit_text + self.ui.lineEdit_text.setFocus() + else: + # Clear the UI if no valid item is selected + self.image_label.clear() + self.ui.lineEdit_text.clear() + + def load_selected_item(self, item): + self.current_index = self.ui.listWidget_files.row(item) + self.load_current_item() + + def load_files(self): + self.ui.listWidget_files.clear() # Clear existing items + self.image_files = [] + + for file in os.listdir(self.folder_path): + if file.endswith(".png"): + base_name = os.path.splitext(file)[0] + if os.path.exists(os.path.join(self.folder_path, f"{base_name}.txt")): + self.image_files.append(base_name) + + # Sort files by filename + self.image_files.sort() + + self.update_list_widget() + + if self.image_files: + self.current_index = 0 + self.load_current_item() + + def update_list_widget(self): + self.ui.listWidget_files.clear() + for base_name in self.image_files: + if not self.show_only_undone or base_name not in self.approved_annotations: + item = QListWidgetItem(base_name) + if base_name in self.approved_annotations: + item.setForeground(QBrush(QColor("green"))) + item.setText(f"✓ {base_name}") + self.ui.listWidget_files.addItem(item) + + def toggle_undone_filter(self): + self.show_only_undone = self.ui.pushButton_onlyUndone.isChecked() + self.update_list_widget() + self.update_current_index() + self.load_current_item() + + def highlight_approved(self): + base_name = self.image_files[self.current_index] + if base_name in self.approved_annotations: + self.ui.lineEdit_text.setStyleSheet("border: 2px solid green;") + self.image_label.setStyleSheet("border: 3px solid green;") + else: + self.ui.lineEdit_text.setStyleSheet("") + self.image_label.setStyleSheet("") + + def update_current_index(self): + if self.show_only_undone: + # Find the first undone item + for i, base_name in enumerate(self.image_files): + if base_name not in self.approved_annotations: + self.current_index = i + return + # If all items are done, set current_index to -1 + self.current_index = -1 + else: + # If there are items, set current_index to 0, otherwise -1 + self.current_index = 0 if self.image_files else -1 + + def next_image(self): + self.save_text() + start_index = self.current_index + while True: + self.current_index = (self.current_index + 1) % len(self.image_files) + if ( + not self.show_only_undone + or self.image_files[self.current_index] not in self.approved_annotations + ): + break + if self.current_index == start_index: + # We've gone through all images and found nothing + return + self.load_current_item() + + def prev_image(self): + self.save_text() + start_index = self.current_index + while True: + self.current_index = (self.current_index - 1) % len(self.image_files) + if ( + not self.show_only_undone + or self.image_files[self.current_index] not in self.approved_annotations + ): + break + if self.current_index == start_index: + # We've gone through all images and found nothing + return + self.load_current_item() + + def save_text(self): + if 0 <= self.current_index < len(self.image_files): + base_name = self.image_files[self.current_index] + text_path = os.path.join(self.folder_path, f"{base_name}.txt") + with open(text_path, "w") as f: + f.write(self.ui.lineEdit_text.text()) + + def approve_annotation(self): + if 0 <= self.current_index < len(self.image_files): + base_name = self.image_files[self.current_index] + self.approved_annotations[base_name] = self.ui.lineEdit_text.text() + self.save_approved_annotations() + self.highlight_approved() + + if self.show_only_undone: + self.update_list_widget() + self.next_image() + else: + # Update list widget item + items = self.ui.listWidget_files.findItems(base_name, Qt.MatchContains) + if items: + item = items[0] + item.setForeground(QBrush(QColor("green"))) + item.setText(f"✓ {base_name}") + + def unapprove_annotation(self): + if 0 <= self.current_index < len(self.image_files): + base_name = self.image_files[self.current_index] + if base_name in self.approved_annotations: + del self.approved_annotations[base_name] + self.save_approved_annotations() + self.highlight_approved() + + # Update list widget item + item = self.ui.listWidget_files.item(self.current_index) + item.setForeground(QBrush(QColor("black"))) + item.setText(base_name) + + def handle_key_event(self, event: QKeyEvent): + if event.key() == Qt.Key_N: + self.next_image() + return True + elif event.key() == Qt.Key_B: + self.prev_image() + return True + elif event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: + if event.modifiers() & Qt.ShiftModifier: + self.prev_image() + else: + self.approve_annotation() + self.next_image() + return True + elif event.key() == Qt.Key_Up: + if event.modifiers() & Qt.ControlModifier: + self.jump_to_prev_unapproved() + else: + self.prev_image() + return True + elif event.key() == Qt.Key_Down: + if event.modifiers() & Qt.ControlModifier: + self.jump_to_next_unapproved() + else: + self.next_image() + return True + elif event.key() == Qt.Key_U: + self.unapprove_annotation() + return True + return False + + def jump_to_next_unapproved(self): + start_index = (self.current_index + 1) % len(self.image_files) + for i in range(len(self.image_files)): + index = (start_index + i) % len(self.image_files) + if self.image_files[index] not in self.approved_annotations: + self.current_index = index + self.load_current_item() + return + # If we get here, all items are approved + print("All items are approved.") + + def jump_to_prev_unapproved(self): + start_index = (self.current_index - 1) % len(self.image_files) + for i in range(len(self.image_files)): + index = (start_index - i) % len(self.image_files) + if self.image_files[index] not in self.approved_annotations: + self.current_index = index + self.load_current_item() + return + # If we get here, all items are approved + print("All items are approved.") + + def keyPressEvent(self, event: QKeyEvent): + if not self.handle_key_event(event): + super().keyPressEvent(event) + + def resizeEvent(self, event): + super().resizeEvent(event) + if hasattr(self, "image_label"): + self.image_label.setFixedSize(self.ui.widget_image.size()) diff --git a/training_dojo.ui b/training_dojo.ui new file mode 100644 index 0000000..7ac5f0b --- /dev/null +++ b/training_dojo.ui @@ -0,0 +1,94 @@ + + + TrainingDojo + + + + 0 + 0 + 652 + 410 + + + + Dialog + + + + + + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + + + + + + 29 + + + + Text... + + + + + + + + + + < + + + + + + + > + + + + + + + + + + + + + + 0 + 0 + + + + Only Undone + + + true + + + + + + + + diff --git a/ui_mainwindow.py b/ui_mainwindow.py index 20c61ac..ae9f8c8 100644 --- a/ui_mainwindow.py +++ b/ui_mainwindow.py @@ -1058,6 +1058,16 @@ def setupUi(self, MainWindow): self.horizontalLayout_26.addWidget(self.spinBox_bottomCrop) + self.label_22 = QLabel(self.widget_cropPanel) + self.label_22.setObjectName(u"label_22") + + self.horizontalLayout_26.addWidget(self.label_22) + + self.toolButton_speed = QToolButton(self.widget_cropPanel) + self.toolButton_speed.setObjectName(u"toolButton_speed") + + self.horizontalLayout_26.addWidget(self.toolButton_speed) + self.horizontalSpacer_4 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout_26.addItem(self.horizontalSpacer_4) @@ -1271,6 +1281,8 @@ def retranslateUi(self, MainWindow): self.spinBox_rightCrop.setSuffix(QCoreApplication.translate("MainWindow", u"px", None)) self.label_19.setText(QCoreApplication.translate("MainWindow", u"Bottom", None)) self.spinBox_bottomCrop.setSuffix(QCoreApplication.translate("MainWindow", u"px", None)) + self.label_22.setText(QCoreApplication.translate("MainWindow", u"Speed", None)) + self.toolButton_speed.setText(QCoreApplication.translate("MainWindow", u"x1", None)) self.label_12.setText(QCoreApplication.translate("MainWindow", u"### Open a Camera or Load a File", None)) # retranslateUi diff --git a/ui_ocr_training_data_dialog.py b/ui_ocr_training_data_dialog.py index 5f51d1a..2fb6dc9 100644 --- a/ui_ocr_training_data_dialog.py +++ b/ui_ocr_training_data_dialog.py @@ -17,27 +17,25 @@ QPalette, QPixmap, QRadialGradient, QTransform) from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox, QFormLayout, QGridLayout, QHBoxLayout, QLabel, - QLineEdit, QPushButton, QSizePolicy, QSpinBox, - QToolButton, QWidget) + QLineEdit, QPushButton, QSizePolicy, QSpacerItem, + QSpinBox, QToolButton, QWidget) class Ui_OCRTrainingDataDialog(object): def setupUi(self, OCRTrainingDataDialog): if not OCRTrainingDataDialog.objectName(): OCRTrainingDataDialog.setObjectName(u"OCRTrainingDataDialog") - OCRTrainingDataDialog.resize(267, 132) + OCRTrainingDataDialog.resize(377, 164) OCRTrainingDataDialog.setMinimumSize(QSize(267, 0)) - OCRTrainingDataDialog.setMaximumSize(QSize(650, 16777215)) + OCRTrainingDataDialog.setMaximumSize(QSize(650, 300)) self.gridLayout = QGridLayout(OCRTrainingDataDialog) self.gridLayout.setObjectName(u"gridLayout") - self.buttonBox = QDialogButtonBox(OCRTrainingDataDialog) - self.buttonBox.setObjectName(u"buttonBox") - self.buttonBox.setOrientation(Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) - - self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 1) - self.widget = QWidget(OCRTrainingDataDialog) self.widget.setObjectName(u"widget") + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) + self.widget.setSizePolicy(sizePolicy) self.formLayout = QFormLayout(self.widget) self.formLayout.setObjectName(u"formLayout") self.label = QLabel(self.widget) @@ -47,9 +45,6 @@ def setupUi(self, OCRTrainingDataDialog): self.widget_2 = QWidget(self.widget) self.widget_2.setObjectName(u"widget_2") - sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth()) self.widget_2.setSizePolicy(sizePolicy) self.horizontalLayout = QHBoxLayout(self.widget_2) @@ -98,9 +93,25 @@ def setupUi(self, OCRTrainingDataDialog): self.formLayout.setWidget(2, QFormLayout.FieldRole, self.spinBox_maxSize) + self.pushButton_openTrainingDojo = QPushButton(self.widget) + self.pushButton_openTrainingDojo.setObjectName(u"pushButton_openTrainingDojo") + + self.formLayout.setWidget(3, QFormLayout.FieldRole, self.pushButton_openTrainingDojo) + self.gridLayout.addWidget(self.widget, 1, 0, 1, 1) + self.buttonBox = QDialogButtonBox(OCRTrainingDataDialog) + self.buttonBox.setObjectName(u"buttonBox") + self.buttonBox.setOrientation(Qt.Horizontal) + self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) + + self.gridLayout.addWidget(self.buttonBox, 3, 0, 1, 1) + + self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + + self.gridLayout.addItem(self.verticalSpacer, 2, 0, 1, 1) + self.retranslateUi(OCRTrainingDataDialog) self.buttonBox.accepted.connect(OCRTrainingDataDialog.accept) @@ -117,5 +128,6 @@ def retranslateUi(self, OCRTrainingDataDialog): self.pushButton_saveZipFile.setText(QCoreApplication.translate("OCRTrainingDataDialog", u"Save Zip File", None)) self.label_2.setText(QCoreApplication.translate("OCRTrainingDataDialog", u"Max Size", None)) self.spinBox_maxSize.setSuffix(QCoreApplication.translate("OCRTrainingDataDialog", u"Mb", None)) + self.pushButton_openTrainingDojo.setText(QCoreApplication.translate("OCRTrainingDataDialog", u"Open OCR Training Dojo", None)) # retranslateUi diff --git a/ui_training_dojo.py b/ui_training_dojo.py new file mode 100644 index 0000000..4d614d1 --- /dev/null +++ b/ui_training_dojo.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'training_dojo.ui' +## +## Created by: Qt User Interface Compiler version 6.7.2 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QDialog, QGridLayout, QHBoxLayout, + QLineEdit, QListWidget, QListWidgetItem, QPushButton, + QSizePolicy, QVBoxLayout, QWidget) + +class Ui_TrainingDojo(object): + def setupUi(self, TrainingDojo): + if not TrainingDojo.objectName(): + TrainingDojo.setObjectName(u"TrainingDojo") + TrainingDojo.resize(652, 410) + self.gridLayout = QGridLayout(TrainingDojo) + self.gridLayout.setObjectName(u"gridLayout") + self.listWidget_files = QListWidget(TrainingDojo) + self.listWidget_files.setObjectName(u"listWidget_files") + + self.gridLayout.addWidget(self.listWidget_files, 1, 0, 1, 1) + + self.widget = QWidget(TrainingDojo) + self.widget.setObjectName(u"widget") + sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) + self.widget.setSizePolicy(sizePolicy) + self.verticalLayout = QVBoxLayout(self.widget) + self.verticalLayout.setObjectName(u"verticalLayout") + self.widget_image = QWidget(self.widget) + self.widget_image.setObjectName(u"widget_image") + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.widget_image.sizePolicy().hasHeightForWidth()) + self.widget_image.setSizePolicy(sizePolicy1) + + self.verticalLayout.addWidget(self.widget_image) + + self.lineEdit_text = QLineEdit(self.widget) + self.lineEdit_text.setObjectName(u"lineEdit_text") + font = QFont() + font.setPointSize(29) + self.lineEdit_text.setFont(font) + + self.verticalLayout.addWidget(self.lineEdit_text) + + self.widget_3 = QWidget(self.widget) + self.widget_3.setObjectName(u"widget_3") + self.horizontalLayout = QHBoxLayout(self.widget_3) + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.pushButton_prev = QPushButton(self.widget_3) + self.pushButton_prev.setObjectName(u"pushButton_prev") + + self.horizontalLayout.addWidget(self.pushButton_prev) + + self.pushButton_next = QPushButton(self.widget_3) + self.pushButton_next.setObjectName(u"pushButton_next") + + self.horizontalLayout.addWidget(self.pushButton_next) + + + self.verticalLayout.addWidget(self.widget_3) + + + self.gridLayout.addWidget(self.widget, 1, 1, 1, 1) + + self.pushButton_onlyUndone = QPushButton(TrainingDojo) + self.pushButton_onlyUndone.setObjectName(u"pushButton_onlyUndone") + sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) + sizePolicy2.setHeightForWidth(self.pushButton_onlyUndone.sizePolicy().hasHeightForWidth()) + self.pushButton_onlyUndone.setSizePolicy(sizePolicy2) + self.pushButton_onlyUndone.setCheckable(True) + + self.gridLayout.addWidget(self.pushButton_onlyUndone, 2, 0, 1, 1) + + + self.retranslateUi(TrainingDojo) + + QMetaObject.connectSlotsByName(TrainingDojo) + # setupUi + + def retranslateUi(self, TrainingDojo): + TrainingDojo.setWindowTitle(QCoreApplication.translate("TrainingDojo", u"Dialog", None)) + self.lineEdit_text.setPlaceholderText(QCoreApplication.translate("TrainingDojo", u"Text...", None)) + self.pushButton_prev.setText(QCoreApplication.translate("TrainingDojo", u"<", None)) + self.pushButton_next.setText(QCoreApplication.translate("TrainingDojo", u">", None)) + self.pushButton_onlyUndone.setText(QCoreApplication.translate("TrainingDojo", u"Only Undone", None)) + # retranslateUi +