Commit 56699885 authored by dmMaze's avatar dmMaze
Browse files

Editable line number, close #439

parent 319c0d44
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -338,7 +338,7 @@ class DrawingPanel(Widget):
        self.setPenToolColor([0, 0, 0, 127])

        self.toolConfigStackwidget = QStackedWidget()
        self.toolConfigStackwidget.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
        self.toolConfigStackwidget.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Minimum)
        self.toolConfigStackwidget.addWidget(self.inpaintConfigPanel)
        self.toolConfigStackwidget.addWidget(self.penConfigPanel)
        self.toolConfigStackwidget.addWidget(self.rectPanel)
+3 −4
Original line number Diff line number Diff line
@@ -425,7 +425,7 @@ class TextStyleLabel(Widget):
        self.active_stylename_edited = active_stylename_edited
        self.stylelabel = StyleLabel(style_name, parent=self)
        self.stylelabel.edit_finished.connect(self.on_style_name_edited)
        self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
        self.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Maximum)

        self.setToolTip(self.tr('Click to set as Global format. Double click to edit name.'))
        self.setCursor(Qt.CursorShape.PointingHandCursor)
@@ -642,8 +642,7 @@ class TextStyleArea(QScrollArea):
        self.flayout.addWidget(self.new_btn)
        self.flayout.addWidget(self.clear_btn)

        # self.scrollContent.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
        self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
        self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum)

        ScrollBar(Qt.Orientation.Vertical, self)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
@@ -880,7 +879,7 @@ class TextStylePanel(Widget):
            self.style_area.hide()
        
        self.title_label.clicked.connect(self.on_title_label_clicked)
        self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
        self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum)

    def expand(self):
        if not self.title_label.expanded:
+42 −13
Original line number Diff line number Diff line
@@ -248,32 +248,56 @@ class RearrangeBlksCommand(QUndoCommand):
    def __init__(self, rmap: Tuple, ctrl, parent=None):
        super().__init__(parent)
        self.ctrl: SceneTextManager = ctrl
        self.src_ids, self.tgt_ids = rmap
        self.src_ids, self.tgt_ids = rmap[0], rmap[1]

        self.nr = len(self.src_ids)
        self.src2tgt = {}
        self.tgt2src = {}
        for s, t in zip(self.src_ids, self.tgt_ids):
            self.src2tgt[s] = t
            self.tgt2src[t] = s
        self.visible_ = None
        self.redo_visible_idx = self.undo_visible_idx = None
        if len(rmap) > 2:
            self.redo_visible_idx, self.undo_visible_idx = rmap[2]

    def redo(self):
        self.rearrage_blk_ids(self.src_ids, self.tgt_ids)
        self.rearange_blk_ids(self.src_ids, self.tgt_ids, self.redo_visible_idx)

    def undo(self):
        self.rearrage_blk_ids(self.tgt_ids, self.src_ids)
        self.rearange_blk_ids(self.tgt_ids, self.src_ids, self.undo_visible_idx)

    def rearange_blk_ids(self, src_ids, tgt_ids, visible_idx = None):
        src_ids = np.array(src_ids)
        tgt_ids = np.array(tgt_ids)
        src_order_ids = np.argsort(src_ids)[::-1]

        src_ids = src_ids[src_order_ids]
        tgt_ids = tgt_ids[src_order_ids]
        
    def rearrage_blk_ids(self, src_ids, tgt_ids):
        blks: List[TextBlkItem] = []
        pws: List[TransPairWidget] = []
        for ii, src_idx in enumerate(src_ids):
            pos = src_idx - ii
        for pos, pos_tgt in zip(src_ids, tgt_ids):
            pw = self.ctrl.pairwidget_list.pop(pos)
            self.ctrl.textEditList.removeWidget(pw, remove_checked=False)
            if visible_idx == pos_tgt:
                pw.hide()
            blk = self.ctrl.textblk_item_list.pop(pos)
            pws.append(pw)
            blks.append(blk)

        for ii, tgt_idx in enumerate(tgt_ids):
            self.ctrl.textblk_item_list.insert(tgt_idx, blks[ii])
            self.ctrl.pairwidget_list.insert(tgt_idx, pws[ii])
            self.ctrl.textEditList.insertPairWidget(pws[ii], tgt_idx)
        tgt_order_ids = np.argsort(tgt_ids)
        for ii in tgt_order_ids:
            pos = tgt_ids[ii]
            self.ctrl.textblk_item_list.insert(pos, blks[ii])
            
            self.ctrl.textEditList.insertPairWidget(pws[ii], pos)
            self.ctrl.pairwidget_list.insert(pos, pws[ii])

        self.ctrl.updateTextBlkItemIdx()    # some optimization could be done here
        self.ctrl.updateTextBlkItemIdx(set(tgt_ids))
        if visible_idx is not None:
            pw_ct = self.ctrl.pairwidget_list[visible_idx]
            pw_ct.show()
            self.ctrl.textEditList.ensureWidgetVisible(pw_ct, yMargin=pw.height())

