refactor: settings & language

This commit is contained in:
2023-09-08 01:01:21 +08:00
parent 7bc8780cb0
commit 06ac59091f
23 changed files with 990 additions and 533 deletions

View File

@ -3,12 +3,14 @@ import sys
import traceback import traceback
from arcaea_offline.database import Database from arcaea_offline.database import Database
from PySide6.QtCore import QCoreApplication, QLibraryInfo, QLocale, QTranslator from PySide6.QtCore import QCoreApplication, QLocale
from PySide6.QtGui import QIcon from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QApplication, QDialog, QMessageBox from PySide6.QtWidgets import QApplication, QDialog, QMessageBox
import ui.resources.images.images_rc import ui.resources.images.images_rc
import ui.resources.translations.translations_rc import ui.resources.translations.translations_rc
from ui.extends.shared.language import changeAppLanguage
from ui.extends.shared.settings import Settings
from ui.implements.mainwindow import MainWindow from ui.implements.mainwindow import MainWindow
from ui.startup.databaseChecker import DatabaseChecker from ui.startup.databaseChecker import DatabaseChecker
@ -24,22 +26,11 @@ logging.basicConfig(
if __name__ == "__main__": if __name__ == "__main__":
QCoreApplication.setApplicationName("Arcaea Offline") QCoreApplication.setApplicationName("Arcaea Offline")
locale = QLocale.system()
translator = QTranslator()
translator_load_success = translator.load(QLocale.system(), "", "", ":/lang/")
if not translator_load_success:
translator.load(":/lang/en_US.qm")
baseTranslator = QTranslator()
baseTranslator.load(
QLocale.system(),
"qt",
"_",
QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath),
)
app = QApplication(sys.argv) app = QApplication(sys.argv)
locale = (
app.installTranslator(translator) QLocale(Settings().language()) if Settings().language() else QLocale.system()
app.installTranslator(baseTranslator) )
changeAppLanguage(locale)
databaseChecker = DatabaseChecker() databaseChecker = DatabaseChecker()
result = databaseChecker.exec() result = databaseChecker.exec()

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsBaseWidget</class>
<widget class="QWidget" name="SettingsBaseWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>641</width>
<height>521</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">SettingsBaseWidget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="titleLabel">
<property name="font">
<font>
<pointsize>16</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string notr="true">Title</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>15</number>
</property>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'settingsBaseWidget.ui'
##
## Created by: Qt User Interface Compiler version 6.5.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, QGridLayout, QLabel, QSizePolicy,
QSpacerItem, QVBoxLayout, QWidget)
class Ui_SettingsBaseWidget(object):
def setupUi(self, SettingsBaseWidget):
if not SettingsBaseWidget.objectName():
SettingsBaseWidget.setObjectName(u"SettingsBaseWidget")
SettingsBaseWidget.resize(641, 521)
SettingsBaseWidget.setWindowTitle(u"SettingsBaseWidget")
self.verticalLayout = QVBoxLayout(SettingsBaseWidget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.titleLabel = QLabel(SettingsBaseWidget)
self.titleLabel.setObjectName(u"titleLabel")
font = QFont()
font.setPointSize(16)
font.setBold(True)
self.titleLabel.setFont(font)
self.titleLabel.setText(u"Title")
self.verticalLayout.addWidget(self.titleLabel)
self.verticalSpacer = QSpacerItem(20, 25, QSizePolicy.Minimum, QSizePolicy.Maximum)
self.verticalLayout.addItem(self.verticalSpacer)
self.gridLayout = QGridLayout()
self.gridLayout.setObjectName(u"gridLayout")
self.gridLayout.setContentsMargins(15, -1, -1, -1)
self.verticalLayout.addLayout(self.gridLayout)
self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.verticalLayout.addItem(self.verticalSpacer_2)
self.retranslateUi(SettingsBaseWidget)
QMetaObject.connectSlotsByName(SettingsBaseWidget)
# setupUi
def retranslateUi(self, SettingsBaseWidget):
pass
# retranslateUi

View File

@ -1,171 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDefault</class>
<widget class="QWidget" name="SettingsDefault">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>682</width>
<height>493</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">SettingsDefault</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
<item row="1" column="1">
<widget class="DevicesComboBox" name="devicesComboBox">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>deviceUuid</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>tesseractFile</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="FileSelector" name="tesseractFileSelector" native="true">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="FileSelector" name="devicesJsonFileSelector" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>devicesJsonFile</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="deviceUuidResetButton">
<property name="text">
<string>resetButton</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>knnModelFile</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="devicesJsonFileResetButton">
<property name="text">
<string>resetButton</string>
</property>
</widget>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>500000</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>siftDatabaseFile</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="FileSelector" name="knnModelFileSelector" native="true"/>
</item>
<item row="4" column="1">
<widget class="FileSelector" name="siftDatabaseFileSelector" native="true"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileSelector</class>
<extends>QWidget</extends>
<header>ui.implements.components.fileSelector</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DevicesComboBox</class>
<extends>QComboBox</extends>
<header>ui.implements.components.devicesComboBox</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -1,131 +0,0 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'settingsDefault.ui'
##
## Created by: Qt User Interface Compiler version 6.5.1
##
## 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, QGridLayout, QLabel, QPushButton,
QSizePolicy, QSpacerItem, QWidget)
from ui.implements.components.devicesComboBox import DevicesComboBox
from ui.implements.components.fileSelector import FileSelector
class Ui_SettingsDefault(object):
def setupUi(self, SettingsDefault):
if not SettingsDefault.objectName():
SettingsDefault.setObjectName(u"SettingsDefault")
SettingsDefault.resize(682, 493)
SettingsDefault.setWindowTitle(u"SettingsDefault")
self.gridLayout = QGridLayout(SettingsDefault)
self.gridLayout.setObjectName(u"gridLayout")
self.devicesComboBox = DevicesComboBox(SettingsDefault)
self.devicesComboBox.setObjectName(u"devicesComboBox")
self.devicesComboBox.setMinimumSize(QSize(200, 0))
self.gridLayout.addWidget(self.devicesComboBox, 1, 1, 1, 1)
self.label_3 = QLabel(SettingsDefault)
self.label_3.setObjectName(u"label_3")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth())
self.label_3.setSizePolicy(sizePolicy)
self.label_3.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1)
self.label_4 = QLabel(SettingsDefault)
self.label_4.setObjectName(u"label_4")
sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth())
self.label_4.setSizePolicy(sizePolicy)
self.label_4.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.gridLayout.addWidget(self.label_4, 2, 0, 1, 1)
self.tesseractFileSelector = FileSelector(SettingsDefault)
self.tesseractFileSelector.setObjectName(u"tesseractFileSelector")
self.tesseractFileSelector.setEnabled(False)
self.tesseractFileSelector.setMinimumSize(QSize(200, 0))
self.gridLayout.addWidget(self.tesseractFileSelector, 2, 1, 1, 1)
self.devicesJsonFileSelector = FileSelector(SettingsDefault)
self.devicesJsonFileSelector.setObjectName(u"devicesJsonFileSelector")
self.devicesJsonFileSelector.setMinimumSize(QSize(200, 0))
self.gridLayout.addWidget(self.devicesJsonFileSelector, 0, 1, 1, 1)
self.label_2 = QLabel(SettingsDefault)
self.label_2.setObjectName(u"label_2")
sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
self.label_2.setSizePolicy(sizePolicy)
self.label_2.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.gridLayout.addWidget(self.label_2, 0, 0, 1, 1)
self.deviceUuidResetButton = QPushButton(SettingsDefault)
self.deviceUuidResetButton.setObjectName(u"deviceUuidResetButton")
self.gridLayout.addWidget(self.deviceUuidResetButton, 1, 2, 1, 1)
self.label = QLabel(SettingsDefault)
self.label.setObjectName(u"label")
self.label.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.gridLayout.addWidget(self.label, 3, 0, 1, 1)
self.devicesJsonFileResetButton = QPushButton(SettingsDefault)
self.devicesJsonFileResetButton.setObjectName(u"devicesJsonFileResetButton")
self.gridLayout.addWidget(self.devicesJsonFileResetButton, 0, 2, 1, 1)
self.verticalSpacer = QSpacerItem(20, 500000, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.gridLayout.addItem(self.verticalSpacer, 5, 1, 1, 1)
self.label_5 = QLabel(SettingsDefault)
self.label_5.setObjectName(u"label_5")
self.label_5.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1)
self.knnModelFileSelector = FileSelector(SettingsDefault)
self.knnModelFileSelector.setObjectName(u"knnModelFileSelector")
self.gridLayout.addWidget(self.knnModelFileSelector, 3, 1, 1, 1)
self.siftDatabaseFileSelector = FileSelector(SettingsDefault)
self.siftDatabaseFileSelector.setObjectName(u"siftDatabaseFileSelector")
self.gridLayout.addWidget(self.siftDatabaseFileSelector, 4, 1, 1, 1)
self.gridLayout.setColumnStretch(1, 1)
self.retranslateUi(SettingsDefault)
QMetaObject.connectSlotsByName(SettingsDefault)
# setupUi
def retranslateUi(self, SettingsDefault):
self.label_3.setText(QCoreApplication.translate("SettingsDefault", u"deviceUuid", None))
self.label_4.setText(QCoreApplication.translate("SettingsDefault", u"tesseractFile", None))
self.label_2.setText(QCoreApplication.translate("SettingsDefault", u"devicesJsonFile", None))
self.deviceUuidResetButton.setText(QCoreApplication.translate("SettingsDefault", u"resetButton", None))
self.label.setText(QCoreApplication.translate("SettingsDefault", u"knnModelFile", None))
self.devicesJsonFileResetButton.setText(QCoreApplication.translate("SettingsDefault", u"resetButton", None))
self.label_5.setText(QCoreApplication.translate("SettingsDefault", u"siftDatabaseFile", None))
pass
# retranslateUi

