Loading ballontranslator/ui/canvas.py +11 −2 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ from .textitem import TextBlkItem, TextBlock from .texteditshapecontrol import TextBlkShapeControl from .stylewidgets import FadeLabel from .image_edit import ImageEditMode, DrawingLayer, StrokeImgItem from .search_replace_widgets import SearchWidget from .search_replace_widgets import SearchWidget, ReplaceOneCommand, ReplaceAllCommand from . import constants as C CANVAS_SCALE_MAX = 3.0 Loading Loading @@ -123,6 +123,8 @@ class Canvas(QGraphicsScene): self.gv.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) self.search_widget = SearchWidget(self.gv, is_floating=True) self.search_widget.replace_one.connect(self.on_search_replace_one) self.search_widget.replace_all.connect(self.on_search_replace_all) self.ctrl_relesed = self.gv.ctrl_released self.vscroll_bar = self.gv.verticalScrollBar() Loading Loading @@ -506,3 +508,10 @@ class Canvas(QGraphicsScene): item.setParentItem(None) self.stroke_img_item = None self.erase_img_key = None def on_search_replace_one(self): self.undoStack.push(ReplaceOneCommand(self.search_widget)) pass def on_search_replace_all(self): pass No newline at end of file ballontranslator/ui/image_edit.py +0 −5 Original line number Diff line number Diff line Loading @@ -5,11 +5,6 @@ from qtpy.QtCore import QRectF, Qt, QPointF, QSize, QPoint, QDateTime from qtpy.QtWidgets import QStyleOptionGraphicsItem, QGraphicsPixmapItem, QWidget, QGraphicsPathItem, QGraphicsItem from qtpy.QtGui import QPen, QColor, QPainterPath, QCursor, QPainter, QPixmap, QImage, QBrush try: from qtpy.QtWidgets import QUndoCommand except: from qtpy.QtGui import QUndoCommand from .misc import DrawPanelConfig, pixmap2ndarray, ndarray2pixmap from utils.io_utils import imread, imwrite Loading ballontranslator/ui/imgtranspanel.py +64 −17 Original line number Diff line number Diff line from typing import List from typing import List, Union from qtpy.QtWidgets import QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QFrame, QApplication from qtpy.QtCore import Signal, Qt, QSize, QEvent from qtpy.QtGui import QColor, QFocusEvent, QInputMethodEvent from .stylewidgets import Widget, SeparatorWidget from qtpy.QtGui import QColor, QFocusEvent, QInputMethodEvent, QKeyEvent try: from qtpy.QtWidgets import QUndoCommand except: from qtpy.QtGui import QUndoCommand from .stylewidgets import Widget, SeparatorWidget from .textitem import TextBlock, TextBlkItem from .fontformatpanel import FontFormatPanel class SourceTextEdit(QTextEdit): hover_enter = Signal(int) hover_leave = Signal(int) focus_in = Signal(int) user_edited = Signal() ensure_visible = Signal() ensure_scene_visible = Signal() redo_signal = Signal() undo_signal = Signal() push_undo_stack = Signal() def __init__(self, idx, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) self.idx = idx Loading @@ -23,15 +33,23 @@ class SourceTextEdit(QTextEdit): self.document().documentLayout().documentSizeChanged.connect(self.adjustSize) self.setAcceptRichText(False) self.setAttribute(Qt.WidgetAttribute.WA_InputMethodEnabled, True) self.old_undo_steps = self.document().availableUndoSteps() self.in_redo_undo = False def adjustSize(self): h = self.document().documentLayout().documentSize().toSize().height() self.setFixedHeight(max(h, 50)) def on_content_changed(self): if self.hasFocus(): if self.hasFocus() and not self.pre_editing: self.user_edited.emit() if not self.in_redo_undo: undo_steps = self.document().availableUndoSteps() if undo_steps != self.old_undo_steps: self.old_undo_steps = undo_steps self.push_undo_stack.emit() def setHoverEffect(self, hover: bool): try: if hover: Loading Loading @@ -72,15 +90,33 @@ class SourceTextEdit(QTextEdit): self.pre_editing = True return super().inputMethodEvent(e) def keyPressEvent(self, e: QKeyEvent) -> None: if e.modifiers() == Qt.KeyboardModifier.ControlModifier: if e.key() == Qt.Key.Key_Z: e.accept() self.undo_signal.emit() return elif e.key() == Qt.Key.Key_Y: e.accept() self.redo_signal.emit() return return super().keyPressEvent(e) def undo(self) -> None: self.in_redo_undo = True self.document().undo() self.in_redo_undo = False self.old_undo_steps = self.document().availableUndoSteps() def redo(self) -> None: self.in_redo_undo = True self.document().redo() self.in_redo_undo = False self.old_undo_steps = self.document().availableUndoSteps() class TransTextEdit(SourceTextEdit): content_change = Signal(int) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.document().contentsChange.connect(self.onContentChange) pass def onContentChange(self, pos: int, delete: int, add: int): if self.hasFocus(): self.content_change.emit(self.idx) class TransPairWidget(Widget): def __init__(self, textblock: TextBlock = None, idx: int = None, *args, **kwargs) -> None: Loading Loading @@ -120,16 +156,11 @@ class TextEditListScrollArea(QScrollArea): def addPairWidget(self, pairwidget: TransPairWidget): self.vlayout.addWidget(pairwidget) pairwidget.setVisible(True) pairwidget.e_trans.ensure_visible.connect(self.on_ensure_visible) pairwidget.e_source.ensure_visible.connect(self.on_ensure_visible) def removeWidget(self, widget: TransPairWidget): widget.setVisible(False) self.vlayout.removeWidget(widget) def on_ensure_visible(self): self.ensureWidgetVisible(self.sender()) class TextPanel(Widget): def __init__(self, app: QApplication, *args, **kwargs) -> None: Loading @@ -144,3 +175,19 @@ class TextPanel(Widget): layout.setSpacing(14) layout.setAlignment(Qt.AlignmentFlag.AlignCenter) class TextEditCommand(QUndoCommand): def __init__(self, edit: Union[SourceTextEdit, TransTextEdit]) -> None: super().__init__() self.edit = edit self.op_counter = -1 def redo(self): self.op_counter += 1 if self.op_counter <= 0: return self.edit.redo() def undo(self): self.edit.undo() ballontranslator/ui/scenetext_manager.py +24 −4 Original line number Diff line number Diff line Loading @@ -10,10 +10,9 @@ try: except: from qtpy.QtGui import QUndoCommand from .imgtranspanel import TransPairWidget from .textitem import TextBlkItem, TextBlock, xywh2xyxypoly from .canvas import Canvas from .imgtranspanel import TextPanel, TransTextEdit from .imgtranspanel import TextPanel, TransTextEdit, SourceTextEdit, TransPairWidget, TextEditCommand from .fontformatpanel import set_textblk_fontsize from .misc import FontFormat, ProgramConfig, pt2px Loading Loading @@ -337,9 +336,19 @@ class SceneTextManager(QObject): self.textEditList.addPairWidget(pair_widget) pair_widget.e_source.setPlainText(blk_item.blk.get_text()) pair_widget.e_source.user_edited.connect(self.on_srcwidget_edited) pair_widget.e_source.ensure_scene_visible.connect(self.on_ensure_textitem_svisible) pair_widget.e_source.push_undo_stack.connect(self.on_push_edit_stack) pair_widget.e_source.redo_signal.connect(self.canvasUndoStack.redo) pair_widget.e_source.undo_signal.connect(self.canvasUndoStack.undo) pair_widget.e_trans.setPlainText(blk_item.toPlainText()) pair_widget.e_trans.focus_in.connect(self.onTransWidgetHoverEnter) pair_widget.e_trans.content_change.connect(self.onTransWidgetContentchange) pair_widget.e_trans.user_edited.connect(self.onTransWidgetUserEdited) pair_widget.e_trans.ensure_scene_visible.connect(self.on_ensure_textitem_svisible) pair_widget.e_trans.push_undo_stack.connect(self.on_push_edit_stack) pair_widget.e_trans.redo_signal.connect(self.canvasUndoStack.redo) pair_widget.e_trans.undo_signal.connect(self.canvasUndoStack.undo) self.new_textblk.emit(blk_item.idx) return blk_item Loading Loading @@ -700,7 +709,9 @@ class SceneTextManager(QObject): self.canvas.gv.ensureVisible(blk_item) self.txtblkShapeControl.setBlkItem(blk_item) def onTransWidgetContentchange(self, idx: int): def onTransWidgetUserEdited(self): edit: TransTextEdit = self.sender() idx = edit.idx blk_item = self.textblk_item_list[idx] blk_item.setTextInteractionFlags(Qt.NoTextInteraction) blk_item.setPlainText(self.pairwidget_list[idx].e_trans.toPlainText()) Loading Loading @@ -771,6 +782,15 @@ class SceneTextManager(QObject): for blk_item in blk_items: blk_item.setSelected(selected) def on_ensure_textitem_svisible(self): edit: Union[TransTextEdit, SourceTextEdit] = self.sender() self.textEditList.ensureWidgetVisible(edit) self.canvas.gv.ensureVisible(self.textblk_item_list[edit.idx]) self.txtblkShapeControl.setBlkItem(self.textblk_item_list[edit.idx]) def on_push_edit_stack(self): self.canvasUndoStack.push(TextEditCommand(self.sender())) def get_text_size(fm: QFontMetrics, text: str) -> Tuple[int, int]: brt = fm.tightBoundingRect(text) Loading ballontranslator/ui/search_replace_widgets.py +113 −34 Original line number Diff line number Diff line from qtpy.QtWidgets import QHBoxLayout, QComboBox, QTextEdit, QLabel, QTreeView, QPlainTextEdit, QCheckBox, QMessageBox, QVBoxLayout, QStyle, QSlider, QProxyStyle, QStyle, QGraphicsDropShadowEffect, QWidget from qtpy.QtCore import Qt, QTimer, QEasingCurve, QPointF, QRect, Signal from qtpy.QtCore import Qt, QTimer, QPointF, QRect, Signal from qtpy.QtGui import QKeyEvent, QTextDocument, QTextCursor, QHideEvent, QInputMethodEvent, QFontMetrics, QColor, QShowEvent try: from qtpy.QtWidgets import QUndoCommand except: from qtpy.QtGui import QUndoCommand from typing import List, Union, Tuple, Dict from .misc import ProgramConfig Loading @@ -11,6 +16,7 @@ from .imgtranspanel import TransPairWidget, SourceTextEdit, TransTextEdit HIGHLIGHT_COLOR = QColor(30, 147, 229, 60) CURRENT_TEXT_COLOR = QColor(244, 249, 28) class SearchEditor(QPlainTextEdit): height_changed = Signal() commit = Signal() Loading Loading @@ -84,8 +90,10 @@ class SearchEditor(QPlainTextEdit): class SearchWidget(Widget): search = Signal() replace = Signal() reinit = Signal() replace_one = Signal() replace_all = Signal() def __init__(self, parent: QWidget = None, is_floating=True, *args, **kwargs) -> None: super().__init__(parent, *args, **kwargs) Loading Loading @@ -198,8 +206,8 @@ class SearchWidget(Widget): self.hide() def hideEvent(self, e: QHideEvent) -> None: self.clean_highted() self.clearSearchResult() self.clean_highlighted() # self.clearSearchResult() return super().hideEvent(e) def showEvent(self, e: QShowEvent) -> None: Loading @@ -224,11 +232,11 @@ class SearchWidget(Widget): self.replace_all_btn.setVisible(visible) self.replace_btn.setVisible(visible) def clean_highted(self): def clean_highlighted(self): for e in self.search_rstedit_list: self.clean_editor_highted(e) self.clean_editor_highlighted(e) def clean_editor_highted(self, e: QTextEdit): def clean_editor_highlighted(self, e: QTextEdit): e.blockSignals(True) e.textCursor().beginEditBlock() cursor = QTextCursor(e.document()) Loading @@ -255,7 +263,7 @@ class SearchWidget(Widget): idx = self.get_result_edit_index(edit) if idx < 0: return self.clean_editor_highted(edit) self.clean_editor_highlighted(edit) counter, pos_map = self._find_page_text(edit, self.search_editor.toPlainText(), self.get_find_flag()) delta_count = counter - self.search_counter_list[idx] self.counter_sum += delta_count Loading Loading @@ -291,8 +299,9 @@ class SearchWidget(Widget): self.result_pos += delta_count else: edit = self.search_rstedit_list.pop(idx) edit.textChanged.disconnect(self.on_rst_text_changed) self.search_counter_list.pop(idx) self.search_cursorpos_map.pop(idx) edit.textChanged.disconnect(self.on_rst_text_changed) if len(self.search_rstedit_list) == 0: self.clearSearchResult() elif self.current_edit is not None: Loading @@ -311,7 +320,7 @@ class SearchWidget(Widget): def page_search(self, update_cursor=True): self.clean_highted() self.clean_highlighted() self.clearSearchResult() if not self.isVisible(): Loading @@ -319,6 +328,7 @@ class SearchWidget(Widget): text = self.search_editor.toPlainText() if text == '': self.updateCounterText() return search_range = self.range_combobox.currentIndex() Loading Loading @@ -370,7 +380,7 @@ class SearchWidget(Widget): break pos_map[cursor.position()] = found_counter found_counter += 1 if highlight: if highlight and self.isVisible(): cf = cursor.charFormat() cf.setBackground(HIGHLIGHT_COLOR) cursor.mergeCharFormat(cf) Loading Loading @@ -445,7 +455,7 @@ class SearchWidget(Widget): c_pos = cursor.position() if c_pos not in pos_map: find_flag |= QTextDocument.FindFlag.FindBackward for k, val in reversed(pos_map.items()): for k in reversed(pos_map): if k < c_pos: text = self.search_editor.toPlainText() cursor.setPosition(k-len(text)) Loading @@ -465,6 +475,7 @@ class SearchWidget(Widget): old_edit = self.current_edit doc = self.current_edit.document() text = self.search_editor.toPlainText() cursor_reset = 0 find_flag = self.get_find_flag() len_text = len(text) Loading @@ -478,13 +489,20 @@ class SearchWidget(Widget): new_cursor: QTextCursor = doc.find(text, self.current_cursor, find_flag) if new_cursor.isNull(): idx = self.current_edit_index() + step if idx >= len(self.search_rstedit_list) or idx < 0: return step # return step value if next move will be out of page # return step value if next move will be out of page num_rstedit = len(self.search_rstedit_list) if idx >= num_rstedit: cursor_reset = step idx = 0 elif idx < 0: cursor_reset = step idx = num_rstedit - 1 self.current_edit = self.search_rstedit_list[idx] self.updateCurrentCursor(intro_cursor=True, backward=step < 0) else: self.current_cursor = new_cursor if self.isVisible(): old_edit.blockSignals(True) cf = old_cursor.charFormat() cf.setBackground(HIGHLIGHT_COLOR) Loading @@ -492,7 +510,7 @@ class SearchWidget(Widget): old_edit.blockSignals(False) self.highlight_current_text() return 0 return cursor_reset def highlight_current_text(self): if self.current_edit is None or not self.current_cursor.hasSelection(): Loading @@ -501,15 +519,17 @@ class SearchWidget(Widget): cursor = self.current_edit.textCursor() if cursor.hasSelection(): cursor.clearSelection() cursor.setPosition(self.current_cursor.position()) self.current_edit.setTextCursor(cursor) cursor.setPosition(self.current_cursor.position()) if self.isVisible(): cf = self.current_cursor.charFormat() cf.setBackground(CURRENT_TEXT_COLOR) self.current_cursor.setCharFormat(cf) self.current_edit.blockSignals(False) self.current_edit.setFocus() self.current_edit.ensure_visible.emit() self.current_edit.ensure_scene_visible.emit() else: self.current_edit.blockSignals(False) def on_next_search_result(self): if self.current_cursor is None: Loading @@ -517,6 +537,8 @@ class SearchWidget(Widget): move = self.move_cursor(1) if move == 0: self.result_pos = min(self.result_pos + 1, self.counter_sum - 1) else: self.result_pos = 0 self.updateCounterText() def on_prev_search_result(self): Loading @@ -525,6 +547,8 @@ class SearchWidget(Widget): move = self.move_cursor(-1) if move == 0: self.result_pos = max(self.result_pos - 1, 0) else: self.result_pos = self.counter_sum - 1 self.updateCounterText() def on_whole_word_clicked(self): Loading @@ -547,12 +571,14 @@ class SearchWidget(Widget): def on_commit_search(self): self.page_search() self.highlight_current_text() def on_replaceall_btn_clicked(self): pass def on_replace_btn_clicked(self): pass if self.current_cursor is not None: self.replace_one.emit() def on_new_textblk(self, idx: int): if self.isVisible(): Loading Loading @@ -581,12 +607,65 @@ class SearchWidget(Widget): elif e.idx == edit.idx: if type(edit) == TransTextEdit: insert_idx += 1 if current_idx >= insert_idx: self.result_pos += found_counter self.search_cursorpos_map.insert(insert_idx, pos_map) edit.textChanged.connect(self.on_rst_text_changed) self.search_counter_list.insert(insert_idx, found_counter) self.search_rstedit_list.insert(insert_idx, edit) self.counter_sum += found_counter if current_idx != -1 and current_idx >= insert_idx: self.result_pos += found_counter else: self.result_pos = 0 self.setCurrentEditor(edit) self.updateCounterText() class ReplaceOneCommand(QUndoCommand): def __init__(self, se: SearchWidget, parent=None): super(ReplaceOneCommand, self).__init__(parent) self.sw = se self.reptxt = self.sw.replace_editor.toPlainText() self.repl_len = len(self.reptxt) self.rep_cursor = QTextCursor(self.sw.current_edit.document()) self.sel_start = self.sw.current_cursor.selectionStart() self.oritxt = self.sw.current_cursor.selectedText() self.ori_len = len(self.oritxt) self.edit: Union[SourceTextEdit, TransTextEdit] = self.sw.current_edit self.edit_is_src = type(self.edit) == SourceTextEdit def redo(self): if self.sw.current_edit is not None \ and self.sw.search_editor.toPlainText() == self.oritxt: move = self.sw.move_cursor(1) if move == 0: self.sw.result_pos = min(self.sw.counter_sum - 1, self.sw.result_pos + 1) else: self.sw.result_pos = 0 self.rep_cursor.setPosition(self.sel_start) self.rep_cursor.setPosition(self.sel_start+self.ori_len, QTextCursor.MoveMode.KeepAnchor) self.rep_cursor.insertText(self.reptxt) self.edit.user_edited.emit() def undo(self): self.rep_cursor.setPosition(self.sel_start) self.rep_cursor.setPosition(self.sel_start+self.repl_len, QTextCursor.MoveMode.KeepAnchor) self.rep_cursor.insertText(self.oritxt) if self.sw.current_edit is not None \ and self.sw.search_editor.toPlainText() == self.oritxt: move = self.sw.move_cursor(-1) if move == 0: self.sw.result_pos = max(self.sw.result_pos - 1, 0) else: self.sw.result_pos = self.sw.counter_sum - 1 self.sw.updateCounterText() self.edit.user_edited.emit() class ReplaceAllCommand(QUndoCommand): pass No newline at end of file Loading
ballontranslator/ui/canvas.py +11 −2 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ from .textitem import TextBlkItem, TextBlock from .texteditshapecontrol import TextBlkShapeControl from .stylewidgets import FadeLabel from .image_edit import ImageEditMode, DrawingLayer, StrokeImgItem from .search_replace_widgets import SearchWidget from .search_replace_widgets import SearchWidget, ReplaceOneCommand, ReplaceAllCommand from . import constants as C CANVAS_SCALE_MAX = 3.0 Loading Loading @@ -123,6 +123,8 @@ class Canvas(QGraphicsScene): self.gv.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) self.search_widget = SearchWidget(self.gv, is_floating=True) self.search_widget.replace_one.connect(self.on_search_replace_one) self.search_widget.replace_all.connect(self.on_search_replace_all) self.ctrl_relesed = self.gv.ctrl_released self.vscroll_bar = self.gv.verticalScrollBar() Loading Loading @@ -506,3 +508,10 @@ class Canvas(QGraphicsScene): item.setParentItem(None) self.stroke_img_item = None self.erase_img_key = None def on_search_replace_one(self): self.undoStack.push(ReplaceOneCommand(self.search_widget)) pass def on_search_replace_all(self): pass No newline at end of file
ballontranslator/ui/image_edit.py +0 −5 Original line number Diff line number Diff line Loading @@ -5,11 +5,6 @@ from qtpy.QtCore import QRectF, Qt, QPointF, QSize, QPoint, QDateTime from qtpy.QtWidgets import QStyleOptionGraphicsItem, QGraphicsPixmapItem, QWidget, QGraphicsPathItem, QGraphicsItem from qtpy.QtGui import QPen, QColor, QPainterPath, QCursor, QPainter, QPixmap, QImage, QBrush try: from qtpy.QtWidgets import QUndoCommand except: from qtpy.QtGui import QUndoCommand from .misc import DrawPanelConfig, pixmap2ndarray, ndarray2pixmap from utils.io_utils import imread, imwrite Loading
ballontranslator/ui/imgtranspanel.py +64 −17 Original line number Diff line number Diff line from typing import List from typing import List, Union from qtpy.QtWidgets import QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QFrame, QApplication from qtpy.QtCore import Signal, Qt, QSize, QEvent from qtpy.QtGui import QColor, QFocusEvent, QInputMethodEvent from .stylewidgets import Widget, SeparatorWidget from qtpy.QtGui import QColor, QFocusEvent, QInputMethodEvent, QKeyEvent try: from qtpy.QtWidgets import QUndoCommand except: from qtpy.QtGui import QUndoCommand from .stylewidgets import Widget, SeparatorWidget from .textitem import TextBlock, TextBlkItem from .fontformatpanel import FontFormatPanel class SourceTextEdit(QTextEdit): hover_enter = Signal(int) hover_leave = Signal(int) focus_in = Signal(int) user_edited = Signal() ensure_visible = Signal() ensure_scene_visible = Signal() redo_signal = Signal() undo_signal = Signal() push_undo_stack = Signal() def __init__(self, idx, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) self.idx = idx Loading @@ -23,15 +33,23 @@ class SourceTextEdit(QTextEdit): self.document().documentLayout().documentSizeChanged.connect(self.adjustSize) self.setAcceptRichText(False) self.setAttribute(Qt.WidgetAttribute.WA_InputMethodEnabled, True) self.old_undo_steps = self.document().availableUndoSteps() self.in_redo_undo = False def adjustSize(self): h = self.document().documentLayout().documentSize().toSize().height() self.setFixedHeight(max(h, 50)) def on_content_changed(self): if self.hasFocus(): if self.hasFocus() and not self.pre_editing: self.user_edited.emit() if not self.in_redo_undo: undo_steps = self.document().availableUndoSteps() if undo_steps != self.old_undo_steps: self.old_undo_steps = undo_steps self.push_undo_stack.emit() def setHoverEffect(self, hover: bool): try: if hover: Loading Loading @@ -72,15 +90,33 @@ class SourceTextEdit(QTextEdit): self.pre_editing = True return super().inputMethodEvent(e) def keyPressEvent(self, e: QKeyEvent) -> None: if e.modifiers() == Qt.KeyboardModifier.ControlModifier: if e.key() == Qt.Key.Key_Z: e.accept() self.undo_signal.emit() return elif e.key() == Qt.Key.Key_Y: e.accept() self.redo_signal.emit() return return super().keyPressEvent(e) def undo(self) -> None: self.in_redo_undo = True self.document().undo() self.in_redo_undo = False self.old_undo_steps = self.document().availableUndoSteps() def redo(self) -> None: self.in_redo_undo = True self.document().redo() self.in_redo_undo = False self.old_undo_steps = self.document().availableUndoSteps() class TransTextEdit(SourceTextEdit): content_change = Signal(int) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.document().contentsChange.connect(self.onContentChange) pass def onContentChange(self, pos: int, delete: int, add: int): if self.hasFocus(): self.content_change.emit(self.idx) class TransPairWidget(Widget): def __init__(self, textblock: TextBlock = None, idx: int = None, *args, **kwargs) -> None: Loading Loading @@ -120,16 +156,11 @@ class TextEditListScrollArea(QScrollArea): def addPairWidget(self, pairwidget: TransPairWidget): self.vlayout.addWidget(pairwidget) pairwidget.setVisible(True) pairwidget.e_trans.ensure_visible.connect(self.on_ensure_visible) pairwidget.e_source.ensure_visible.connect(self.on_ensure_visible) def removeWidget(self, widget: TransPairWidget): widget.setVisible(False) self.vlayout.removeWidget(widget) def on_ensure_visible(self): self.ensureWidgetVisible(self.sender()) class TextPanel(Widget): def __init__(self, app: QApplication, *args, **kwargs) -> None: Loading @@ -144,3 +175,19 @@ class TextPanel(Widget): layout.setSpacing(14) layout.setAlignment(Qt.AlignmentFlag.AlignCenter) class TextEditCommand(QUndoCommand): def __init__(self, edit: Union[SourceTextEdit, TransTextEdit]) -> None: super().__init__() self.edit = edit self.op_counter = -1 def redo(self): self.op_counter += 1 if self.op_counter <= 0: return self.edit.redo() def undo(self): self.edit.undo()
ballontranslator/ui/scenetext_manager.py +24 −4 Original line number Diff line number Diff line Loading @@ -10,10 +10,9 @@ try: except: from qtpy.QtGui import QUndoCommand from .imgtranspanel import TransPairWidget from .textitem import TextBlkItem, TextBlock, xywh2xyxypoly from .canvas import Canvas from .imgtranspanel import TextPanel, TransTextEdit from .imgtranspanel import TextPanel, TransTextEdit, SourceTextEdit, TransPairWidget, TextEditCommand from .fontformatpanel import set_textblk_fontsize from .misc import FontFormat, ProgramConfig, pt2px Loading Loading @@ -337,9 +336,19 @@ class SceneTextManager(QObject): self.textEditList.addPairWidget(pair_widget) pair_widget.e_source.setPlainText(blk_item.blk.get_text()) pair_widget.e_source.user_edited.connect(self.on_srcwidget_edited) pair_widget.e_source.ensure_scene_visible.connect(self.on_ensure_textitem_svisible) pair_widget.e_source.push_undo_stack.connect(self.on_push_edit_stack) pair_widget.e_source.redo_signal.connect(self.canvasUndoStack.redo) pair_widget.e_source.undo_signal.connect(self.canvasUndoStack.undo) pair_widget.e_trans.setPlainText(blk_item.toPlainText()) pair_widget.e_trans.focus_in.connect(self.onTransWidgetHoverEnter) pair_widget.e_trans.content_change.connect(self.onTransWidgetContentchange) pair_widget.e_trans.user_edited.connect(self.onTransWidgetUserEdited) pair_widget.e_trans.ensure_scene_visible.connect(self.on_ensure_textitem_svisible) pair_widget.e_trans.push_undo_stack.connect(self.on_push_edit_stack) pair_widget.e_trans.redo_signal.connect(self.canvasUndoStack.redo) pair_widget.e_trans.undo_signal.connect(self.canvasUndoStack.undo) self.new_textblk.emit(blk_item.idx) return blk_item Loading Loading @@ -700,7 +709,9 @@ class SceneTextManager(QObject): self.canvas.gv.ensureVisible(blk_item) self.txtblkShapeControl.setBlkItem(blk_item) def onTransWidgetContentchange(self, idx: int): def onTransWidgetUserEdited(self): edit: TransTextEdit = self.sender() idx = edit.idx blk_item = self.textblk_item_list[idx] blk_item.setTextInteractionFlags(Qt.NoTextInteraction) blk_item.setPlainText(self.pairwidget_list[idx].e_trans.toPlainText()) Loading Loading @@ -771,6 +782,15 @@ class SceneTextManager(QObject): for blk_item in blk_items: blk_item.setSelected(selected) def on_ensure_textitem_svisible(self): edit: Union[TransTextEdit, SourceTextEdit] = self.sender() self.textEditList.ensureWidgetVisible(edit) self.canvas.gv.ensureVisible(self.textblk_item_list[edit.idx]) self.txtblkShapeControl.setBlkItem(self.textblk_item_list[edit.idx]) def on_push_edit_stack(self): self.canvasUndoStack.push(TextEditCommand(self.sender())) def get_text_size(fm: QFontMetrics, text: str) -> Tuple[int, int]: brt = fm.tightBoundingRect(text) Loading
ballontranslator/ui/search_replace_widgets.py +113 −34 Original line number Diff line number Diff line from qtpy.QtWidgets import QHBoxLayout, QComboBox, QTextEdit, QLabel, QTreeView, QPlainTextEdit, QCheckBox, QMessageBox, QVBoxLayout, QStyle, QSlider, QProxyStyle, QStyle, QGraphicsDropShadowEffect, QWidget from qtpy.QtCore import Qt, QTimer, QEasingCurve, QPointF, QRect, Signal from qtpy.QtCore import Qt, QTimer, QPointF, QRect, Signal from qtpy.QtGui import QKeyEvent, QTextDocument, QTextCursor, QHideEvent, QInputMethodEvent, QFontMetrics, QColor, QShowEvent try: from qtpy.QtWidgets import QUndoCommand except: from qtpy.QtGui import QUndoCommand from typing import List, Union, Tuple, Dict from .misc import ProgramConfig Loading @@ -11,6 +16,7 @@ from .imgtranspanel import TransPairWidget, SourceTextEdit, TransTextEdit HIGHLIGHT_COLOR = QColor(30, 147, 229, 60) CURRENT_TEXT_COLOR = QColor(244, 249, 28) class SearchEditor(QPlainTextEdit): height_changed = Signal() commit = Signal() Loading Loading @@ -84,8 +90,10 @@ class SearchEditor(QPlainTextEdit): class SearchWidget(Widget): search = Signal() replace = Signal() reinit = Signal() replace_one = Signal() replace_all = Signal() def __init__(self, parent: QWidget = None, is_floating=True, *args, **kwargs) -> None: super().__init__(parent, *args, **kwargs) Loading Loading @@ -198,8 +206,8 @@ class SearchWidget(Widget): self.hide() def hideEvent(self, e: QHideEvent) -> None: self.clean_highted() self.clearSearchResult() self.clean_highlighted() # self.clearSearchResult() return super().hideEvent(e) def showEvent(self, e: QShowEvent) -> None: Loading @@ -224,11 +232,11 @@ class SearchWidget(Widget): self.replace_all_btn.setVisible(visible) self.replace_btn.setVisible(visible) def clean_highted(self): def clean_highlighted(self): for e in self.search_rstedit_list: self.clean_editor_highted(e) self.clean_editor_highlighted(e) def clean_editor_highted(self, e: QTextEdit): def clean_editor_highlighted(self, e: QTextEdit): e.blockSignals(True) e.textCursor().beginEditBlock() cursor = QTextCursor(e.document()) Loading @@ -255,7 +263,7 @@ class SearchWidget(Widget): idx = self.get_result_edit_index(edit) if idx < 0: return self.clean_editor_highted(edit) self.clean_editor_highlighted(edit) counter, pos_map = self._find_page_text(edit, self.search_editor.toPlainText(), self.get_find_flag()) delta_count = counter - self.search_counter_list[idx] self.counter_sum += delta_count Loading Loading @@ -291,8 +299,9 @@ class SearchWidget(Widget): self.result_pos += delta_count else: edit = self.search_rstedit_list.pop(idx) edit.textChanged.disconnect(self.on_rst_text_changed) self.search_counter_list.pop(idx) self.search_cursorpos_map.pop(idx) edit.textChanged.disconnect(self.on_rst_text_changed) if len(self.search_rstedit_list) == 0: self.clearSearchResult() elif self.current_edit is not None: Loading @@ -311,7 +320,7 @@ class SearchWidget(Widget): def page_search(self, update_cursor=True): self.clean_highted() self.clean_highlighted() self.clearSearchResult() if not self.isVisible(): Loading @@ -319,6 +328,7 @@ class SearchWidget(Widget): text = self.search_editor.toPlainText() if text == '': self.updateCounterText() return search_range = self.range_combobox.currentIndex() Loading Loading @@ -370,7 +380,7 @@ class SearchWidget(Widget): break pos_map[cursor.position()] = found_counter found_counter += 1 if highlight: if highlight and self.isVisible(): cf = cursor.charFormat() cf.setBackground(HIGHLIGHT_COLOR) cursor.mergeCharFormat(cf) Loading Loading @@ -445,7 +455,7 @@ class SearchWidget(Widget): c_pos = cursor.position() if c_pos not in pos_map: find_flag |= QTextDocument.FindFlag.FindBackward for k, val in reversed(pos_map.items()): for k in reversed(pos_map): if k < c_pos: text = self.search_editor.toPlainText() cursor.setPosition(k-len(text)) Loading @@ -465,6 +475,7 @@ class SearchWidget(Widget): old_edit = self.current_edit doc = self.current_edit.document() text = self.search_editor.toPlainText() cursor_reset = 0 find_flag = self.get_find_flag() len_text = len(text) Loading @@ -478,13 +489,20 @@ class SearchWidget(Widget): new_cursor: QTextCursor = doc.find(text, self.current_cursor, find_flag) if new_cursor.isNull(): idx = self.current_edit_index() + step if idx >= len(self.search_rstedit_list) or idx < 0: return step # return step value if next move will be out of page # return step value if next move will be out of page num_rstedit = len(self.search_rstedit_list) if idx >= num_rstedit: cursor_reset = step idx = 0 elif idx < 0: cursor_reset = step idx = num_rstedit - 1 self.current_edit = self.search_rstedit_list[idx] self.updateCurrentCursor(intro_cursor=True, backward=step < 0) else: self.current_cursor = new_cursor if self.isVisible(): old_edit.blockSignals(True) cf = old_cursor.charFormat() cf.setBackground(HIGHLIGHT_COLOR) Loading @@ -492,7 +510,7 @@ class SearchWidget(Widget): old_edit.blockSignals(False) self.highlight_current_text() return 0 return cursor_reset def highlight_current_text(self): if self.current_edit is None or not self.current_cursor.hasSelection(): Loading @@ -501,15 +519,17 @@ class SearchWidget(Widget): cursor = self.current_edit.textCursor() if cursor.hasSelection(): cursor.clearSelection() cursor.setPosition(self.current_cursor.position()) self.current_edit.setTextCursor(cursor) cursor.setPosition(self.current_cursor.position()) if self.isVisible(): cf = self.current_cursor.charFormat() cf.setBackground(CURRENT_TEXT_COLOR) self.current_cursor.setCharFormat(cf) self.current_edit.blockSignals(False) self.current_edit.setFocus() self.current_edit.ensure_visible.emit() self.current_edit.ensure_scene_visible.emit() else: self.current_edit.blockSignals(False) def on_next_search_result(self): if self.current_cursor is None: Loading @@ -517,6 +537,8 @@ class SearchWidget(Widget): move = self.move_cursor(1) if move == 0: self.result_pos = min(self.result_pos + 1, self.counter_sum - 1) else: self.result_pos = 0 self.updateCounterText() def on_prev_search_result(self): Loading @@ -525,6 +547,8 @@ class SearchWidget(Widget): move = self.move_cursor(-1) if move == 0: self.result_pos = max(self.result_pos - 1, 0) else: self.result_pos = self.counter_sum - 1 self.updateCounterText() def on_whole_word_clicked(self): Loading @@ -547,12 +571,14 @@ class SearchWidget(Widget): def on_commit_search(self): self.page_search() self.highlight_current_text() def on_replaceall_btn_clicked(self): pass def on_replace_btn_clicked(self): pass if self.current_cursor is not None: self.replace_one.emit() def on_new_textblk(self, idx: int): if self.isVisible(): Loading Loading @@ -581,12 +607,65 @@ class SearchWidget(Widget): elif e.idx == edit.idx: if type(edit) == TransTextEdit: insert_idx += 1 if current_idx >= insert_idx: self.result_pos += found_counter self.search_cursorpos_map.insert(insert_idx, pos_map) edit.textChanged.connect(self.on_rst_text_changed) self.search_counter_list.insert(insert_idx, found_counter) self.search_rstedit_list.insert(insert_idx, edit) self.counter_sum += found_counter if current_idx != -1 and current_idx >= insert_idx: self.result_pos += found_counter else: self.result_pos = 0 self.setCurrentEditor(edit) self.updateCounterText() class ReplaceOneCommand(QUndoCommand): def __init__(self, se: SearchWidget, parent=None): super(ReplaceOneCommand, self).__init__(parent) self.sw = se self.reptxt = self.sw.replace_editor.toPlainText() self.repl_len = len(self.reptxt) self.rep_cursor = QTextCursor(self.sw.current_edit.document()) self.sel_start = self.sw.current_cursor.selectionStart() self.oritxt = self.sw.current_cursor.selectedText() self.ori_len = len(self.oritxt) self.edit: Union[SourceTextEdit, TransTextEdit] = self.sw.current_edit self.edit_is_src = type(self.edit) == SourceTextEdit def redo(self): if self.sw.current_edit is not None \ and self.sw.search_editor.toPlainText() == self.oritxt: move = self.sw.move_cursor(1) if move == 0: self.sw.result_pos = min(self.sw.counter_sum - 1, self.sw.result_pos + 1) else: self.sw.result_pos = 0 self.rep_cursor.setPosition(self.sel_start) self.rep_cursor.setPosition(self.sel_start+self.ori_len, QTextCursor.MoveMode.KeepAnchor) self.rep_cursor.insertText(self.reptxt) self.edit.user_edited.emit() def undo(self): self.rep_cursor.setPosition(self.sel_start) self.rep_cursor.setPosition(self.sel_start+self.repl_len, QTextCursor.MoveMode.KeepAnchor) self.rep_cursor.insertText(self.oritxt) if self.sw.current_edit is not None \ and self.sw.search_editor.toPlainText() == self.oritxt: move = self.sw.move_cursor(-1) if move == 0: self.sw.result_pos = max(self.sw.result_pos - 1, 0) else: self.sw.result_pos = self.sw.counter_sum - 1 self.sw.updateCounterText() self.edit.user_edited.emit() class ReplaceAllCommand(QUndoCommand): pass No newline at end of file