class TextPanel(Widget):
    def __init__(self, app: QApplication, *args, **kwargs) -> None:
@@ -404,6 +428,7 @@ class SceneTextManager(QObject):
        pair_widget.e_trans.show_select_menu.connect(self.on_show_select_menu)
        pair_widget.e_trans.focus_out.connect(self.on_pairw_focusout)
        pair_widget.drag_move.connect(self.textEditList.handle_drag_pos)
        pair_widget.idx_edited.connect(self.textEditList.on_idx_edited)

        self.new_textblk.emit(blk_item.idx)
        return blk_item
@@ -941,6 +966,7 @@ class SceneTextManager(QObject):
    def on_transwidget_selection_changed(self):
        selitems = self.canvas.selected_text_items()
        selset = {pw.idx: pw for pw in self.textEditList.checked_list}
        self.canvas.block_selection_signal = True
        for blkitem in selitems:
            if blkitem.idx not in selset:
                blkitem.setSelected(False)
@@ -948,6 +974,7 @@ class SceneTextManager(QObject):
                selset.pop(blkitem.idx)
        for idx in selset:
            self.textblk_item_list[idx].setSelected(True)
        self.canvas.block_selection_signal = False

    def on_textedit_list_focusout(self):
        fw = self.app.focusWidget()
@@ -966,8 +993,10 @@ class SceneTextManager(QObject):
        if len(selected_blks) > 0:
            self.canvas.push_undo_command(ApplyEffectCommand(selected_blks, ffmt))

    def updateTextBlkItemIdx(self):
    def updateTextBlkItemIdx(self, sel_ids: set = None):
        for ii, blk_item in enumerate(self.textblk_item_list):
            if sel_ids is not None and ii not in sel_ids:
                continue
            blk_item.idx = ii
            self.pairwidget_list[ii].updateIndex(ii)
        cl = self.textEditList.checked_list
+0 −5
Original line number Diff line number Diff line
@@ -529,11 +529,6 @@ class ClickableLabel(QLabel):
            self.clicked.emit()
        return super().mousePressEvent(e)

class IgnoreMouseLabel(QLabel):

    def mousePressEvent(self, e: QMouseEvent) -> None:
        e.ignore()
        return super().mousePressEvent(e)
    
class CheckableLabel(QLabel):

+145 −10
Original line number Diff line number Diff line
from typing import List, Union

from qtpy.QtWidgets import QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QApplication, QHBoxLayout, QSizePolicy
from qtpy.QtCore import Signal, Qt, QMimeData, QEvent, QPoint
from qtpy.QtGui import QColor, QFocusEvent, QInputMethodEvent, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, QTextCursor, QMouseEvent, QDrag, QPixmap, QKeySequence
from qtpy.QtWidgets import QStackedWidget, QSizePolicy, QTextEdit, QScrollArea, QGraphicsDropShadowEffect, QVBoxLayout, QApplication, QHBoxLayout, QSizePolicy, QLabel, QLineEdit
from qtpy.QtCore import Signal, Qt, QMimeData, QEvent, QPoint, QSize
from qtpy.QtGui import QIntValidator, QColor, QFocusEvent, QInputMethodEvent, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, QTextCursor, QMouseEvent, QDrag, QPixmap, QKeySequence
import keyboard
import webbrowser
import numpy as np