View File

@ -40,6 +40,9 @@
<property name="editTriggers"> <property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set> <set>QAbstractItemView::NoEditTriggers</set>
</property> </property>
<property name="defaultDropAction">
<enum>Qt::IgnoreAction</enum>
</property>
<property name="alternatingRowColors"> <property name="alternatingRowColors">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -56,19 +59,10 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<widget class="SettingsDefault" name="page_default"/>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>SettingsDefault</class>
<extends>QWidget</extends>
<header>ui.implements.settings.settingsDefault</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@ -3,7 +3,7 @@
################################################################################ ################################################################################
## Form generated from reading UI file 'tabSettings.ui' ## Form generated from reading UI file 'tabSettings.ui'
## ##
## Created by: Qt User Interface Compiler version 6.5.0 ## Created by: Qt User Interface Compiler version 6.5.2
## ##
## WARNING! All changes made in this file will be lost when recompiling UI file! ## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################ ################################################################################
@ -19,8 +19,6 @@ from PySide6.QtWidgets import (QAbstractItemView, QAbstractScrollArea, QApplicat
QListWidget, QListWidgetItem, QSizePolicy, QStackedWidget, QListWidget, QListWidgetItem, QSizePolicy, QStackedWidget,
QWidget) QWidget)
from ui.implements.settings.settingsDefault import SettingsDefault
class Ui_TabSettings(object): class Ui_TabSettings(object):
def setupUi(self, TabSettings): def setupUi(self, TabSettings):
if not TabSettings.objectName(): if not TabSettings.objectName():
@ -40,6 +38,7 @@ class Ui_TabSettings(object):
self.listWidget.setBaseSize(QSize(100, 0)) self.listWidget.setBaseSize(QSize(100, 0))
self.listWidget.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) self.listWidget.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.listWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) self.listWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.listWidget.setDefaultDropAction(Qt.IgnoreAction)
self.listWidget.setAlternatingRowColors(True) self.listWidget.setAlternatingRowColors(True)
self.listWidget.setSelectionBehavior(QAbstractItemView.SelectRows) self.listWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
@ -52,9 +51,6 @@ class Ui_TabSettings(object):
sizePolicy1.setVerticalStretch(0) sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth()) sizePolicy1.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth())
self.stackedWidget.setSizePolicy(sizePolicy1) self.stackedWidget.setSizePolicy(sizePolicy1)
self.page_default = SettingsDefault()
self.page_default.setObjectName(u"page_default")
self.stackedWidget.addWidget(self.page_default)
self.horizontalLayout.addWidget(self.stackedWidget) self.horizontalLayout.addWidget(self.stackedWidget)

View File

@ -0,0 +1,66 @@
from PySide6.QtCore import QEvent, QLibraryInfo, QLocale, QObject, QTranslator
from PySide6.QtWidgets import QApplication
INSTALLED_TRANSLATORS = []
def changeAppLanguage(
locale: QLocale,
fallbackLocale: QLocale = QLocale("en_US"),
):
app = QApplication.instance()
for translator in INSTALLED_TRANSLATORS:
app.removeTranslator(translator)
translator = QTranslator()
translatorLoadSuccess = translator.load(locale, "", "", ":/lang/")
if not translatorLoadSuccess:
translator.load(fallbackLocale, "", "", ":/lang/")
qtTranslator = QTranslator()
qtTranslatorLoadSuccess = qtTranslator.load(
locale,
"qt",
"_",
QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath),
)
if not qtTranslatorLoadSuccess:
qtTranslator.load(
fallbackLocale,
"qt",
"_",
QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath),
)
app.installTranslator(translator)
INSTALLED_TRANSLATORS.append(translator)
app.installTranslator(qtTranslator)
INSTALLED_TRANSLATORS.append(qtTranslator)
def localeToCode(locale: QLocale):
code = QLocale.languageToCode(locale.language())
country = locale.country()
if country and country != QLocale.Country.AnyCountry:
code += f"_{QLocale.countryToCode(country)}"
return code
def localeToFullName(locale: QLocale):
ret = QLocale.languageToString(locale.language())
country = locale.country()
if country and country != QLocale.Country.AnyCountry:
ret += f" ({QLocale.countryToString(country)})"
return ret
class LanguageChangeEventFilter(QObject):
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
if (
event.type() == QEvent.Type.LanguageChange
and hasattr(watched, "retranslateUi")
and callable(watched.retranslateUi)
):
watched.retranslateUi(watched)
return super().eventFilter(watched, event)

