From 6b6bd9d728537962b83a9b6d94b114a3b4466b00 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Wed, 30 Oct 2024 22:09:05 +0800 Subject: [PATCH 1/3] add confirm for the RUN button --- config/stylesheet.css | 6 +++++- translate/zh_CN.ts | 6 ++++++ ui/config_proj.py | 4 ++++ ui/mainwindow.py | 12 +++++++++--- ui/mainwindowbars.py | 2 +- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/config/stylesheet.css b/config/stylesheet.css index 39a119cd..74f3e19d 100644 --- a/config/stylesheet.css +++ b/config/stylesheet.css @@ -322,10 +322,14 @@ QMenu::item:selected { } QDialog { - font-size: 7px; + font-size: 14px; background-color: @widgetBackgroundColor; } +QDialog QPushButton { + padding: 5px 15px; +} + QGroupBox { background-color: @emptyContentBackgroundColor; } diff --git a/translate/zh_CN.ts b/translate/zh_CN.ts index 659aefc5..c3d054f1 100644 --- a/translate/zh_CN.ts +++ b/translate/zh_CN.ts @@ -885,6 +885,12 @@ Failed to load project from 无法从所选路径加载项目: + + + Are you sure to run image translation again? +All existing translation results will be cleared! + 确定要重新运行吗?现有翻译结果将被清空! + ModuleManager diff --git a/ui/config_proj.py b/ui/config_proj.py index 67bf30cf..d517bf3d 100644 --- a/ui/config_proj.py +++ b/ui/config_proj.py @@ -306,6 +306,10 @@ def backup(self): def is_empty(self): return len(self.pages) == 0 + @property + def is_all_pages_no_text(self): + return all([len(blklist) == 0 for blklist in self.pages.values()]) + @property def img_valid(self): return self.img_array is not None diff --git a/ui/mainwindow.py b/ui/mainwindow.py index 6672c471..0a74781f 100644 --- a/ui/mainwindow.py +++ b/ui/mainwindow.py @@ -278,7 +278,7 @@ def setupConfig(self): module_manager.blktrans_pipeline_finished.connect(self.on_blktrans_finished) module_manager.imgtrans_thread.post_process_mask = self.drawingPanel.rectPanel.post_process_mask - self.leftBar.run_imgtrans.connect(self.on_run_imgtrans) + self.leftBar.run_imgtrans_clicked.connect(self.run_imgtrans) self.bottomBar.inpaint_btn_clicked.connect(self.inpaintBtnClicked) self.bottomBar.translatorStatusbtn.clicked.connect(self.translatorStatusBtnPressed) self.bottomBar.transTranspageBtn.run_target.connect(self.on_transpagebtn_pressed) @@ -1077,11 +1077,17 @@ def on_display_lang_changed(self, lang: str): self.set_display_lang(lang) def run_imgtrans(self): + if not self.imgtrans_proj.is_all_pages_no_text: + reply = QMessageBox.question(self, self.tr('Confirmation'), + self.tr('Are you sure to run image translation again?\nAll existing translation results will be cleared!'), + QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if reply != QMessageBox.Yes: + return self.on_run_imgtrans() def run_imgtrans_wo_textstyle_update(self): self._run_imgtrans_wo_textstyle_update = True - self.on_run_imgtrans() + self.run_imgtrans() def on_run_imgtrans(self): self.backup_blkstyles.clear() @@ -1321,7 +1327,7 @@ def run_next_dir(self): shared.pbar['translate'] = tqdm(range(npages), desc="Translation") if pcfg.module.enable_inpaint: shared.pbar['inpaint'] = tqdm(range(npages), desc="Inpaint") - self.run_imgtrans() + self.on_run_imgtrans() def on_create_errdialog(self, error_msg: str, detail_traceback: str = '', exception_type: str = ''): try: diff --git a/ui/mainwindowbars.py b/ui/mainwindowbars.py index d42568f3..2759bc28 100644 --- a/ui/mainwindowbars.py +++ b/ui/mainwindowbars.py @@ -181,7 +181,7 @@ def __init__(self, mainwindow, *args, **kwargs) -> None: self.runImgtransBtn = QPushButton() self.runImgtransBtn.setText('RUN') self.runImgtransBtn.setFixedSize(LEFTBTN_WIDTH, LEFTBTN_WIDTH) - self.run_imgtrans = self.runImgtransBtn.clicked + self.run_imgtrans_clicked = self.runImgtransBtn.clicked self.runImgtransBtn.setFixedSize(LEFTBTN_WIDTH, LEFTBTN_WIDTH) vlayout = QVBoxLayout(self) From d0c7f5e40bcb25db1244bd1522115c1021c07339 Mon Sep 17 00:00:00 2001 From: dmMaze Date: Thu, 31 Oct 2024 14:47:44 +0800 Subject: [PATCH 2/3] fix widgets size in text advanced format panel --- config/stylesheet.css | 13 +++++++++++++ ui/custom_widget/__init__.py | 4 ++-- ui/custom_widget/combobox.py | 4 ++++ ui/custom_widget/label.py | 31 +++++++++++++++++++++++++++++++ ui/module_parse_widgets.py | 17 +---------------- ui/text_advanced_format.py | 6 +++--- ui/text_panel.py | 1 - 7 files changed, 54 insertions(+), 22 deletions(-) diff --git a/config/stylesheet.css b/config/stylesheet.css index 74f3e19d..ae67c7d5 100644 --- a/config/stylesheet.css +++ b/config/stylesheet.css @@ -1006,6 +1006,19 @@ TextStyleLabel { border-radius: 7px; } +SmallParamLabel { + font-size: 12px; + height: 20px; +} + +SmallComboBox { + height: 20px; + font-size: 12px; + /* padding-left: 8px; */ + border: 1px solid @borderColor; + background-color: @transtexteditBackgroundColor; +} + ArrowLeftButton { image: url(icons/arrow-left.svg); border: none; diff --git a/ui/custom_widget/__init__.py b/ui/custom_widget/__init__.py index b40007e2..0671789f 100644 --- a/ui/custom_widget/__init__.py +++ b/ui/custom_widget/__init__.py @@ -1,10 +1,10 @@ from .scrollbar import ScrollBar -from .combobox import ComboBox, ConfigComboBox, ParamComboBox, SizeComboBox +from .combobox import ComboBox, ConfigComboBox, ParamComboBox, SizeComboBox, SmallComboBox from .widget import Widget, SeparatorWidget from .view_panel import PanelGroupBox, PanelArea, PanelAreaContent, ViewWidget, ExpandLabel from .message import MessageBox, TaskProgressBar, FrameLessMessageBox, ProgressMessageBox, ImgtransProgressMessageBox from .flow_layout import FlowLayout -from .label import FadeLabel, ColorPickerLabel, ClickableLabel, CheckableLabel, TextCheckerLabel +from .label import FadeLabel, ColorPickerLabel, ClickableLabel, CheckableLabel, TextCheckerLabel, ParamNameLabel, SmallParamLabel from .slider import PaintQSlider from .helper import isDarkTheme, themeColor from .push_button import NoBorderPushBtn diff --git a/ui/custom_widget/combobox.py b/ui/custom_widget/combobox.py index 314a3faf..4fd6246d 100644 --- a/ui/custom_widget/combobox.py +++ b/ui/custom_widget/combobox.py @@ -25,6 +25,10 @@ def wheelEvent(self, *args, **kwargs): return super().wheelEvent(*args, **kwargs) else: return self.scrollWidget.wheelEvent(*args, **kwargs) + + +class SmallComboBox(ComboBox): + pass class ConfigComboBox(ComboBox): diff --git a/ui/custom_widget/label.py b/ui/custom_widget/label.py index dd538d7e..38d93cca 100644 --- a/ui/custom_widget/label.py +++ b/ui/custom_widget/label.py @@ -6,6 +6,7 @@ from qtpy.QtGui import QMouseEvent, QWheelEvent, QColor +from utils.shared import CONFIG_FONTSIZE_CONTENT class FadeLabel(QLabel): def __init__(self, *args, **kwargs): @@ -141,3 +142,33 @@ def mousePressEvent(self, event: QMouseEvent): self.checkStateChanged.emit(self.checked) +class ParamNameLabel(QLabel): + def __init__(self, param_name: str, alignment = None, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + if alignment is None: + self.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) + else: + self.setAlignment(alignment) + + font = self.font() + font.setPointSizeF(CONFIG_FONTSIZE_CONTENT-2) + self.setFont(font) + self.setText(param_name) + self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True) + + +class SmallParamLabel(QLabel): + def __init__(self, param_name: str, alignment = None, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + if alignment is None: + self.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) + else: + self.setAlignment(alignment) + + # font = self.font() + # font.setPointSizeF(CONFIG_FONTSIZE_CONTENT-2) + # self.setFont(font) + self.setText(param_name) + self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True) \ No newline at end of file diff --git a/ui/module_parse_widgets.py b/ui/module_parse_widgets.py index e4fdbf4f..fecd140e 100644 --- a/ui/module_parse_widgets.py +++ b/ui/module_parse_widgets.py @@ -3,7 +3,7 @@ from modules import GET_VALID_INPAINTERS, GET_VALID_TEXTDETECTORS, GET_VALID_TRANSLATORS, GET_VALID_OCR, \ BaseTranslator, DEFAULT_DEVICE, GPUINTENSIVE_SET from utils.logger import logger as LOGGER -from .custom_widget import ConfigComboBox, ParamComboBox, NoBorderPushBtn +from .custom_widget import ConfigComboBox, ParamComboBox, NoBorderPushBtn, ParamNameLabel from utils.shared import CONFIG_FONTSIZE_CONTENT, CONFIG_COMBOBOX_MIDEAN, CONFIG_COMBOBOX_LONG, CONFIG_COMBOBOX_SHORT, CONFIG_COMBOBOX_HEIGHT from utils.config import pcfg @@ -12,21 +12,6 @@ from qtpy.QtGui import QDoubleValidator -class ParamNameLabel(QLabel): - def __init__(self, param_name: str, alignment = None, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - - if alignment is None: - self.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) - else: - self.setAlignment(alignment) - - font = self.font() - font.setPointSizeF(CONFIG_FONTSIZE_CONTENT-2) - self.setFont(font) - self.setText(param_name) - self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True) - class ParamLineEditor(QLineEdit): paramwidget_edited = Signal(str, str) diff --git a/ui/text_advanced_format.py b/ui/text_advanced_format.py index 4302f61d..45a4f2ea 100644 --- a/ui/text_advanced_format.py +++ b/ui/text_advanced_format.py @@ -3,7 +3,7 @@ from qtpy.QtWidgets import QVBoxLayout, QPushButton, QComboBox, QLabel, QHBoxLayout from qtpy.QtCore import Signal, Qt, QRectF -from .custom_widget import PanelGroupBox, PanelArea, ComboBox, QFontChecker +from .custom_widget import PanelGroupBox, PanelArea, ComboBox, QFontChecker, SmallParamLabel, SmallComboBox from utils.fontformat import FontFormat @@ -18,7 +18,7 @@ def __init__(self, panel_name: str, config_name: str, config_expand_name: str): self.active_format: FontFormat = None - self.linespacing_type_combobox = ComboBox( + self.linespacing_type_combobox = SmallComboBox( parent=self, options=[ self.tr("Proportional"), @@ -28,7 +28,7 @@ def __init__(self, panel_name: str, config_name: str, config_expand_name: str): self.linespacing_type_combobox.activated.connect( lambda: self.on_format_changed('line_spacing_type', self.linespacing_type_combobox.currentIndex) ) - linespacing_type_label = QLabel(self.tr('Line Spacing Type: ')) + linespacing_type_label = SmallParamLabel(self.tr('Line Spacing Type')) linespacing_type_layout = QHBoxLayout() linespacing_type_layout.addWidget(linespacing_type_label) linespacing_type_layout.addWidget(self.linespacing_type_combobox) diff --git a/ui/text_panel.py b/ui/text_panel.py index 836c5d8c..52edb7d8 100644 --- a/ui/text_panel.py +++ b/ui/text_panel.py @@ -12,7 +12,6 @@ from .custom_widget import Widget, ColorPickerLabel, ClickableLabel, CheckableLabel, TextCheckerLabel, AlignmentChecker, QFontChecker, SizeComboBox from .textitem import TextBlkItem from .text_graphical_effect import TextEffectPanelDeprecated -from .text_effect import TextEffectPanel from .text_advanced_format import TextAdvancedFormatPanel from .text_style_presets import TextStylePresetPanel from . import funcmaps as FM From a4c50344d042a6008db2022003f9997db226db3c Mon Sep 17 00:00:00 2001 From: dmMaze Date: Thu, 31 Oct 2024 18:38:58 +0800 Subject: [PATCH 3/3] support import translation from TXT/markdown #639 --- ui/config_proj.py | 67 +++++++++++++++++++++++++++++++++++++++++ ui/mainwindow.py | 40 ++++++++++++++++++++++-- ui/mainwindowbars.py | 4 +++ ui/scenetext_manager.py | 1 + 4 files changed, 109 insertions(+), 3 deletions(-) diff --git a/ui/config_proj.py b/ui/config_proj.py index d517bf3d..6626c5bd 100644 --- a/ui/config_proj.py +++ b/ui/config_proj.py @@ -26,6 +26,39 @@ def read_jpg_metadata(imgpath: str): bubdict = json.loads(user_comment) return bubdict +page_start_pattern = re.compile(r'^###\s+', re.MULTILINE) +text_blkid_start_pattern = re.compile(r'^\d+\.', re.MULTILINE) + +def parse_txt_translation(file_path: str): + with open(file_path, 'r', encoding='utf8') as f: + content = f.read() + page_start = None + page_list = [] + for matched in page_start_pattern.finditer(content): + start, end = matched.span() + if page_start is not None: + page_list.append({'page_content': content[page_start: start]}) + page_start = start + if page_start is not None: + page_list.append({'page_content': content[page_start:]}) + + for page_dict in page_list: + page_content = page_dict['page_content'] + page_dict['page_name'] = page_start_pattern.sub('', page_content.split('\n')[0]).strip() + blkid_start = blkid_end = None + blk_list = [] + for matched in text_blkid_start_pattern.finditer(page_content): + start, end = matched.span() + if blkid_start is not None: + blk_list.append(page_content[blkid_end: start].strip()) + blkid_start = start + blkid_end = end + if blkid_start is not None: + blk_list.append(page_content[blkid_end:]) + page_dict['blk_list'] = blk_list + + return page_list + class TextBlkEncoder(NumpyEncoder): def default(self, obj): @@ -139,6 +172,40 @@ def load_from_dict(self, proj_dict: dict): if len(self.pages) > 0: self.set_current_img_byidx(0) + def load_translation_from_txt(self, file_path: str): + page_list = parse_txt_translation(file_path) + missing_pages = [] + unmatched_pages = [] + unexpected_pages = [] + matched_pages = [] + for page_dict in page_list: + page_name = page_dict['page_name'] + if page_name in self.pages: + matched_pages.append(page_name) + else: + unexpected_pages.append(page_name) + continue + blklist = self.pages[page_name] + n_blk = len(blklist) + src_blk_list = page_dict['blk_list'] + n_src_blk = len(src_blk_list) + if n_src_blk != n_blk: + LOGGER.warning(f'Unmatched text blocks in {page_name}, number of text blocks in this page vs source file: {n_blk}-{n_src_blk}') + unmatched_pages.append(page_name) + for blkid in range(min(n_blk, n_src_blk)): + blk = blklist[blkid] + blk.rich_text = '' + blk.translation = src_blk_list[blkid] + + matched_pages = set(matched_pages) + if len(matched_pages) != self.num_pages: + for page_name in self.pages: + if page_name not in matched_pages: + missing_pages.append(page_name) + + all_matched = len(missing_pages) == 0 and len(unmatched_pages) == 0 and len(unexpected_pages) == 0 + return all_matched, {'missing_pages': missing_pages, 'unmatched_pages': unmatched_pages, 'unexpected_pages': unexpected_pages, 'matched_pages': matched_pages} + def load_from_json(self, json_path: str): old_dir = self.directory directory = osp.dirname(json_path) diff --git a/ui/mainwindow.py b/ui/mainwindow.py index 0a74781f..18d2cc67 100644 --- a/ui/mainwindow.py +++ b/ui/mainwindow.py @@ -14,7 +14,7 @@ from utils.text_processing import is_cjk, full_len, half_len from utils.textblock import TextBlock, TextAlignment from utils import shared -from utils import create_error_dialog +from utils import create_error_dialog, create_info_dialog from modules.translators.trans_chatgpt import GPTTranslator from .misc import parse_stylesheet, set_html_family, QKEY from utils.config import ProgramConfig, pcfg, save_config, text_styles, save_text_styles, load_textstyle_from, FontFormat @@ -139,6 +139,7 @@ def setupUi(self): self.leftBar.export_trans_txt.connect(lambda : self.on_export_txt(dump_target='translation')) self.leftBar.export_src_md.connect(lambda : self.on_export_txt(dump_target='source', suffix='.md')) self.leftBar.export_trans_md.connect(lambda : self.on_export_txt(dump_target='translation', suffix='.md')) + self.leftBar.import_trans_txt.connect(self.on_import_trans_txt) self.pageList = PageListView() self.pageList.reveal_file.connect(self.on_reveal_file) @@ -476,7 +477,6 @@ def pageListCurrentItemChanged(self): self.imgtrans_proj.set_current_img(item.text()) self.canvas.clear_undostack(update_saved_step=True) self.canvas.updateCanvas() - self.st_manager.hovering_transwidget = None self.st_manager.updateSceneTextitems() self.titleBar.setTitleContent(page_name=self.imgtrans_proj.current_img) self.module_manager.handle_page_changed() @@ -1186,7 +1186,41 @@ def on_export_txt(self, dump_target, suffix='.txt'): msg.setText(self.tr('Text file exported to ') + self.imgtrans_proj.dump_txt_path(dump_target, suffix)) msg.exec_() except Exception as e: - create_error_dialog(e, self.tr('failed to export as TEXT file')) + create_error_dialog(e, self.tr('Failed to export as TEXT file')) + + def on_import_trans_txt(self): + try: + selected_file = '' + dialog = QFileDialog() + selected_file = str(dialog.getOpenFileUrl(self.parent(), self.tr('Import *.md/*.txt'), filter="*.txt *.md *.TXT *.MD")[0].toLocalFile()) + if not osp.exists(selected_file): + return + + all_matched, match_rst = self.imgtrans_proj.load_translation_from_txt(selected_file) + matched_pages = match_rst['matched_pages'] + + if self.imgtrans_proj.current_img in matched_pages: + self.canvas.clear_undostack(update_saved_step=True) + self.st_manager.updateSceneTextitems() + + if all_matched: + msg = self.tr('Translation imported and matched successfully.') + else: + msg = self.tr('Imported txt file not fully matched with current project, please make sure source txt file structured like results from \"export TXT/markdown\"') + if len(match_rst['missing_pages']) > 0: + msg += '\n' + self.tr('Missing pages: ') + '\n' + msg += '\n'.join(match_rst['missing_pages']) + if len(match_rst['unexpected_pages']) > 0: + msg += '\n' + self.tr('Unexpected pages: ') + '\n' + msg += '\n'.join(match_rst['unexpected_pages']) + if len(match_rst['unmatched_pages']) > 0: + msg += '\n' + self.tr('Unmatched pages: ') + '\n' + msg += '\n'.join(match_rst['unmatched_pages']) + msg = msg.strip() + create_info_dialog(msg) + + except Exception as e: + create_error_dialog(e, self.tr('Failed to import translation from ') + selected_file) def on_reveal_file(self): current_img_path = self.imgtrans_proj.current_img_path() diff --git a/ui/mainwindowbars.py b/ui/mainwindowbars.py index a01cc061..6cf58e63 100644 --- a/ui/mainwindowbars.py +++ b/ui/mainwindowbars.py @@ -154,6 +154,9 @@ def __init__(self, mainwindow, *args, **kwargs) -> None: actionExportTranslationMD = QAction(self.tr("Export translation as markdown"), self) self.export_trans_md = actionExportTranslationMD.triggered + actionImportTranslationTxt = QAction(self.tr("Import translation from TXT/markdown"), self) + self.import_trans_txt = actionImportTranslationTxt.triggered + self.recentMenu = QMenu(self.tr("Open Recent"), self) openMenu = QMenu(self) @@ -168,6 +171,7 @@ def __init__(self, mainwindow, *args, **kwargs) -> None: actionExportTranslationTxt, actionExportSrcMD, actionExportTranslationMD, + actionImportTranslationTxt, ]) self.openBtn = OpenBtn() self.openBtn.setFixedSize(LEFTBTN_WIDTH, LEFTBTN_WIDTH) diff --git a/ui/scenetext_manager.py b/ui/scenetext_manager.py index c7497412..4128e806 100644 --- a/ui/scenetext_manager.py +++ b/ui/scenetext_manager.py @@ -440,6 +440,7 @@ def clearSceneTextitems(self): self.pairwidget_list.clear() def updateSceneTextitems(self): + self.hovering_transwidget = None self.txtblkShapeControl.setBlkItem(None) self.clearSceneTextitems() for textblock in self.imgtrans_proj.current_block_list():