from .stylewidgets import Widget, SeparatorWidget, ClickableLabel, IgnoreMouseLabel, ScrollBar
from .stylewidgets import Widget, SeparatorWidget, ClickableLabel, ScrollBar
from .textitem import TextBlock
from utils.config import pcfg
from utils.logger import logger as LOGGER


STYLE_TRANSPAIR_CHECKED = "background-color: rgba(30, 147, 229, 20%);"
@@ -304,19 +305,110 @@ class TransTextEdit(SourceTextEdit):
    pass


class RowIndexEditor(QLineEdit):

    focus_out = Signal()
    
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.setValidator(QIntValidator())
        self.setReadOnly(True)
        self.setTextMargins(0, 0, 0, 0)

    def focusOutEvent(self, e: QFocusEvent) -> None:
        super().focusOutEvent(e)
        self.focus_out.emit()

    def minimumSizeHint(self):
        size = super().minimumSizeHint()
        return QSize(1, size.height())
    
    def sizeHint(self):
        size = super().sizeHint()
        return QSize(1, size.height())
    

class RowIndexLabel(QStackedWidget):

    submmit_idx = Signal(int)

    def __init__(self, text: str = None, parent=None):
        super().__init__(parent=parent)
        self.lineedit = RowIndexEditor(parent=self)
        self.lineedit.focus_out.connect(self.on_lineedit_focusout)

        self.show_label = QLabel(self)
        self.text = self.show_label.text

        self.addWidget(self.show_label)
        self.addWidget(self.lineedit)
        self.setCurrentIndex(0)

        if text is not None:
            self.setText(text)
        self.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Maximum)

    def setText(self, text):
        if isinstance(text, int):
            text = str(text)
        self.show_label.setText(text)
        self.lineedit.setText(text)

    def keyPressEvent(self, e: QKeyEvent) -> None:
        super().keyPressEvent(e)

        key = e.key()
        if key == Qt.Key.Key_Return:
            self.try_update_idx()

    def try_update_idx(self):
        idx_str = self.lineedit.text().strip()
        if not idx_str:
            return
        if self.text() == idx_str:
            return
        try:
            idx = int(idx_str)
            self.lineedit.setReadOnly(True)
            self.submmit_idx.emit(idx)
            
        except Exception as e:
            LOGGER.warning(f'Invalid index str: {idx}')

    def mouseDoubleClickEvent(self, e: QMouseEvent) -> None:
        self.startEdit()
        return super().mouseDoubleClickEvent(e)

    def startEdit(self) -> None:
        self.setCurrentIndex(1)
        self.lineedit.setReadOnly(False)
        self.lineedit.setFocus()

    def on_lineedit_focusout(self):
        edited = not self.lineedit.isReadOnly()
        self.lineedit.setReadOnly(True)
        self.setCurrentIndex(0)
        if edited:
            self.try_update_idx()

    def mousePressEvent(self, e: QMouseEvent) -> None:
        e.ignore()
        return super().mousePressEvent(e)
 