View File

@ -1,5 +1,6 @@
import sys import sys
from PySide6.QtCore import QSettings, QFileInfo
from PySide6.QtCore import QFileInfo, QSettings
__all__ = [ __all__ = [
"DATABASE_URL", "DATABASE_URL",
@ -8,11 +9,14 @@ __all__ = [
"TESSERACT_FILE", "TESSERACT_FILE",
"KNN_MODEL_FILE", "KNN_MODEL_FILE",
"SIFT_DATABASE_FILE", "SIFT_DATABASE_FILE",
"ANDREAL_FOLDER",
"ANDREAL_EXECUTABLE",
"Settings", "Settings",
] ]
# a key without slashes will appear in the "General" section # a key without slashes will appear in the "General" section
# see https://doc.qt.io/qt-6/qsettings.html#Format-enum for details # see https://doc.qt.io/qt-6/qsettings.html#Format-enum for details
LANGUAGE = "Language"
DATABASE_URL = "DatabaseUrl" DATABASE_URL = "DatabaseUrl"
DEVICES_JSON_FILE = "Ocr/DevicesJsonFile" DEVICES_JSON_FILE = "Ocr/DevicesJsonFile"
@ -21,7 +25,7 @@ TESSERACT_FILE = "Ocr/TesseractFile"
KNN_MODEL_FILE = "Ocr/KnnModelFile" KNN_MODEL_FILE = "Ocr/KnnModelFile"
SIFT_DATABASE_FILE = "Ocr/SiftDatabaseFile" SIFT_DATABASE_FILE = "Ocr/SiftDatabaseFile"
ANDREAL_PATH = "Andreal/AndrealFolderPath" ANDREAL_FOLDER = "Andreal/AndrealFolder"
ANDREAL_EXECUTABLE = "Andreal/AndrealExecutable" ANDREAL_EXECUTABLE = "Andreal/AndrealExecutable"
@ -44,6 +48,12 @@ class Settings(QSettings):
self.setValue(key, None) self.setValue(key, None)
self.sync() self.sync()
def language(self):
return self._strItem(LANGUAGE)
def setLanguage(self, value: str):
self._setStrItem(LANGUAGE, value)
def databaseUrl(self): def databaseUrl(self):
return self._strItem(DATABASE_URL) return self._strItem(DATABASE_URL)
@ -94,3 +104,21 @@ class Settings(QSettings):
def resetSiftDatabaseFile(self): def resetSiftDatabaseFile(self):
self._resetStrItem(SIFT_DATABASE_FILE) self._resetStrItem(SIFT_DATABASE_FILE)
def andrealFolder(self):
return self._strItem(ANDREAL_FOLDER)
def setAndrealFolder(self, value: str):
self._setStrItem(ANDREAL_FOLDER, value)
def resetAndrealFolder(self):
self._resetStrItem(ANDREAL_FOLDER)
def andrealExecutable(self):
return self._strItem(ANDREAL_EXECUTABLE)
def setAndrealExecutable(self, value: str):
self._setStrItem(ANDREAL_EXECUTABLE, value)
def resetAndrealExecutable(self):
self._resetStrItem(ANDREAL_EXECUTABLE)

View File

@ -8,6 +8,7 @@ from PySide6.QtGui import QShowEvent
from PySide6.QtWidgets import QWidget from PySide6.QtWidgets import QWidget
from ui.designer.components.chartSelector_ui import Ui_ChartSelector from ui.designer.components.chartSelector_ui import Ui_ChartSelector
from ui.extends.shared.language import LanguageChangeEventFilter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -20,6 +21,9 @@ class ChartSelector(Ui_ChartSelector, QWidget):
self.db = Database() self.db = Database()
self.setupUi(self) self.setupUi(self)
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)
self.valueChanged.connect(self.updateResultLabel) self.valueChanged.connect(self.updateResultLabel)
self.songIdSelector.valueChanged.connect(self.updateRatingClassEnabled) self.songIdSelector.valueChanged.connect(self.updateRatingClassEnabled)

View File

@ -2,6 +2,7 @@ from PySide6.QtCore import QDir, QFileInfo, QMetaObject, Qt, Signal, Slot
from PySide6.QtWidgets import QFileDialog, QWidget from PySide6.QtWidgets import QFileDialog, QWidget
from ui.designer.components.fileSelector_ui import Ui_FileSelector from ui.designer.components.fileSelector_ui import Ui_FileSelector
from ui.extends.shared.language import LanguageChangeEventFilter
class FileSelector(Ui_FileSelector, QWidget): class FileSelector(Ui_FileSelector, QWidget):
@ -13,6 +14,9 @@ class FileSelector(Ui_FileSelector, QWidget):
self.setupUi(self) self.setupUi(self)
self.reset() self.reset()
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)
self.elidedLabel.setElideMode(Qt.TextElideMode.ElideMiddle) self.elidedLabel.setElideMode(Qt.TextElideMode.ElideMiddle)
self.accepted.connect(self.filesSelected) self.accepted.connect(self.filesSelected)

View File

@ -12,12 +12,17 @@ from ui.extends.components.ocrQueue import (
OcrQueueTableProxyModel, OcrQueueTableProxyModel,
OcrScoreDelegate, OcrScoreDelegate,
) )
from ui.extends.shared.language import LanguageChangeEventFilter
class OcrQueue(Ui_OcrQueue, QWidget): class OcrQueue(Ui_OcrQueue, QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)
self.__model: Optional[OcrQueueModel] = None self.__model: Optional[OcrQueueModel] = None
self.__tableProxyModel: Optional[OcrQueueTableProxyModel] = None self.__tableProxyModel: Optional[OcrQueueTableProxyModel] = None

View File

@ -15,6 +15,7 @@ from PySide6.QtWidgets import (
) )
from ui.designer.components.scoreEditor_ui import Ui_ScoreEditor from ui.designer.components.scoreEditor_ui import Ui_ScoreEditor
from ui.extends.shared.language import LanguageChangeEventFilter
class ScoreValidateResult(IntEnum): class ScoreValidateResult(IntEnum):
@ -33,6 +34,9 @@ class ScoreEditor(Ui_ScoreEditor, QWidget):
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)
self.__validateBeforeAccept = True self.__validateBeforeAccept = True
self.__chart = None self.__chart = None
self.__score_id = None self.__score_id = None

View File

@ -11,6 +11,7 @@ from PySide6.QtWidgets import QCompleter, QWidget
from ui.designer.components.songIdSelector_ui import Ui_SongIdSelector from ui.designer.components.songIdSelector_ui import Ui_SongIdSelector
from ui.extends.components.songIdSelector import SearchCompleterModel from ui.extends.components.songIdSelector import SearchCompleterModel
from ui.extends.shared.delegates.descriptionDelegate import DescriptionDelegate from ui.extends.shared.delegates.descriptionDelegate import DescriptionDelegate
from ui.extends.shared.language import LanguageChangeEventFilter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,6 +24,9 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
self.db = Database() self.db = Database()
self.setupUi(self) self.setupUi(self)
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)
self.previousPackageButton.clicked.connect( self.previousPackageButton.clicked.connect(
lambda: self.quickSwitchSelection("previous", "package") lambda: self.quickSwitchSelection("previous", "package")
) )

View File

@ -1,21 +1,7 @@
from traceback import format_exception
from PySide6.QtWidgets import QMainWindow from PySide6.QtWidgets import QMainWindow
from ui.designer.mainwindow_ui import Ui_MainWindow from ui.designer.mainwindow_ui import Ui_MainWindow
from ui.implements.tabs.tabOcrEntry import TabOcrEntry from ui.extends.shared.language import LanguageChangeEventFilter
# try:
# import arcaea_offline_ocr
# from ui.implements.tabs.tabOcr import TabOcr
# OCR_ENABLED_FLAG = True
# except Exception as e:
# from ui.implements.tabs.tabOcrDisabled import TabOcrDisabled
# OCR_ENABLED_FLAG = False
# OCR_ERROR_TEXT = "\n".join(format_exception(e))
class MainWindow(Ui_MainWindow, QMainWindow): class MainWindow(Ui_MainWindow, QMainWindow):
@ -23,16 +9,5 @@ class MainWindow(Ui_MainWindow, QMainWindow):
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
# currentIndex = self.tabWidget.currentIndex() self.languageChangeEventFilter = LanguageChangeEventFilter(self)
# ocrTabIndex = self.tabWidget.indexOf(self.tab_ocr) self.installEventFilter(self.languageChangeEventFilter)
# self.tabWidget.removeTab(ocrTabIndex)
# self.tab_ocr.deleteLater()
# if OCR_ENABLED_FLAG:
# self.tab_ocr = TabOcr(self.tabWidget)
# else:
# self.tab_ocr = TabOcrDisabled(self.tabWidget)
# self.tab_ocr.contentLabel.setText(OCR_ERROR_TEXT)
# self.tab_ocr = TabOcrEntry(self.tabWidget)
# self.tabWidget.insertTab(ocrTabIndex, self.tab_ocr, "")
# self.tabWidget.setCurrentIndex(currentIndex)
# self.retranslateUi(self)

View File

