Loading ballontranslator/ui/fontformatpanel.py +2 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ def restore_textcursor(formatting_func): def wrapper(blkitem: TextBlkItem, *args, **kwargs): if blkitem is None: return blkitem.is_formatting = True cursor = blkitem.textCursor() set_all = not cursor.hasSelection() pos1 = cursor.position() Loading @@ -38,6 +39,7 @@ def restore_textcursor(formatting_func): cursor.setPosition(pos1) blkitem.setTextCursor(cursor) blkitem.repaint_background() blkitem.is_formatting = False return wrapper @restore_textcursor Loading ballontranslator/ui/mainwindow.py +1 −0 Original line number Diff line number Diff line Loading @@ -308,6 +308,7 @@ class MainWindow(QMainWindow): self.leftStackWidget.hide() def closeEvent(self, event: QCloseEvent) -> None: self.st_manager.hovering_transwidget = None self.canvas.disconnect() self.canvas.undoStack.disconnect() self.save_config() Loading ballontranslator/ui/scenetext_manager.py +104 −31 Original line number Diff line number Diff line Loading @@ -15,18 +15,19 @@ from .canvas import Canvas from .textedit_area import TextPanel, TransTextEdit, SourceTextEdit, TransPairWidget, TextEditCommand from .fontformatpanel import set_textblk_fontsize from .misc import FontFormat, ProgramConfig, pt2px from .page_search_widget import PageSearchWidget from .texteditshapecontrol import TextBlkShapeControl from utils.imgproc_utils import extract_ballon_region from utils.text_processing import seg_text, is_cjk from utils.text_layout import layout_text class MoveBlkItemsCommand(QUndoCommand): def __init__(self, items: List[TextBlkItem], parent=None): def __init__(self, items: List[TextBlkItem], shape_ctrl: TextBlkShapeControl): super(MoveBlkItemsCommand, self).__init__() self.items = items self.old_pos_lst = [] self.new_pos_lst = [] self.shape_ctrl = shape_ctrl for item in items: self.old_pos_lst.append(item.oldPos) self.new_pos_lst.append(item.pos()) Loading @@ -35,10 +36,14 @@ class MoveBlkItemsCommand(QUndoCommand): def redo(self): for item, new_pos in zip(self.items, self.new_pos_lst): item.setPos(new_pos) if self.shape_ctrl.blk_item == item and self.shape_ctrl.pos() != new_pos: self.shape_ctrl.setPos(new_pos) def undo(self): for item, old_pos in zip(self.items, self.old_pos_lst): item.setPos(old_pos) if self.shape_ctrl.blk_item == item and self.shape_ctrl.pos() != old_pos: self.shape_ctrl.setPos(old_pos) def mergeWith(self, command: QUndoCommand): if command.old_pos_lst == self.old_pos_lst: Loading Loading @@ -116,19 +121,24 @@ class ReshapeItemCommand(QUndoCommand): class RotateItemCommand(QUndoCommand): def __init__(self, item: TextBlkItem, new_angle: float): def __init__(self, item: TextBlkItem, new_angle: float, shape_ctrl: TextBlkShapeControl): super(RotateItemCommand, self).__init__() self.item = item self.old_angle = item.rotation() self.new_angle = new_angle self.shape_ctrl = shape_ctrl def redo(self): self.item.setRotation(self.new_angle) self.item.blk.angle = self.new_angle if self.shape_ctrl.blk_item == self.item and self.shape_ctrl.rotation() != self.new_angle: self.shape_ctrl.setRotation(self.new_angle) def undo(self): self.item.setRotation(self.old_angle) self.item.blk.angle = self.old_angle if self.shape_ctrl.blk_item == self.item and self.shape_ctrl.rotation() != self.old_angle: self.shape_ctrl.setRotation(self.old_angle) def mergeWith(self, command: QUndoCommand): item = command.item Loading Loading @@ -317,6 +327,34 @@ class AutoLayoutCommand(QUndoCommand): item.setLetterSpacing(item.letter_spacing, force=True) class TextItemEditCommand(QUndoCommand): def __init__(self, blkitem: TextBlkItem, trans_edit: TransTextEdit, num_steps: int): super(TextItemEditCommand, self).__init__() self.op_counter = 0 self.edit = trans_edit self.blkitem = blkitem self.num_steps = min(num_steps, 2) if blkitem.input_method_from == -1: self.num_steps = 1 else: blkitem.input_method_from = -1 def redo(self): if self.op_counter == 0: self.op_counter += 1 return for _ in range(self.num_steps): self.blkitem.redo() if self.edit is not None: self.edit.redo() def undo(self): for _ in range(self.num_steps): self.blkitem.undo() if self.edit is not None: self.edit.undo() class SceneTextManager(QObject): new_textblk = Signal(int) def __init__(self, Loading Loading @@ -369,6 +407,7 @@ class SceneTextManager(QObject): self.txtblkShapeControl.updateBoundingRect() def clearSceneTextitems(self): self.hovering_transwidget = None self.txtblkShapeControl.setBlkItem(None) for blkitem in self.textblk_item_list: self.canvas.removeItem(blkitem) Loading Loading @@ -411,16 +450,16 @@ class SceneTextManager(QObject): 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_source.redo_signal.connect(self.on_textedit_redo) pair_widget.e_source.undo_signal.connect(self.on_textedit_undo) pair_widget.e_trans.setPlainText(blk_item.toPlainText()) pair_widget.e_trans.focus_in.connect(self.onTransWidgetHoverEnter) pair_widget.e_trans.user_edited_verbose.connect(self.onTransWidgetUserEditedVerbose) 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) pair_widget.e_trans.redo_signal.connect(self.on_textedit_redo) pair_widget.e_trans.undo_signal.connect(self.on_textedit_undo) self.new_textblk.emit(blk_item.idx) return blk_item Loading @@ -436,7 +475,10 @@ class SceneTextManager(QObject): textblk_item.moved.connect(self.onTextBlkItemMoved) textblk_item.reshaped.connect(self.onTextBlkItemReshaped) textblk_item.rotated.connect(self.onTextBlkItemRotated) textblk_item.content_changed.connect(self.onTextBlkItemContentChanged) textblk_item.push_undo_stack.connect(self.on_push_textitem_undostack) textblk_item.undo_signal.connect(self.on_textedit_undo) textblk_item.redo_signal.connect(self.on_textedit_redo) textblk_item.user_edited_verbose.connect(self.onTextItemUserEditedVerbose) textblk_item.doc_size_changed.connect(self.onTextBlkItemSizeChanged) textblk_item.pasted.connect(self.onBlkitemPaste) return textblk_item Loading Loading @@ -471,13 +513,9 @@ class SceneTextManager(QObject): def recoverTextblkItemList(self, blkitem_list: List[TextBlkItem], p_widget_list: List[TransPairWidget]): for blkitem, p_widget in zip(blkitem_list, p_widget_list): self.recoverTextblkItem(blkitem, p_widget) if self.txtblkShapeControl.blk_item is not None and blkitem.isSelected(): blkitem.setSelected(False) def onTextBlkItemContentChanged(self, blk_item: TextBlkItem): if blk_item.hasFocus(): trans_widget = self.pairwidget_list[blk_item.idx].e_trans if not trans_widget.hasFocus(): trans_widget.setText(blk_item.toPlainText()) self.canvas.setProjSaveState(True) def onTextBlkItemSizeChanged(self, idx: int): blk_item = self.textblk_item_list[idx] Loading @@ -497,6 +535,17 @@ class SceneTextManager(QObject): self.canvas.editing_textblkitem = blk_item self.formatpanel.set_textblk_item(blk_item) self.txtblkShapeControl.setCursor(Qt.CursorShape.IBeamCursor) e_trans = self.pairwidget_list[blk_item.idx].e_trans self.changeHoveringWidget(e_trans) def changeHoveringWidget(self, edit: SourceTextEdit): if self.hovering_transwidget != edit: if self.hovering_transwidget is not None: self.hovering_transwidget.setHoverEffect(False) self.hovering_transwidget = edit if edit is not None: self.textEditList.ensureWidgetVisible(edit) edit.setHoverEffect(True) def onLeftbuttonPressed(self, blk_id: int): blk_item = self.textblk_item_list[blk_id] Loading Loading @@ -530,11 +579,7 @@ class SceneTextManager(QObject): blk_item = self.textblk_item_list[blk_id] if not blk_item.hasFocus(): self.txtblkShapeControl.setBlkItem(blk_item) if self.hovering_transwidget is not None: self.hovering_transwidget.setHoverEffect(False) self.hovering_transwidget = self.pairwidget_list[blk_id].e_trans self.hovering_transwidget.setHoverEffect(True) self.textpanel.textEditList.ensureWidgetVisible(self.hovering_transwidget) self.changeHoveringWidget(self.pairwidget_list[blk_id].e_trans) def onTextBlkItemMoving(self, item: TextBlkItem): self.txtblkShapeControl.updateBoundingRect() Loading @@ -542,7 +587,7 @@ class SceneTextManager(QObject): def onTextBlkItemMoved(self): selected_blks = self.get_selected_blkitems() if len(selected_blks) > 0: self.canvasUndoStack.push(MoveBlkItemsCommand(selected_blks, self)) self.canvasUndoStack.push(MoveBlkItemsCommand(selected_blks, self.txtblkShapeControl)) def onTextBlkItemReshaped(self, item: TextBlkItem): self.canvasUndoStack.push(ReshapeItemCommand(item)) Loading @@ -550,7 +595,7 @@ class SceneTextManager(QObject): def onTextBlkItemRotated(self, new_angle: float): blk_item = self.txtblkShapeControl.blk_item if blk_item: self.canvasUndoStack.push(RotateItemCommand(blk_item, new_angle)) self.canvasUndoStack.push(RotateItemCommand(blk_item, new_angle, self.txtblkShapeControl)) def onDeleteBlkItems(self): selected_blks = self.get_selected_blkitems() Loading Loading @@ -784,6 +829,42 @@ class SceneTextManager(QObject): self.canvas.gv.ensureVisible(blk_item) self.txtblkShapeControl.setBlkItem(blk_item) def on_textedit_redo(self): self.canvasUndoStack.redo() def on_textedit_undo(self): self.canvasUndoStack.undo() def on_push_textitem_undostack(self, num_steps: int, is_formatting: bool): blkitem: TextBlkItem = self.sender() e_trans = self.pairwidget_list[blkitem.idx].e_trans if not is_formatting else None self.canvasUndoStack.push(TextItemEditCommand(blkitem, e_trans, num_steps)) def on_push_edit_stack(self, num_steps: int): edit: Union[TransTextEdit, SourceTextEdit] = self.sender() blkitem = self.textblk_item_list[edit.idx] if type(edit) == TransTextEdit else None self.canvasUndoStack.push(TextEditCommand(edit, num_steps, blkitem)) def onTextItemUserEditedVerbose(self, pos: int, added_text: str, input_method_used: bool): blk_item: TextBlkItem = self.sender() idx = blk_item.idx edit = self.pairwidget_list[idx].e_trans ori_count = edit.document().characterCount() new_count = blk_item.document().characterCount() removed = ori_count + len(added_text) - new_count cursor = edit.textCursor() cursor.setPosition(pos) if removed > 0: cursor.setPosition(pos + removed, QTextCursor.MoveMode.KeepAnchor) if input_method_used: cursor.beginEditBlock() cursor.insertText((added_text)) if input_method_used: cursor.endEditBlock() self.canvas.setProjSaveState(True) def onTransWidgetUserEditedVerbose(self, pos: int, added_text: str, input_method_used: bool): edit: TransTextEdit = self.sender() idx = edit.idx Loading @@ -794,7 +875,6 @@ class SceneTextManager(QObject): ori_count = blk_item.document().characterCount() new_count = edit.document().characterCount() removed = ori_count + len(added_text) - new_count # if not input_method_used: cursor = blk_item.textCursor() cursor.setPosition(pos) Loading Loading @@ -859,7 +939,6 @@ class SceneTextManager(QObject): for blk_item, transwidget in zip(self.textblk_item_list, self.pairwidget_list): transwidget.e_trans.setPlainText(blk_item.blk.translation) blk_item.setPlainText(blk_item.blk.translation) # blk_item.update() def showTextblkItemRect(self, draw_rect: bool): for blk_item in self.textblk_item_list: Loading @@ -874,16 +953,10 @@ class SceneTextManager(QObject): def on_ensure_textitem_svisible(self): edit: Union[TransTextEdit, SourceTextEdit] = self.sender() self.textEditList.ensureWidgetVisible(edit) self.changeHoveringWidget(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, num_steps: int): edit: Union[TransTextEdit, SourceTextEdit] = self.sender() blkitem = self.textblk_item_list[edit.idx] self.canvasUndoStack.push(TextEditCommand(self.sender(), blkitem, num_steps)) def get_text_size(fm: QFontMetrics, text: str) -> Tuple[int, int]: brt = fm.tightBoundingRect(text) br = fm.boundingRect(text) Loading ballontranslator/ui/textedit_area.py +13 −11 Original line number Diff line number Diff line Loading @@ -38,7 +38,6 @@ class SourceTextEdit(QTextEdit): self.old_undo_steps = self.document().availableUndoSteps() self.in_redo_undo = False self.change_from: int = 0 self.change_removed: int = 0 self.change_added: int = 0 self.input_method_from = -1 self.input_method_text = '' Loading @@ -55,7 +54,7 @@ class SourceTextEdit(QTextEdit): def on_content_changing(self, from_: int, removed: int, added: int): if not self.pre_editing: self.text_content_changed = True if self.hasFocus() and not self.pre_editing: if self.hasFocus(): self.change_from = from_ self.change_added = added Loading Loading @@ -142,7 +141,6 @@ class SourceTextEdit(QTextEdit): cursor = self.textCursor() self.input_method_from = cursor.selectionStart() self.pre_editing = True self.input_method_event = e super().inputMethodEvent(e) def keyPressEvent(self, e: QKeyEvent) -> None: Loading @@ -169,6 +167,11 @@ class SourceTextEdit(QTextEdit): self.in_redo_undo = False self.old_undo_steps = self.document().availableUndoSteps() def setPlainTextAndKeepUndoStack(self, text: str): cursor = QTextCursor(self.document()) cursor.select(QTextCursor.SelectionType.Document) cursor.insertText(text) class TransTextEdit(SourceTextEdit): pass Loading Loading @@ -233,14 +236,11 @@ class TextPanel(Widget): class TextEditCommand(QUndoCommand): def __init__(self, edit: Union[SourceTextEdit, TransTextEdit], blkitem: TextBlkItem, num_steps: int) -> None: def __init__(self, edit: Union[SourceTextEdit, TransTextEdit], num_steps: int, blkitem: TextBlkItem) -> None: super().__init__() self.edit = edit self.blkitem = blkitem self.op_counter = 0 self.change_from = edit.change_from self.change_added = edit.change_added self.change_removed = edit.change_removed self.num_steps = min(num_steps, 2) if edit.input_method_from == -1: self.num_steps = 1 Loading @@ -254,10 +254,12 @@ class TextEditCommand(QUndoCommand): for _ in range(self.num_steps): self.edit.redo() if self.blkitem is not None: self.blkitem.document().redo() def undo(self): for _ in range(self.num_steps): self.edit.undo() if self.blkitem is not None: self.blkitem.document().undo() ballontranslator/ui/textitem.py +98 −19 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ TEXTRECT_SELECTED_COLOR = QColor(248, 64, 147, 170) class TextBlkItem(QGraphicsTextItem): begin_edit = Signal(int) end_edit = Signal(int) hover_enter = Signal(int) Loading @@ -25,10 +26,14 @@ class TextBlkItem(QGraphicsTextItem): moving = Signal(QGraphicsTextItem) rotated = Signal(float) reshaped = Signal(QGraphicsTextItem) content_changed = Signal(QGraphicsTextItem) leftbutton_pressed = Signal(int) doc_size_changed = Signal(int) pasted = Signal(int) redo_signal = Signal() undo_signal = Signal() push_undo_stack = Signal(int, bool) user_edited_verbose = Signal(int, str, bool) def __init__(self, blk: TextBlock = None, idx: int = 0, set_format=True, show_rect=False, *args, **kwargs): super().__init__(*args, **kwargs) self.pre_editing = False Loading @@ -55,6 +60,14 @@ class TextBlkItem(QGraphicsTextItem): self.oldRect = QRectF() self.repaint_on_changed = True self.is_formatting = False self.old_undo_steps = 0 self.in_redo_undo = False self.change_from: int = 0 self.change_added: int = 0 self.input_method_from = -1 self.input_method_text = '' self.layout: Union[VerticalTextDocumentLayout, HorizontalTextDocumentLayout] = None self.document().setDocumentMargin(0) self.setVertical(False) Loading @@ -66,16 +79,52 @@ class TextBlkItem(QGraphicsTextItem): def inputMethodEvent(self, e: QInputMethodEvent): if e.preeditString() == '': self.pre_editing = False self.input_method_text = e.commitString() else: if self.pre_editing is False: cursor = self.textCursor() self.input_method_from = cursor.selectionStart() self.pre_editing = True super().inputMethodEvent(e) def is_editting(self): return self.textInteractionFlags() == Qt.TextInteractionFlag.TextEditorInteraction def onDocumentContentChanged(self): if self.hasFocus() and not self.pre_editing: self.content_changed.emit(self) def on_content_changed(self): if (self.hasFocus() or self.is_formatting) and not self.pre_editing: # self.content_changed.emit(self) if not self.in_redo_undo: if not self.is_formatting: change_from = self.change_from added_text = '' input_method_used = False if self.input_method_from != -1: added_text = self.input_method_text change_from = self.input_method_from input_method_used = True elif self.change_added > 0: len_text = len(self.toPlainText()) cursor = self.textCursor() if self.change_added > len_text: self.change_added = 1 change_from = self.textCursor().position() - 1 input_method_used = True cursor.setPosition(change_from) cursor.setPosition(change_from + self.change_added, QTextCursor.MoveMode.KeepAnchor) added_text = cursor.selectedText() self.user_edited_verbose.emit(change_from, added_text, input_method_used) undo_steps = self.document().availableUndoSteps() new_steps = undo_steps - self.old_undo_steps if new_steps > 0: self.old_undo_steps = undo_steps self.push_undo_stack.emit(new_steps, self.is_formatting) if not (self.hasFocus() and self.pre_editing): if self.repaint_on_changed: if not self.repainting: self.repaint_background() Loading Loading @@ -334,7 +383,8 @@ class TextBlkItem(QGraphicsTextItem): layout.documentSizeChanged.connect(self.docSizeChanged) doc.setDocumentLayout(layout) doc.setDefaultFont(default_font) doc.contentsChanged.connect(self.onDocumentContentChanged) doc.contentsChanged.connect(self.on_content_changed) doc.contentsChange.connect(self.on_content_changing) if valid_layout: layout.setMaxSize(rect.width(), rect.height()) Loading @@ -349,6 +399,44 @@ class TextBlkItem(QGraphicsTextItem): self.doc_size_changed.emit(self.idx) def updateUndoSteps(self): self.old_undo_steps = self.document().availableUndoSteps() def on_content_changing(self, from_: int, removed: int, added: int): if not self.pre_editing: if self.hasFocus(): self.change_from = from_ self.change_added = added 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 elif e.key() == Qt.Key.Key_V: if self.isEditing(): e.accept() self.pasted.emit(self.idx) 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() def on_document_enlarged(self): rect = self.rect() old_width = self._display_rect.width() Loading Loading @@ -670,12 +758,3 @@ class TextBlkItem(QGraphicsTextItem): self.setPadding(self.layout.max_font_size(to_px=True)) if repaint: self.repaint_background() No newline at end of file def keyPressEvent(self, e: QKeyEvent) -> None: if e.key() == Qt.Key.Key_V and e.modifiers() == Qt.KeyboardModifier.ControlModifier: if self.isEditing() is not None: e.accept() self.pasted.emit(self.idx) return return super().keyPressEvent(e) No newline at end of file Loading
ballontranslator/ui/fontformatpanel.py +2 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ def restore_textcursor(formatting_func): def wrapper(blkitem: TextBlkItem, *args, **kwargs): if blkitem is None: return blkitem.is_formatting = True cursor = blkitem.textCursor() set_all = not cursor.hasSelection() pos1 = cursor.position() Loading @@ -38,6 +39,7 @@ def restore_textcursor(formatting_func): cursor.setPosition(pos1) blkitem.setTextCursor(cursor) blkitem.repaint_background() blkitem.is_formatting = False return wrapper @restore_textcursor Loading
ballontranslator/ui/mainwindow.py +1 −0 Original line number Diff line number Diff line Loading @@ -308,6 +308,7 @@ class MainWindow(QMainWindow): self.leftStackWidget.hide() def closeEvent(self, event: QCloseEvent) -> None: self.st_manager.hovering_transwidget = None self.canvas.disconnect() self.canvas.undoStack.disconnect() self.save_config() Loading
ballontranslator/ui/scenetext_manager.py +104 −31 Original line number Diff line number Diff line Loading @@ -15,18 +15,19 @@ from .canvas import Canvas from .textedit_area import TextPanel, TransTextEdit, SourceTextEdit, TransPairWidget, TextEditCommand from .fontformatpanel import set_textblk_fontsize from .misc import FontFormat, ProgramConfig, pt2px from .page_search_widget import PageSearchWidget from .texteditshapecontrol import TextBlkShapeControl from utils.imgproc_utils import extract_ballon_region from utils.text_processing import seg_text, is_cjk from utils.text_layout import layout_text class MoveBlkItemsCommand(QUndoCommand): def __init__(self, items: List[TextBlkItem], parent=None): def __init__(self, items: List[TextBlkItem], shape_ctrl: TextBlkShapeControl): super(MoveBlkItemsCommand, self).__init__() self.items = items self.old_pos_lst = [] self.new_pos_lst = [] self.shape_ctrl = shape_ctrl for item in items: self.old_pos_lst.append(item.oldPos) self.new_pos_lst.append(item.pos()) Loading @@ -35,10 +36,14 @@ class MoveBlkItemsCommand(QUndoCommand): def redo(self): for item, new_pos in zip(self.items, self.new_pos_lst): item.setPos(new_pos) if self.shape_ctrl.blk_item == item and self.shape_ctrl.pos() != new_pos: self.shape_ctrl.setPos(new_pos) def undo(self): for item, old_pos in zip(self.items, self.old_pos_lst): item.setPos(old_pos) if self.shape_ctrl.blk_item == item and self.shape_ctrl.pos() != old_pos: self.shape_ctrl.setPos(old_pos) def mergeWith(self, command: QUndoCommand): if command.old_pos_lst == self.old_pos_lst: Loading Loading @@ -116,19 +121,24 @@ class ReshapeItemCommand(QUndoCommand): class RotateItemCommand(QUndoCommand): def __init__(self, item: TextBlkItem, new_angle: float): def __init__(self, item: TextBlkItem, new_angle: float, shape_ctrl: TextBlkShapeControl): super(RotateItemCommand, self).__init__() self.item = item self.old_angle = item.rotation() self.new_angle = new_angle self.shape_ctrl = shape_ctrl def redo(self): self.item.setRotation(self.new_angle) self.item.blk.angle = self.new_angle if self.shape_ctrl.blk_item == self.item and self.shape_ctrl.rotation() != self.new_angle: self.shape_ctrl.setRotation(self.new_angle) def undo(self): self.item.setRotation(self.old_angle) self.item.blk.angle = self.old_angle if self.shape_ctrl.blk_item == self.item and self.shape_ctrl.rotation() != self.old_angle: self.shape_ctrl.setRotation(self.old_angle) def mergeWith(self, command: QUndoCommand): item = command.item Loading Loading @@ -317,6 +327,34 @@ class AutoLayoutCommand(QUndoCommand): item.setLetterSpacing(item.letter_spacing, force=True) class TextItemEditCommand(QUndoCommand): def __init__(self, blkitem: TextBlkItem, trans_edit: TransTextEdit, num_steps: int): super(TextItemEditCommand, self).__init__() self.op_counter = 0 self.edit = trans_edit self.blkitem = blkitem self.num_steps = min(num_steps, 2) if blkitem.input_method_from == -1: self.num_steps = 1 else: blkitem.input_method_from = -1 def redo(self): if self.op_counter == 0: self.op_counter += 1 return for _ in range(self.num_steps): self.blkitem.redo() if self.edit is not None: self.edit.redo() def undo(self): for _ in range(self.num_steps): self.blkitem.undo() if self.edit is not None: self.edit.undo() class SceneTextManager(QObject): new_textblk = Signal(int) def __init__(self, Loading Loading @@ -369,6 +407,7 @@ class SceneTextManager(QObject): self.txtblkShapeControl.updateBoundingRect() def clearSceneTextitems(self): self.hovering_transwidget = None self.txtblkShapeControl.setBlkItem(None) for blkitem in self.textblk_item_list: self.canvas.removeItem(blkitem) Loading Loading @@ -411,16 +450,16 @@ class SceneTextManager(QObject): 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_source.redo_signal.connect(self.on_textedit_redo) pair_widget.e_source.undo_signal.connect(self.on_textedit_undo) pair_widget.e_trans.setPlainText(blk_item.toPlainText()) pair_widget.e_trans.focus_in.connect(self.onTransWidgetHoverEnter) pair_widget.e_trans.user_edited_verbose.connect(self.onTransWidgetUserEditedVerbose) 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) pair_widget.e_trans.redo_signal.connect(self.on_textedit_redo) pair_widget.e_trans.undo_signal.connect(self.on_textedit_undo) self.new_textblk.emit(blk_item.idx) return blk_item Loading @@ -436,7 +475,10 @@ class SceneTextManager(QObject): textblk_item.moved.connect(self.onTextBlkItemMoved) textblk_item.reshaped.connect(self.onTextBlkItemReshaped) textblk_item.rotated.connect(self.onTextBlkItemRotated) textblk_item.content_changed.connect(self.onTextBlkItemContentChanged) textblk_item.push_undo_stack.connect(self.on_push_textitem_undostack) textblk_item.undo_signal.connect(self.on_textedit_undo) textblk_item.redo_signal.connect(self.on_textedit_redo) textblk_item.user_edited_verbose.connect(self.onTextItemUserEditedVerbose) textblk_item.doc_size_changed.connect(self.onTextBlkItemSizeChanged) textblk_item.pasted.connect(self.onBlkitemPaste) return textblk_item Loading Loading @@ -471,13 +513,9 @@ class SceneTextManager(QObject): def recoverTextblkItemList(self, blkitem_list: List[TextBlkItem], p_widget_list: List[TransPairWidget]): for blkitem, p_widget in zip(blkitem_list, p_widget_list): self.recoverTextblkItem(blkitem, p_widget) if self.txtblkShapeControl.blk_item is not None and blkitem.isSelected(): blkitem.setSelected(False) def onTextBlkItemContentChanged(self, blk_item: TextBlkItem): if blk_item.hasFocus(): trans_widget = self.pairwidget_list[blk_item.idx].e_trans if not trans_widget.hasFocus(): trans_widget.setText(blk_item.toPlainText()) self.canvas.setProjSaveState(True) def onTextBlkItemSizeChanged(self, idx: int): blk_item = self.textblk_item_list[idx] Loading @@ -497,6 +535,17 @@ class SceneTextManager(QObject): self.canvas.editing_textblkitem = blk_item self.formatpanel.set_textblk_item(blk_item) self.txtblkShapeControl.setCursor(Qt.CursorShape.IBeamCursor) e_trans = self.pairwidget_list[blk_item.idx].e_trans self.changeHoveringWidget(e_trans) def changeHoveringWidget(self, edit: SourceTextEdit): if self.hovering_transwidget != edit: if self.hovering_transwidget is not None: self.hovering_transwidget.setHoverEffect(False) self.hovering_transwidget = edit if edit is not None: self.textEditList.ensureWidgetVisible(edit) edit.setHoverEffect(True) def onLeftbuttonPressed(self, blk_id: int): blk_item = self.textblk_item_list[blk_id] Loading Loading @@ -530,11 +579,7 @@ class SceneTextManager(QObject): blk_item = self.textblk_item_list[blk_id] if not blk_item.hasFocus(): self.txtblkShapeControl.setBlkItem(blk_item) if self.hovering_transwidget is not None: self.hovering_transwidget.setHoverEffect(False) self.hovering_transwidget = self.pairwidget_list[blk_id].e_trans self.hovering_transwidget.setHoverEffect(True) self.textpanel.textEditList.ensureWidgetVisible(self.hovering_transwidget) self.changeHoveringWidget(self.pairwidget_list[blk_id].e_trans) def onTextBlkItemMoving(self, item: TextBlkItem): self.txtblkShapeControl.updateBoundingRect() Loading @@ -542,7 +587,7 @@ class SceneTextManager(QObject): def onTextBlkItemMoved(self): selected_blks = self.get_selected_blkitems() if len(selected_blks) > 0: self.canvasUndoStack.push(MoveBlkItemsCommand(selected_blks, self)) self.canvasUndoStack.push(MoveBlkItemsCommand(selected_blks, self.txtblkShapeControl)) def onTextBlkItemReshaped(self, item: TextBlkItem): self.canvasUndoStack.push(ReshapeItemCommand(item)) Loading @@ -550,7 +595,7 @@ class SceneTextManager(QObject): def onTextBlkItemRotated(self, new_angle: float): blk_item = self.txtblkShapeControl.blk_item if blk_item: self.canvasUndoStack.push(RotateItemCommand(blk_item, new_angle)) self.canvasUndoStack.push(RotateItemCommand(blk_item, new_angle, self.txtblkShapeControl)) def onDeleteBlkItems(self): selected_blks = self.get_selected_blkitems() Loading Loading @@ -784,6 +829,42 @@ class SceneTextManager(QObject): self.canvas.gv.ensureVisible(blk_item) self.txtblkShapeControl.setBlkItem(blk_item) def on_textedit_redo(self): self.canvasUndoStack.redo() def on_textedit_undo(self): self.canvasUndoStack.undo() def on_push_textitem_undostack(self, num_steps: int, is_formatting: bool): blkitem: TextBlkItem = self.sender() e_trans = self.pairwidget_list[blkitem.idx].e_trans if not is_formatting else None self.canvasUndoStack.push(TextItemEditCommand(blkitem, e_trans, num_steps)) def on_push_edit_stack(self, num_steps: int): edit: Union[TransTextEdit, SourceTextEdit] = self.sender() blkitem = self.textblk_item_list[edit.idx] if type(edit) == TransTextEdit else None self.canvasUndoStack.push(TextEditCommand(edit, num_steps, blkitem)) def onTextItemUserEditedVerbose(self, pos: int, added_text: str, input_method_used: bool): blk_item: TextBlkItem = self.sender() idx = blk_item.idx edit = self.pairwidget_list[idx].e_trans ori_count = edit.document().characterCount() new_count = blk_item.document().characterCount() removed = ori_count + len(added_text) - new_count cursor = edit.textCursor() cursor.setPosition(pos) if removed > 0: cursor.setPosition(pos + removed, QTextCursor.MoveMode.KeepAnchor) if input_method_used: cursor.beginEditBlock() cursor.insertText((added_text)) if input_method_used: cursor.endEditBlock() self.canvas.setProjSaveState(True) def onTransWidgetUserEditedVerbose(self, pos: int, added_text: str, input_method_used: bool): edit: TransTextEdit = self.sender() idx = edit.idx Loading @@ -794,7 +875,6 @@ class SceneTextManager(QObject): ori_count = blk_item.document().characterCount() new_count = edit.document().characterCount() removed = ori_count + len(added_text) - new_count # if not input_method_used: cursor = blk_item.textCursor() cursor.setPosition(pos) Loading Loading @@ -859,7 +939,6 @@ class SceneTextManager(QObject): for blk_item, transwidget in zip(self.textblk_item_list, self.pairwidget_list): transwidget.e_trans.setPlainText(blk_item.blk.translation) blk_item.setPlainText(blk_item.blk.translation) # blk_item.update() def showTextblkItemRect(self, draw_rect: bool): for blk_item in self.textblk_item_list: Loading @@ -874,16 +953,10 @@ class SceneTextManager(QObject): def on_ensure_textitem_svisible(self): edit: Union[TransTextEdit, SourceTextEdit] = self.sender() self.textEditList.ensureWidgetVisible(edit) self.changeHoveringWidget(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, num_steps: int): edit: Union[TransTextEdit, SourceTextEdit] = self.sender() blkitem = self.textblk_item_list[edit.idx] self.canvasUndoStack.push(TextEditCommand(self.sender(), blkitem, num_steps)) def get_text_size(fm: QFontMetrics, text: str) -> Tuple[int, int]: brt = fm.tightBoundingRect(text) br = fm.boundingRect(text) Loading
ballontranslator/ui/textedit_area.py +13 −11 Original line number Diff line number Diff line Loading @@ -38,7 +38,6 @@ class SourceTextEdit(QTextEdit): self.old_undo_steps = self.document().availableUndoSteps() self.in_redo_undo = False self.change_from: int = 0 self.change_removed: int = 0 self.change_added: int = 0 self.input_method_from = -1 self.input_method_text = '' Loading @@ -55,7 +54,7 @@ class SourceTextEdit(QTextEdit): def on_content_changing(self, from_: int, removed: int, added: int): if not self.pre_editing: self.text_content_changed = True if self.hasFocus() and not self.pre_editing: if self.hasFocus(): self.change_from = from_ self.change_added = added Loading Loading @@ -142,7 +141,6 @@ class SourceTextEdit(QTextEdit): cursor = self.textCursor() self.input_method_from = cursor.selectionStart() self.pre_editing = True self.input_method_event = e super().inputMethodEvent(e) def keyPressEvent(self, e: QKeyEvent) -> None: Loading @@ -169,6 +167,11 @@ class SourceTextEdit(QTextEdit): self.in_redo_undo = False self.old_undo_steps = self.document().availableUndoSteps() def setPlainTextAndKeepUndoStack(self, text: str): cursor = QTextCursor(self.document()) cursor.select(QTextCursor.SelectionType.Document) cursor.insertText(text) class TransTextEdit(SourceTextEdit): pass Loading Loading @@ -233,14 +236,11 @@ class TextPanel(Widget): class TextEditCommand(QUndoCommand): def __init__(self, edit: Union[SourceTextEdit, TransTextEdit], blkitem: TextBlkItem, num_steps: int) -> None: def __init__(self, edit: Union[SourceTextEdit, TransTextEdit], num_steps: int, blkitem: TextBlkItem) -> None: super().__init__() self.edit = edit self.blkitem = blkitem self.op_counter = 0 self.change_from = edit.change_from self.change_added = edit.change_added self.change_removed = edit.change_removed self.num_steps = min(num_steps, 2) if edit.input_method_from == -1: self.num_steps = 1 Loading @@ -254,10 +254,12 @@ class TextEditCommand(QUndoCommand): for _ in range(self.num_steps): self.edit.redo() if self.blkitem is not None: self.blkitem.document().redo() def undo(self): for _ in range(self.num_steps): self.edit.undo() if self.blkitem is not None: self.blkitem.document().undo()
ballontranslator/ui/textitem.py +98 −19 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ TEXTRECT_SELECTED_COLOR = QColor(248, 64, 147, 170) class TextBlkItem(QGraphicsTextItem): begin_edit = Signal(int) end_edit = Signal(int) hover_enter = Signal(int) Loading @@ -25,10 +26,14 @@ class TextBlkItem(QGraphicsTextItem): moving = Signal(QGraphicsTextItem) rotated = Signal(float) reshaped = Signal(QGraphicsTextItem) content_changed = Signal(QGraphicsTextItem) leftbutton_pressed = Signal(int) doc_size_changed = Signal(int) pasted = Signal(int) redo_signal = Signal() undo_signal = Signal() push_undo_stack = Signal(int, bool) user_edited_verbose = Signal(int, str, bool) def __init__(self, blk: TextBlock = None, idx: int = 0, set_format=True, show_rect=False, *args, **kwargs): super().__init__(*args, **kwargs) self.pre_editing = False Loading @@ -55,6 +60,14 @@ class TextBlkItem(QGraphicsTextItem): self.oldRect = QRectF() self.repaint_on_changed = True self.is_formatting = False self.old_undo_steps = 0 self.in_redo_undo = False self.change_from: int = 0 self.change_added: int = 0 self.input_method_from = -1 self.input_method_text = '' self.layout: Union[VerticalTextDocumentLayout, HorizontalTextDocumentLayout] = None self.document().setDocumentMargin(0) self.setVertical(False) Loading @@ -66,16 +79,52 @@ class TextBlkItem(QGraphicsTextItem): def inputMethodEvent(self, e: QInputMethodEvent): if e.preeditString() == '': self.pre_editing = False self.input_method_text = e.commitString() else: if self.pre_editing is False: cursor = self.textCursor() self.input_method_from = cursor.selectionStart() self.pre_editing = True super().inputMethodEvent(e) def is_editting(self): return self.textInteractionFlags() == Qt.TextInteractionFlag.TextEditorInteraction def onDocumentContentChanged(self): if self.hasFocus() and not self.pre_editing: self.content_changed.emit(self) def on_content_changed(self): if (self.hasFocus() or self.is_formatting) and not self.pre_editing: # self.content_changed.emit(self) if not self.in_redo_undo: if not self.is_formatting: change_from = self.change_from added_text = '' input_method_used = False if self.input_method_from != -1: added_text = self.input_method_text change_from = self.input_method_from input_method_used = True elif self.change_added > 0: len_text = len(self.toPlainText()) cursor = self.textCursor() if self.change_added > len_text: self.change_added = 1 change_from = self.textCursor().position() - 1 input_method_used = True cursor.setPosition(change_from) cursor.setPosition(change_from + self.change_added, QTextCursor.MoveMode.KeepAnchor) added_text = cursor.selectedText() self.user_edited_verbose.emit(change_from, added_text, input_method_used) undo_steps = self.document().availableUndoSteps() new_steps = undo_steps - self.old_undo_steps if new_steps > 0: self.old_undo_steps = undo_steps self.push_undo_stack.emit(new_steps, self.is_formatting) if not (self.hasFocus() and self.pre_editing): if self.repaint_on_changed: if not self.repainting: self.repaint_background() Loading Loading @@ -334,7 +383,8 @@ class TextBlkItem(QGraphicsTextItem): layout.documentSizeChanged.connect(self.docSizeChanged) doc.setDocumentLayout(layout) doc.setDefaultFont(default_font) doc.contentsChanged.connect(self.onDocumentContentChanged) doc.contentsChanged.connect(self.on_content_changed) doc.contentsChange.connect(self.on_content_changing) if valid_layout: layout.setMaxSize(rect.width(), rect.height()) Loading @@ -349,6 +399,44 @@ class TextBlkItem(QGraphicsTextItem): self.doc_size_changed.emit(self.idx) def updateUndoSteps(self): self.old_undo_steps = self.document().availableUndoSteps() def on_content_changing(self, from_: int, removed: int, added: int): if not self.pre_editing: if self.hasFocus(): self.change_from = from_ self.change_added = added 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 elif e.key() == Qt.Key.Key_V: if self.isEditing(): e.accept() self.pasted.emit(self.idx) 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() def on_document_enlarged(self): rect = self.rect() old_width = self._display_rect.width() Loading Loading @@ -670,12 +758,3 @@ class TextBlkItem(QGraphicsTextItem): self.setPadding(self.layout.max_font_size(to_px=True)) if repaint: self.repaint_background() No newline at end of file def keyPressEvent(self, e: QKeyEvent) -> None: if e.key() == Qt.Key.Key_V and e.modifiers() == Qt.KeyboardModifier.ControlModifier: if self.isEditing() is not None: e.accept() self.pasted.emit(self.idx) return return super().keyPressEvent(e) No newline at end of file