Commit dd196b18 authored by dmMaze's avatar dmMaze
Browse files

merge textedit undostack into canvas

parent e1499632
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -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
@@ -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()
@@ -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
+0 −5
Original line number Diff line number Diff line
@@ -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

+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
@@ -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:
@@ -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:
@@ -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:
@@ -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()
+24 −4
Original line number Diff line number Diff line
@@ -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

@@ -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

@@ -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())
@@ -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)
+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
@@ -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()
@@ -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)
@@ -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:
@@ -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())
@@ -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
@@ -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:
@@ -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():
@@ -319,6 +328,7 @@ class SearchWidget(Widget):

        text = self.search_editor.toPlainText()
        if text == '':
            self.updateCounterText()
            return

        search_range = self.range_combobox.currentIndex()
@@ -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)
@@ -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))
@@ -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)
@@ -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)
@@ -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():
@@ -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:
@@ -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):
@@ -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):
@@ -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():
@@ -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