@ -0,0 +1,83 @@
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QLabel, QPushButton
from ui.implements.components.fileSelector import FileSelector
from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget
class SettingsAndreal(SettingsBaseWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
if self.settings.andrealFolder():
self.andrealFolderValueWidget.selectFile(self.settings.andrealFolder())
self.andrealFolderValueWidget.filesSelected.connect(self.setAndrealFolder)
self.andrealFolderResetButton.clicked.connect(self.resetAndrealFolder)
self.insertItem(
"andrealFolder",
self.andrealFolderLabel,
self.andrealFolderValueWidget,
self.andrealFolderResetButton,
)
if self.settings.andrealExecutable():
self.andrealExecutableValueWidget.selectFile(
self.settings.andrealExecutable()
)
self.andrealExecutableValueWidget.filesSelected.connect(
self.setAndrealExecutable
)
self.andrealExecutableResetButton.clicked.connect(self.resetAndrealExecutable)
self.insertItem(
"andrealExecutable",
self.andrealExecutableLabel,
self.andrealExecutableValueWidget,
self.andrealExecutableResetButton,
)
def setAndrealFolder(self):
selectedFile = self.andrealFolderValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setAndrealFolder(file)
def resetAndrealFolder(self):
self.andrealFolderValueWidget.reset()
self.settings.resetAndrealFolder()
def setAndrealExecutable(self):
selectedFile = self.andrealExecutableValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setAndrealExecutable(file)
def resetAndrealExecutable(self):
self.andrealExecutableValueWidget.reset()
self.settings.resetAndrealExecutable()
def setupUi(self, *args):
self.andrealFolderLabel = QLabel(self)
self.andrealFolderValueWidget = FileSelector(self)
self.andrealFolderResetButton = QPushButton(self)
self.andrealExecutableLabel = QLabel(self)
self.andrealExecutableValueWidget = FileSelector(self)
self.andrealExecutableResetButton = QPushButton(self)
super().setupUi(self)
self.retranslateUi()
def retranslateUi(self, *args):
super().retranslateUi(self)
# fmt: off
self.setTitle(QCoreApplication.translate("Settings", "andreal.title"))
self.andrealFolderLabel.setText(QCoreApplication.translate("Settings", "andreal.andrealFolder.label"))
self.andrealFolderResetButton.setText(QCoreApplication.translate("Settings", "resetButton"))
self.andrealExecutableLabel.setText(QCoreApplication.translate("Settings", "andreal.andrealExecutable.label"))
self.andrealExecutableResetButton.setText(QCoreApplication.translate("Settings", "resetButton"))
# fmt: on

View File

@ -0,0 +1,41 @@
from PySide6.QtCore import QCoreApplication, Qt
from PySide6.QtWidgets import QLabel, QPushButton, QWidget
from ui.designer.settings.settingsBaseWidget_ui import Ui_SettingsBaseWidget
from ui.extends.shared.settings import Settings
class SettingsBaseWidget(Ui_SettingsBaseWidget, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.settings = Settings()
def setTitle(self, title: str):
self.titleLabel.setText(title)
def insertItem(
self,
prefix: str,
label: QLabel,
valueWidget: QWidget,
resetButton: QPushButton | None = None,
):
row = self.gridLayout.rowCount()
label.setObjectName(f"{prefix}Label")
label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignCenter)
self.gridLayout.addWidget(label, row, 0)
valueWidget.setObjectName(f"{prefix}ValueWidget")
self.gridLayout.addWidget(valueWidget, row, 1)
if resetButton:
resetButton.setObjectName(f"{prefix}ResetButton")
self.gridLayout.addWidget(resetButton, row, 2)
def setupUi(self, widget):
super().setupUi(widget)
self.gridLayout.setColumnStretch(1, 1)
def retranslateUi(self, widget):
super().retranslateUi(widget)

View File

@ -1,98 +0,0 @@
import logging
from PySide6.QtCore import Slot
from PySide6.QtWidgets import QWidget
from ui.designer.settings.settingsDefault_ui import Ui_SettingsDefault
from ui.extends.ocr import load_devices_json
from ui.extends.shared.settings import *
logger = logging.getLogger(__name__)
class SettingsDefault(Ui_SettingsDefault, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.settings = Settings(self)
self.devicesJsonFileSelector.filesSelected.connect(self.fillDevicesComboBox)
self.devicesJsonFileResetButton.clicked.connect(self.resetDevicesJsonFile)
self.deviceUuidResetButton.clicked.connect(self.resetDeviceUuid)
devicesJsonPath = self.settings.devicesJsonFile()
self.devicesJsonFileSelector.selectFile(devicesJsonPath)
tesseractPath = self.settings.tesseractPath()
self.tesseractFileSelector.selectFile(tesseractPath)
self.knnModelFileSelector.selectFile(self.settings.knnModelFile())
self.siftDatabaseFileSelector.selectFile(self.settings.siftDatabaseFile())
self.devicesJsonFileSelector.accepted.connect(
self.on_devicesJsonFileSelector_accepted
)
self.tesseractFileSelector.accepted.connect(
self.on_tesseractFileSelector_accepted
)
self.knnModelFileSelector.accepted.connect(
self.on_knnModelFileSelector_accepted
)
self.siftDatabaseFileSelector.accepted.connect(
self.on_siftDatabaseFileSelector_accepted
)
def setDevicesJsonFile(self):
try:
filename = self.devicesJsonFileSelector.selectedFiles()[0]
devices = load_devices_json(filename)
assert isinstance(devices, list)
self.settings.setDevicesJsonFile(filename)
except Exception as e:
logger.exception("set deviceJsonFile error")
# QMessageBox
return
def resetDevicesJsonFile(self):
self.devicesJsonFileSelector.reset()
self.settings.resetDevicesJsonFile()
def on_devicesJsonFileSelector_accepted(self):
self.setDevicesJsonFile()
def fillDevicesComboBox(self):
devicesJsonPath = self.devicesJsonFileSelector.selectedFiles()[0]
self.devicesComboBox.loadDevicesJson(devicesJsonPath)
storedDeviceUuid = self.settings.deviceUuid()
self.devicesComboBox.selectDevice(storedDeviceUuid)
@Slot()
def on_devicesComboBox_activated(self):
device = self.devicesComboBox.currentData()
if device:
self.settings.setDeviceUuid(device.uuid)
def resetDeviceUuid(self):
self.devicesComboBox.setCurrentIndex(-1)
self.settings.resetDeviceUuid()
def setTesseractFile(self):
file = self.tesseractFileSelector.selectedFiles()[0]
self.settings.setTesseractPath(file)
def on_tesseractFileSelector_accepted(self):
self.setTesseractFile()
def setKnnModelFile(self):
file = self.knnModelFileSelector.selectedFiles()[0]
self.settings.setKnnModelFile(file)
def on_knnModelFileSelector_accepted(self):
self.setKnnModelFile()
def setSiftDatabaseFile(self):
file = self.siftDatabaseFileSelector.selectedFiles()[0]
self.settings.setSiftDatabaseFile(file)
def on_siftDatabaseFileSelector_accepted(self):
self.setSiftDatabaseFile()

View File

@ -0,0 +1,106 @@
import sys
from PySide6.QtCore import QCoreApplication, QDir, QLocale, QProcess
from PySide6.QtWidgets import (
QApplication,
QCheckBox,
QComboBox,
QLabel,
QMessageBox,
QPushButton,
)
from ui.extends.shared.language import changeAppLanguage, localeToCode, localeToFullName
from ui.extends.shared.settings import DATABASE_URL, LANGUAGE
from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget
class SettingsGeneral(SettingsBaseWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
availableLanguageFiles = QDir(":/lang").entryInfoList(QDir.Filter.Files)
for fileInfo in availableLanguageFiles:
languageCode = fileInfo.baseName()
locale = QLocale(languageCode)
self.languageValueWidget.addItem(localeToFullName(locale), locale)
self.languageValueWidget.setCurrentIndex(-1)
self.languageValueWidget.currentIndexChanged.connect(self.changeLanguage)
self.languageFollowSystemCheckBox.toggled.connect(
self.changeLanguageFollowSystem
)
if self.settings.language():
locale = QLocale(self.settings.language())
index = self.languageValueWidget.findData(locale)
if index > -1:
self.languageValueWidget.setCurrentIndex(index)
else:
self.languageFollowSystemCheckBox.setChecked(True)
self.insertItem("language", self.languageLabel, self.languageValueWidget)
self.gridLayout.addWidget(
self.languageFollowSystemCheckBox,
self.gridLayout.rowCount() - 1,
self.gridLayout.columnCount(),
)
self.dbUrlResetButton.clicked.connect(self.resetDbUrl)
self.insertItem(
"dbUrl",
self.dbUrlLabel,
QLabel(self.settings.databaseUrl()),
self.dbUrlResetButton,
)
def changeLanguage(self):
locale = self.languageValueWidget.currentData()
if locale:
changeAppLanguage(locale)
self.settings.setLanguage(localeToCode(locale))
def changeLanguageFollowSystem(self):
followSystem = self.languageFollowSystemCheckBox.isChecked()
self.languageValueWidget.setCurrentIndex(-1)
if followSystem:
self.settings.remove(LANGUAGE)
changeAppLanguage(QLocale.system())
self.languageValueWidget.setEnabled(False)
else:
self.languageValueWidget.setEnabled(True)
def resetDbUrl(self):
userConfirm = QMessageBox.warning(
self,
None,
QCoreApplication.translate("Settings", "general.dbUrlResetWarning"),
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
if userConfirm == QMessageBox.StandardButton.Yes:
self.settings.remove(DATABASE_URL)
QApplication.instance().quit()
def setupUi(self, *args):
self.languageLabel = QLabel(self)
self.languageValueWidget = QComboBox(self)
self.languageFollowSystemCheckBox = QCheckBox(self)
self.dbUrlLabel = QLabel(self)
self.dbUrlResetButton = QPushButton(self)
super().setupUi(self)
self.retranslateUi()
def retranslateUi(self, *args):
super().retranslateUi(self)
# fmt: off
self.setTitle(QCoreApplication.translate("Settings", "general.title"))
self.languageLabel.setText(QCoreApplication.translate("Settings", "general.language.label"))
self.languageFollowSystemCheckBox.setText(QCoreApplication.translate("Settings", "general.language.followSystem"))
self.dbUrlLabel.setText(QCoreApplication.translate("Settings", "general.dbUrl.label"))
self.dbUrlResetButton.setText(QCoreApplication.translate("Settings", "resetButton"))
# fmt: on

View File

@ -0,0 +1,147 @@
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QLabel, QPushButton
from ui.implements.components.devicesComboBox import DevicesComboBox
from ui.implements.components.fileSelector import FileSelector
from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget
class SettingsOcr(SettingsBaseWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
if self.settings.devicesJsonFile():
self.devicesJsonValueWidget.selectFile(self.settings.devicesJsonFile())
self.devicesJsonValueWidget.filesSelected.connect(self.setDevicesJson)
self.devicesJsonResetButton.clicked.connect(self.resetDevicesJson)
self.insertItem(
"devicesJson",
self.devicesJsonLabel,
self.devicesJsonValueWidget,
self.devicesJsonResetButton,
)
if self.settings.deviceUuid():
self.deviceUuidValueWidget.selectDevice(self.settings.deviceUuid())
self.deviceUuidValueWidget.activated.connect(self.setDeviceUuid)
self.deviceUuidResetButton.clicked.connect(self.resetDeviceUuid)
self.insertItem(
"deviceUuid",
self.deviceUuidLabel,
self.deviceUuidValueWidget,
self.deviceUuidResetButton,
)
if self.settings.knnModelFile():
self.knnModelFileValueWidget.selectFile(self.settings.knnModelFile())
self.knnModelFileValueWidget.filesSelected.connect(self.setKnnModelFile)
self.knnModelFileResetButton.clicked.connect(self.resetKnnModelFile)
self.insertItem(
"knnModelFile",
self.knnModelFileLabel,
self.knnModelFileValueWidget,
self.knnModelFileResetButton,
)
if self.settings.siftDatabaseFile():
self.siftDatabaseFileValueWidget.selectFile(
self.settings.siftDatabaseFile()
)
self.siftDatabaseFileValueWidget.filesSelected.connect(self.setSiftDatabaseFile)
self.siftDatabaseFileResetButton.clicked.connect(self.resetSiftDatabaseFile)
self.insertItem(
"siftDatabaseFile",
self.siftDatabaseFileLabel,
self.siftDatabaseFileValueWidget,
self.siftDatabaseFileResetButton,
)
def setDevicesJson(self):
selectedFile = self.devicesJsonValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setDevicesJsonFile(file)
self.fillDeviceUuidComboBox()
def fillDeviceUuidComboBox(self):
devicesJsonPath = self.devicesJsonValueWidget.selectedFiles()[0]
self.deviceUuidValueWidget.loadDevicesJson(devicesJsonPath)
storedDeviceUuid = self.settings.deviceUuid()
self.deviceUuidValueWidget.selectDevice(storedDeviceUuid)
def resetDevicesJson(self):
self.deviceUuidValueWidget.clear()
self.devicesJsonValueWidget.reset()
self.settings.resetDeviceUuid()
self.settings.resetDevicesJsonFile()
def setDeviceUuid(self):
device = self.deviceUuidValueWidget.currentData()
if device:
self.settings.setDeviceUuid(device.uuid)
def resetDeviceUuid(self):
self.deviceUuidValueWidget.setCurrentIndex(-1)
self.settings.resetDeviceUuid()
def setKnnModelFile(self):
selectedFile = self.knnModelFileValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setKnnModelFile(file)
def resetKnnModelFile(self):
self.knnModelFileValueWidget.reset()
self.settings.resetKnnModelFile()
def setSiftDatabaseFile(self):
selectedFile = self.siftDatabaseFileValueWidget.selectedFiles()
if selectedFile and selectedFile[0]:
file = selectedFile[0]
self.settings.setSiftDatabaseFile(file)
def resetSiftDatabaseFile(self):
self.siftDatabaseFileValueWidget.reset()
self.settings.resetSiftDatabaseFile()
def setupUi(self, *args):
self.devicesJsonLabel = QLabel(self)
self.devicesJsonValueWidget = FileSelector(self)
self.devicesJsonResetButton = QPushButton(self)
self.deviceUuidLabel = QLabel(self)
self.deviceUuidValueWidget = DevicesComboBox(self)
self.deviceUuidResetButton = QPushButton(self)
self.knnModelFileLabel = QLabel(self)
self.knnModelFileValueWidget = FileSelector(self)
self.knnModelFileResetButton = QPushButton(self)
self.siftDatabaseFileLabel = QLabel(self)
self.siftDatabaseFileValueWidget = FileSelector(self)
self.siftDatabaseFileResetButton = QPushButton(self)
super().setupUi(self)
self.retranslateUi()
def retranslateUi(self, *args):
super().retranslateUi(self)
# fmt: off
self.setTitle(QCoreApplication.translate("Settings", "ocr.title"))
self.devicesJsonLabel.setText(QCoreApplication.translate("Settings", "ocr.devicesJson.label"))
self.devicesJsonResetButton.setText(QCoreApplication.translate("Settings", "resetButton"))
self.deviceUuidLabel.setText(QCoreApplication.translate("Settings", "ocr.deviceUuid.label"))
self.deviceUuidResetButton.setText(QCoreApplication.translate("Settings", "resetButton"))
self.knnModelFileLabel.setText(QCoreApplication.translate("Settings", "ocr.knnModelFile.label"))
self.knnModelFileResetButton.setText(QCoreApplication.translate("Settings", "resetButton"))
self.siftDatabaseFileLabel.setText(QCoreApplication.translate("Settings", "ocr.siftDatabaseFile.label"))
self.siftDatabaseFileResetButton.setText(QCoreApplication.translate("Settings", "resetButton"))
# fmt: on

View File

@ -1,11 +1,21 @@
from PySide6.QtCore import QModelIndex, Slot from PySide6.QtCore import QCoreApplication, Slot
from PySide6.QtWidgets import QListWidgetItem, QWidget from PySide6.QtWidgets import QListWidgetItem, QWidget
from ui.designer.tabs.tabSettings_ui import Ui_TabSettings from ui.designer.tabs.tabSettings_ui import Ui_TabSettings
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.implements.settings.settingsAndreal import SettingsAndreal
from ui.implements.settings.settingsGeneral import SettingsGeneral
from ui.implements.settings.settingsOcr import SettingsOcr
class SettingsEntryItem(QListWidgetItem): class SettingsLabelItem(QListWidgetItem):
pass def __init__(self, translatorKey: str):
super().__init__()
self.translatorKey = translatorKey
self.retranslateUi()
def retranslateUi(self):
self.setText(QCoreApplication.translate("Settings", self.translatorKey))
class TabSettings(Ui_TabSettings, QWidget): class TabSettings(Ui_TabSettings, QWidget):
@ -13,11 +23,42 @@ class TabSettings(Ui_TabSettings, QWidget):
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.listWidget.addItem("Default") self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.listWidget.activated.connect(self.switchPage) self.installEventFilter(self.languageChangeEventFilter)
self.listWidget.itemClicked.connect(self.switchPage)
self.listWidget.addItem(self.settingsGeneralLabel)
self.stackedWidget.addWidget(self.settingsGeneral)
self.listWidget.addItem(self.settingsOcrLabel)
self.stackedWidget.addWidget(self.settingsOcr)
self.listWidget.addItem(self.settingsAndrealLabel)
self.stackedWidget.addWidget(self.settingsAndreal)
self.listWidget.setCurrentRow(self.stackedWidget.currentIndex()) self.listWidget.setCurrentRow(self.stackedWidget.currentIndex())
@Slot(QModelIndex) @Slot(QListWidgetItem)
def switchPage(self, index: QModelIndex): def switchPage(self, item: QListWidgetItem):
self.stackedWidget.setCurrentIndex(index.row()) item.setSelected(True)
self.stackedWidget.setCurrentIndex(self.listWidget.indexFromItem(item).row())
def setupUi(self, *args):
self.settingsGeneralLabel = SettingsLabelItem("general.title")
self.settingsGeneral = SettingsGeneral(self)
self.settingsOcrLabel = SettingsLabelItem("ocr.title")
self.settingsOcr = SettingsOcr(self)
self.settingsAndrealLabel = SettingsLabelItem("andreal.title")
self.settingsAndreal = SettingsAndreal(self)
super().setupUi(self)
def retranslateUi(self, *args):
super().retranslateUi(self)
self.settingsGeneralLabel.retranslateUi()
self.settingsGeneral.retranslateUi()
self.settingsOcrLabel.retranslateUi()
self.settingsOcr.retranslateUi()
self.settingsAndrealLabel.retranslateUi()
self.settingsAndreal.retranslateUi()

View File

@ -214,67 +214,67 @@
<context> <context>
<name>OcrQueue</name> <name>OcrQueue</name>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="20"/> <location filename="../../designer/components/ocrQueue.ui" line="22"/>
<source>queue.title</source> <source>queue.title</source>
<translation>Queue</translation> <translation>Queue</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="26"/> <location filename="../../designer/components/ocrQueue.ui" line="28"/>
<source>iccOptionsGroupBox</source> <source>iccOptionsGroupBox</source>
<translation>ICC Profile Options</translation> <translation>ICC Profile Options</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="32"/> <location filename="../../designer/components/ocrQueue.ui" line="34"/>
<source>icc.ignore</source> <source>icc.ignore</source>
<translation>Ignore</translation> <translation>Ignore</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="39"/> <location filename="../../designer/components/ocrQueue.ui" line="41"/>
<source>icc.usePIL</source> <source>icc.usePIL</source>
<translation>Use PIL</translation> <translation>Use PIL</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="49"/> <location filename="../../designer/components/ocrQueue.ui" line="51"/>
<source>icc.tryFix</source> <source>icc.tryFix</source>
<translation>Try fix</translation> <translation>Try fix</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="59"/> <location filename="../../designer/components/ocrQueue.ui" line="61"/>
<source>queue.addImageButton</source> <source>queue.addImageButton</source>
<translation>Add Image</translation> <translation>Add Image</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="69"/> <location filename="../../designer/components/ocrQueue.ui" line="71"/>
<source>queue.removeSelected</source> <source>queue.removeSelected</source>
<translation>Remove Selected</translation> <translation>Remove Selected</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="79"/> <location filename="../../designer/components/ocrQueue.ui" line="81"/>
<source>queue.removeAll</source> <source>queue.removeAll</source>
<translation>Remove All</translation> <translation>Remove All</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="99"/> <location filename="../../designer/components/ocrQueue.ui" line="101"/>
<source>queue.startOcrButton</source> <source>queue.startOcrButton</source>
<translation>Start OCR</translation> <translation>Start OCR</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="151"/> <location filename="../../designer/components/ocrQueue.ui" line="153"/>
<source>results</source> <source>results</source>
<translation>Results</translation> <translation>Results</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="160"/> <location filename="../../designer/components/ocrQueue.ui" line="162"/>
<source>results.acceptSelectedButton</source> <source>results.acceptSelectedButton</source>
<translation>Accept Selected</translation> <translation>Accept Selected</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="167"/> <location filename="../../designer/components/ocrQueue.ui" line="169"/>
<source>results.acceptAllButton</source> <source>results.acceptAllButton</source>
<translation>Accept All</translation> <translation>Accept All</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="187"/> <location filename="../../designer/components/ocrQueue.ui" line="189"/>
<source>results.ignoreValidate</source> <source>results.ignoreValidate</source>
<translation>Ignore <translation>Ignore
validation</translation> validation</translation>
@ -408,37 +408,82 @@ validation</translation>
</message> </message>
</context> </context>
<context> <context>
<name>SettingsDefault</name> <name>Settings</name>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="91"/> <location filename="../../implements/settings/settingsAndreal.py" line="76"/>
<source>devicesJsonFile</source> <source>andreal.title</source>
<translation>Default devices.json</translation> <translation>Andreal</translation>
</message> </message>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="36"/> <location filename="../../implements/settings/settingsAndreal.py" line="78"/>
<source>deviceUuid</source> <source>andreal.andrealFolder.label</source>
<translation>Default Device</translation> <translation>Andreal Folder</translation>
</message> </message>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="52"/> <location filename="../../implements/settings/settingsAndreal.py" line="79"/>
<source>tesseractFile</source> <location filename="../../implements/settings/settingsAndreal.py" line="82"/>
<translation>tesseract Path</translation> <location filename="../../implements/settings/settingsGeneral.py" line="105"/>
</message> <location filename="../../implements/settings/settingsOcr.py" line="137"/>
<message> <location filename="../../implements/settings/settingsOcr.py" line="140"/>
<location filename="../../designer/settings/settingsDefault.ui" line="101"/> <location filename="../../implements/settings/settingsOcr.py" line="143"/>
<location filename="../../designer/settings/settingsDefault.ui" line="118"/> <location filename="../../implements/settings/settingsOcr.py" line="146"/>
<source>resetButton</source> <source>resetButton</source>
<translation>Reset</translation> <translation>Reset</translation>
</message> </message>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="108"/> <location filename="../../implements/settings/settingsAndreal.py" line="81"/>
<source>knnModelFile</source> <source>andreal.andrealExecutable.label</source>
<translation>Default KNearest Model</translation> <translation>Andreal Executable</translation>
</message> </message>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="141"/> <location filename="../../implements/settings/settingsGeneral.py" line="76"/>
<source>siftDatabaseFile</source> <source>general.dbUrlResetWarning</source>
<translation>Default SIFT Database File</translation> <translation>Application will now delete this setting and exit. Reboot application manually to specify a new database file. Continue?</translation>
</message>
<message>
<location filename="../../implements/settings/settingsGeneral.py" line="99"/>
<source>general.title</source>
<translation>General</translation>
</message>
<message>
<location filename="../../implements/settings/settingsGeneral.py" line="101"/>
<source>general.language.label</source>
<translation>Language</translation>
</message>
<message>
<location filename="../../implements/settings/settingsGeneral.py" line="102"/>
<source>general.language.followSystem</source>
<translation>Follow system</translation>
</message>
<message>
<location filename="../../implements/settings/settingsGeneral.py" line="104"/>
<source>general.dbUrl.label</source>
<translation>Database URL</translation>
</message>
<message>
<location filename="../../implements/settings/settingsOcr.py" line="134"/>
<source>ocr.title</source>
<translation>OCR</translation>
</message>
<message>
<location filename="../../implements/settings/settingsOcr.py" line="136"/>
<source>ocr.devicesJson.label</source>
<translation>Default devices.json</translation>
</message>
<message>
<location filename="../../implements/settings/settingsOcr.py" line="139"/>
<source>ocr.deviceUuid.label</source>
<translation>Default device</translation>
</message>
<message>
<location filename="../../implements/settings/settingsOcr.py" line="142"/>
<source>ocr.knnModelFile.label</source>
<translation>Default KNearest model</translation>
</message>
<message>
<location filename="../../implements/settings/settingsOcr.py" line="145"/>
<source>ocr.siftDatabaseFile.label</source>
<translation>Default SIFT database file</translation>
</message> </message>
</context> </context>
<context> <context>
@ -622,6 +667,79 @@ validation</translation>
<source>tab.infoLookup</source> <source>tab.infoLookup</source>
<translation>Chart Info Lookup</translation> <translation>Chart Info Lookup</translation>
</message> </message>
<message>
<location filename="../../designer/tabs/tabToolsEntry.ui" line="29"/>
<source>tab.andreal</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>TabTools_Andreal</name>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="23"/>
<source>andrealPathSelector</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="40"/>
<source>andrealExecutableSelector</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="50"/>
<source>imageType</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="60"/>
<source>imageVersion</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="98"/>
<source>exportJsonButton</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="105"/>
<source>generateButton</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="127"/>
<source>previewImage</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="149"/>
<source>imageType.recent</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="156"/>
<source>imageType.best</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="163"/>
<source>imageType.best30</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="172"/>
<source>imageFormat</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="220"/>
<source>chart</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="227"/>
<source>imageQuality</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>TabTools_InfoLookup</name> <name>TabTools_InfoLookup</name>

View File

@ -214,67 +214,67 @@
<context> <context>
<name>OcrQueue</name> <name>OcrQueue</name>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="20"/> <location filename="../../designer/components/ocrQueue.ui" line="22"/>
<source>queue.title</source> <source>queue.title</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="26"/> <location filename="../../designer/components/ocrQueue.ui" line="28"/>
<source>iccOptionsGroupBox</source> <source>iccOptionsGroupBox</source>
<translation>ICC </translation> <translation>ICC </translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="32"/> <location filename="../../designer/components/ocrQueue.ui" line="34"/>
<source>icc.ignore</source> <source>icc.ignore</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="39"/> <location filename="../../designer/components/ocrQueue.ui" line="41"/>
<source>icc.usePIL</source> <source>icc.usePIL</source>
<translation>使 PIL </translation> <translation>使 PIL </translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="49"/> <location filename="../../designer/components/ocrQueue.ui" line="51"/>
<source>icc.tryFix</source> <source>icc.tryFix</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="59"/> <location filename="../../designer/components/ocrQueue.ui" line="61"/>
<source>queue.addImageButton</source> <source>queue.addImageButton</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="69"/> <location filename="../../designer/components/ocrQueue.ui" line="71"/>
<source>queue.removeSelected</source> <source>queue.removeSelected</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="79"/> <location filename="../../designer/components/ocrQueue.ui" line="81"/>
<source>queue.removeAll</source> <source>queue.removeAll</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="99"/> <location filename="../../designer/components/ocrQueue.ui" line="101"/>
<source>queue.startOcrButton</source> <source>queue.startOcrButton</source>
<translation> OCR</translation> <translation> OCR</translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="151"/> <location filename="../../designer/components/ocrQueue.ui" line="153"/>
<source>results</source> <source>results</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="160"/> <location filename="../../designer/components/ocrQueue.ui" line="162"/>
<source>results.acceptSelectedButton</source> <source>results.acceptSelectedButton</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="167"/> <location filename="../../designer/components/ocrQueue.ui" line="169"/>
<source>results.acceptAllButton</source> <source>results.acceptAllButton</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/components/ocrQueue.ui" line="187"/> <location filename="../../designer/components/ocrQueue.ui" line="189"/>
<source>results.ignoreValidate</source> <source>results.ignoreValidate</source>
<translation></translation> <translation></translation>
</message> </message>
@ -407,36 +407,81 @@
</message> </message>
</context> </context>
<context> <context>
<name>SettingsDefault</name> <name>Settings</name>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="91"/> <location filename="../../implements/settings/settingsAndreal.py" line="76"/>
<source>devicesJsonFile</source> <source>andreal.title</source>
<translation></translation> <translation>Andreal</translation>
</message> </message>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="36"/> <location filename="../../implements/settings/settingsAndreal.py" line="78"/>
<source>deviceUuid</source> <source>andreal.andrealFolder.label</source>
<translation></translation> <translation>Andreal </translation>
</message> </message>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="52"/> <location filename="../../implements/settings/settingsAndreal.py" line="79"/>
<source>tesseractFile</source> <location filename="../../implements/settings/settingsAndreal.py" line="82"/>
<translation>tesseract </translation> <location filename="../../implements/settings/settingsGeneral.py" line="105"/>
</message> <location filename="../../implements/settings/settingsOcr.py" line="137"/>
<message> <location filename="../../implements/settings/settingsOcr.py" line="140"/>
<location filename="../../designer/settings/settingsDefault.ui" line="101"/> <location filename="../../implements/settings/settingsOcr.py" line="143"/>
<location filename="../../designer/settings/settingsDefault.ui" line="118"/> <location filename="../../implements/settings/settingsOcr.py" line="146"/>
<source>resetButton</source> <source>resetButton</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="108"/> <location filename="../../implements/settings/settingsAndreal.py" line="81"/>
<source>knnModelFile</source> <source>andreal.andrealExecutable.label</source>
<translation>Andreal </translation>
</message>
<message>
<location filename="../../implements/settings/settingsGeneral.py" line="76"/>
<source>general.dbUrlResetWarning</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/settings/settingsGeneral.py" line="99"/>
<source>general.title</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/settings/settingsGeneral.py" line="101"/>
<source>general.language.label</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/settings/settingsGeneral.py" line="102"/>
<source>general.language.followSystem</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/settings/settingsGeneral.py" line="104"/>
<source>general.dbUrl.label</source>
<translation> URL</translation>
</message>
<message>
<location filename="../../implements/settings/settingsOcr.py" line="134"/>
<source>ocr.title</source>
<translation>OCR</translation>
</message>
<message>
<location filename="../../implements/settings/settingsOcr.py" line="136"/>
<source>ocr.devicesJson.label</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/settings/settingsOcr.py" line="139"/>
<source>ocr.deviceUuid.label</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/settings/settingsOcr.py" line="142"/>
<source>ocr.knnModelFile.label</source>
<translation> KNearest </translation> <translation> KNearest </translation>
</message> </message>
<message> <message>
<location filename="../../designer/settings/settingsDefault.ui" line="141"/> <location filename="../../implements/settings/settingsOcr.py" line="145"/>
<source>siftDatabaseFile</source> <source>ocr.siftDatabaseFile.label</source>
<translation> SIFT </translation> <translation> SIFT </translation>
</message> </message>
</context> </context>
@ -621,6 +666,79 @@
<source>tab.infoLookup</source> <source>tab.infoLookup</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../../designer/tabs/tabToolsEntry.ui" line="29"/>
<source>tab.andreal</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>TabTools_Andreal</name>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="23"/>
<source>andrealPathSelector</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="40"/>
<source>andrealExecutableSelector</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="50"/>
<source>imageType</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="60"/>
<source>imageVersion</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="98"/>
<source>exportJsonButton</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="105"/>
<source>generateButton</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="127"/>
<source>previewImage</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="149"/>
<source>imageType.recent</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="156"/>
<source>imageType.best</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="163"/>
<source>imageType.best30</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="172"/>
<source>imageFormat</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="220"/>
<source>chart</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../designer/tabs/tabTools/tabTools_Andreal.ui" line="227"/>
<source>imageQuality</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>TabTools_InfoLookup</name> <name>TabTools_InfoLookup</name>