class TransPairWidget(Widget):

    check_state_changed = Signal(object, bool, bool)
    drag_move = Signal(int)
    idx_edited = Signal(int, int)

    def __init__(self, textblock: TextBlock = None, idx: int = None, fold: bool = False, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.e_source = SourceTextEdit(idx, self, fold)
        self.e_trans = TransTextEdit(idx, self, fold)
        self.idx_label = IgnoreMouseLabel(self)
        self.idx_label = RowIndexLabel(idx, self)
        self.idx_label.setText(str(idx + 1).zfill(2))   # showed index start from 1!
        self.submmit_idx = self.idx_label.submmit_idx.connect(self.on_idx_edited)
        self.textblock = textblock
        self.idx = idx
        self.checked = False
@@ -339,6 +431,10 @@ class TransPairWidget(Widget):

        self.setAcceptDrops(True)

    def on_idx_edited(self, new_idx: int):
        new_idx -= 1
        self.idx_edited.emit(self.idx, new_idx)

    def dragEnterEvent(self, e: QDragEnterEvent) -> None:
        if isinstance(e.source(), TransPairWidget):
            e.accept()
@@ -369,7 +465,7 @@ class TransPairWidget(Widget):
            else:
                self.setStyleSheet("")

    def mouseReleaseEvent(self, e: QMouseEvent) -> None:
    def update_checkstate_by_mousevent(self, e: QMouseEvent):
        if e.button() == Qt.MouseButton.LeftButton:
            modifiers = e.modifiers()
            if modifiers & Qt.KeyboardModifier.ShiftModifier and modifiers & Qt.KeyboardModifier.ControlModifier:
@@ -378,7 +474,11 @@ class TransPairWidget(Widget):
                shift_pressed = modifiers == Qt.KeyboardModifier.ShiftModifier
                ctrl_pressed = modifiers == Qt.KeyboardModifier.ControlModifier
            self.check_state_changed.emit(self, shift_pressed, ctrl_pressed)
        return super().mouseReleaseEvent(e)

    def mousePressEvent(self, e: QMouseEvent) -> None:
        if not self.checked:
            self.update_checkstate_by_mousevent(e)
        return super().mousePressEvent(e)

    def updateIndex(self, idx: int):
        if self.idx != idx:
@@ -464,7 +564,21 @@ class TextEditListScrollArea(QScrollArea):
                new_maps = np.where(drags != new_pos)
                if len(new_maps) == 0:
                    return

                drags_ori, drags_tgt = drags[new_maps], new_pos[new_maps]
                result_list = list(range(len(self.pairwidget_list)))
                to_insert = []
                for ii, src_idx in enumerate(drags_ori):
                    pos = src_idx - ii
                    to_insert.append(result_list.pop(pos))
                for ii, tgt_idx in enumerate(drags_tgt):
                    result_list.insert(tgt_idx, to_insert[ii])
                drags_ori, drags_tgt = [], []
                for ii, idx in enumerate(result_list):
                    if ii != idx:
                        drags_ori.append(idx)
                        drags_tgt.append(ii)

                self.rearrange_blks.emit((drags_ori, drags_tgt))

        return super().mouseMoveEvent(e)
@@ -486,12 +600,14 @@ class TextEditListScrollArea(QScrollArea):
    def clearDrag(self):
        self.drag_to_pos = -1
        if self.drag is not None:
            try:
                self.drag.cancel()
            except RuntimeError:
                pass
            self.drag = None

    def dragMoveEvent(self, e: QDragMoveEvent) -> None:
        e.accept()
        e.position()
        return super().dragMoveEvent(e)
    
    def dragEnterEvent(self, e: QDragEnterEvent):
@@ -509,6 +625,26 @@ class TextEditListScrollArea(QScrollArea):
            self.drag_to_pos = to_pos
            self.set_drag_style(to_pos)

    def on_idx_edited(self, src_idx: int, tgt_idx: int):
        src_idx_ori = tgt_idx
        tgt_idx = max(min(tgt_idx, len(self.pairwidget_list) - 1), 0)
        if src_idx_ori != tgt_idx:
            self.pairwidget_list[src_idx].idx_label.setText(str(src_idx + 1).zfill(2))
        if src_idx == tgt_idx:
            return
        ids_ori, ids_tgt = [src_idx], [tgt_idx]
        
        if src_idx < tgt_idx:
            for idx in range(src_idx+1, tgt_idx+1):
                ids_ori.append(idx)
                ids_tgt.append(idx-1)
        else:
            for idx in range(tgt_idx, src_idx):
                ids_ori.append(idx)
                ids_tgt.append(idx+1)
        self.rearrange_blks.emit((ids_ori, ids_tgt, (tgt_idx, src_idx)))
        # self.ensureVisible

    def addPairWidget(self, pairwidget: TransPairWidget):
        self.vlayout.insertWidget(pairwidget.idx, pairwidget)
        pairwidget.check_state_changed.connect(self.on_widget_checkstate_changed)
@@ -612,7 +748,6 @@ class TextEditListScrollArea(QScrollArea):
            if idx == 0:
                self.sel_anchor_widget = pw


    def clearAllSelected(self, emit_signal=True):
        self.sel_anchor_widget = None
        if len(self.checked_list) > 0: