Skip to content

Commit e2c9800

Browse files
committed
Editable line number, close #439
1 parent 319c0d4 commit e2c9800

File tree

4 files changed

+189
-27
lines changed

4 files changed

+189
-27
lines changed

ui/drawingpanel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ def __init__(self, canvas: Canvas, inpainter_panel: InpaintConfigPanel, *args, *
338338
self.setPenToolColor([0, 0, 0, 127])
339339

340340
self.toolConfigStackwidget = QStackedWidget()
341-
self.toolConfigStackwidget.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
341+
self.toolConfigStackwidget.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Minimum)
342342
self.toolConfigStackwidget.addWidget(self.inpaintConfigPanel)
343343
self.toolConfigStackwidget.addWidget(self.penConfigPanel)
344344
self.toolConfigStackwidget.addWidget(self.rectPanel)

ui/fontformatpanel.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ def __init__(self, style_name: str = '', parent: Widget = None, fontfmt: FontFor
425425
self.active_stylename_edited = active_stylename_edited
426426
self.stylelabel = StyleLabel(style_name, parent=self)
427427
self.stylelabel.edit_finished.connect(self.on_style_name_edited)
428-
self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
428+
self.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Maximum)
429429

430430
self.setToolTip(self.tr('Click to set as Global format. Double click to edit name.'))
431431
self.setCursor(Qt.CursorShape.PointingHandCursor)
@@ -642,8 +642,7 @@ def __init__(self, parent: Widget = None):
642642
self.flayout.addWidget(self.new_btn)
643643
self.flayout.addWidget(self.clear_btn)
644644

645-
# self.scrollContent.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
646-
self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
645+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum)
647646

648647
ScrollBar(Qt.Orientation.Vertical, self)
649648
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
@@ -880,7 +879,7 @@ def __init__(self, text=None, parent=None, expanded=True, *args, **kwargs):
880879
self.style_area.hide()
881880

882881
self.title_label.clicked.connect(self.on_title_label_clicked)
883-
self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
882+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum)
884883

885884
def expand(self):
886885
if not self.title_label.expanded:

ui/scenetext_manager.py

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -248,32 +248,56 @@ class RearrangeBlksCommand(QUndoCommand):
248248
def __init__(self, rmap: Tuple, ctrl, parent=None):
249249
super().__init__(parent)
250250
self.ctrl: SceneTextManager = ctrl
251-
self.src_ids, self.tgt_ids = rmap
251+
self.src_ids, self.tgt_ids = rmap[0], rmap[1]
252+
252253
self.nr = len(self.src_ids)
254+
self.src2tgt = {}
255+
self.tgt2src = {}
256+
for s, t in zip(self.src_ids, self.tgt_ids):
257+
self.src2tgt[s] = t
258+
self.tgt2src[t] = s
259+
self.visible_ = None
260+
self.redo_visible_idx = self.undo_visible_idx = None
261+
if len(rmap) > 2:
262+
self.redo_visible_idx, self.undo_visible_idx = rmap[2]
253263

254264
def redo(self):
255-
self.rearrage_blk_ids(self.src_ids, self.tgt_ids)
265+
self.rearange_blk_ids(self.src_ids, self.tgt_ids, self.redo_visible_idx)
256266

257267
def undo(self):
258-
self.rearrage_blk_ids(self.tgt_ids, self.src_ids)
268+
self.rearange_blk_ids(self.tgt_ids, self.src_ids, self.undo_visible_idx)
269+
270+
def rearange_blk_ids(self, src_ids, tgt_ids, visible_idx = None):
271+
src_ids = np.array(src_ids)
272+
tgt_ids = np.array(tgt_ids)
273+
src_order_ids = np.argsort(src_ids)[::-1]
259274

260-
def rearrage_blk_ids(self, src_ids, tgt_ids):
275+
src_ids = src_ids[src_order_ids]
276+
tgt_ids = tgt_ids[src_order_ids]
277+
261278
blks: List[TextBlkItem] = []
262279
pws: List[TransPairWidget] = []
263-
for ii, src_idx in enumerate(src_ids):
264-
pos = src_idx - ii
280+
for pos, pos_tgt in zip(src_ids, tgt_ids):
265281
pw = self.ctrl.pairwidget_list.pop(pos)
266-
self.ctrl.textEditList.removeWidget(pw, remove_checked=False)
282+
if visible_idx == pos_tgt:
283+
pw.hide()
267284
blk = self.ctrl.textblk_item_list.pop(pos)
268285
pws.append(pw)
269286
blks.append(blk)
270287

271-
for ii, tgt_idx in enumerate(tgt_ids):
272-
self.ctrl.textblk_item_list.insert(tgt_idx, blks[ii])
273-
self.ctrl.pairwidget_list.insert(tgt_idx, pws[ii])
274-
self.ctrl.textEditList.insertPairWidget(pws[ii], tgt_idx)
288+
tgt_order_ids = np.argsort(tgt_ids)
289+
for ii in tgt_order_ids:
290+
pos = tgt_ids[ii]
291+
self.ctrl.textblk_item_list.insert(pos, blks[ii])
292+
293+
self.ctrl.textEditList.insertPairWidget(pws[ii], pos)
294+
self.ctrl.pairwidget_list.insert(pos, pws[ii])
275295

276-
self.ctrl.updateTextBlkItemIdx() # some optimization could be done here
296+
self.ctrl.updateTextBlkItemIdx(set(tgt_ids))
297+
if visible_idx is not None:
298+
pw_ct = self.ctrl.pairwidget_list[visible_idx]
299+
pw_ct.show()
300+
self.ctrl.textEditList.ensureWidgetVisible(pw_ct, yMargin=pw.height())
277301

278302
class TextPanel(Widget):
279303
def __init__(self, app: QApplication, *args, **kwargs) -> None:
@@ -404,6 +428,7 @@ def addTextBlock(self, blk: Union[TextBlock, TextBlkItem] = None) -> TextBlkItem
404428
pair_widget.e_trans.show_select_menu.connect(self.on_show_select_menu)
405429
pair_widget.e_trans.focus_out.connect(self.on_pairw_focusout)
406430
pair_widget.drag_move.connect(self.textEditList.handle_drag_pos)
431+
pair_widget.idx_edited.connect(self.textEditList.on_idx_edited)
407432

408433
self.new_textblk.emit(blk_item.idx)
409434
return blk_item
@@ -941,13 +966,15 @@ def apply_fontformat(self, fontformat: FontFormat):
941966
def on_transwidget_selection_changed(self):
942967
selitems = self.canvas.selected_text_items()
943968
selset = {pw.idx: pw for pw in self.textEditList.checked_list}
969+
self.canvas.block_selection_signal = True
944970
for blkitem in selitems:
945971
if blkitem.idx not in selset:
946972
blkitem.setSelected(False)
947973
else:
948974
selset.pop(blkitem.idx)
949975
for idx in selset:
950976
self.textblk_item_list[idx].setSelected(True)
977+
self.canvas.block_selection_signal = False
951978

952979
def on_textedit_list_focusout(self):
953980
fw = self.app.focusWidget()
@@ -966,8 +993,10 @@ def on_apply_effect(self):
966993
if len(selected_blks) > 0:
967994
self.canvas.push_undo_command(ApplyEffectCommand(selected_blks, ffmt))
968995

969-
def updateTextBlkItemIdx(self):
996+
def updateTextBlkItemIdx(self, sel_ids: set = None):
970997
for ii, blk_item in enumerate(self.textblk_item_list):
998+
if sel_ids is not None and ii not in sel_ids:
999+
continue
9711000
blk_item.idx = ii
9721001
self.pairwidget_list[ii].updateIndex(ii)
9731002
cl = self.textEditList.checked_list

ui/textedit_area.py

Lines changed: 143 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from typing import List, Union
22

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

1010
from .stylewidgets import Widget, SeparatorWidget, ClickableLabel, IgnoreMouseLabel, ScrollBar
1111
from .textitem import TextBlock
1212
from utils.config import pcfg
13+
from utils.logger import logger as LOGGER
1314

1415

1516
STYLE_TRANSPAIR_CHECKED = "background-color: rgba(30, 147, 229, 20%);"
@@ -303,20 +304,106 @@ def setPlainTextAndKeepUndoStack(self, text: str):
303304
class TransTextEdit(SourceTextEdit):
304305
pass
305306

307+
class RowIndexEditor(QLineEdit):
306308

309+
focus_out = Signal()
310+
311+
def __init__(self, parent=None):
312+
super().__init__(parent=parent)
313+
self.setValidator(QIntValidator())
314+
self.setReadOnly(True)
315+
self.setTextMargins(0, 0, 0, 0)
316+
317+
def focusOutEvent(self, e: QFocusEvent) -> None:
318+
super().focusOutEvent(e)
319+
self.focus_out.emit()
320+
321+
def sizeHint(self):
322+
size = super().sizeHint()
323+
return QSize(1, size.height())
324+
325+
326+
class RowIndexLabel(QStackedWidget):
327+
328+
submmit_idx = Signal(int)
329+
330+
def __init__(self, text: str = None, parent=None):
331+
super().__init__(parent=parent)
332+
self.lineedit = RowIndexEditor(parent=self)
333+
self.lineedit.focus_out.connect(self.on_lineedit_focusout)
334+
335+
self.show_label = QLabel(self)
336+
self.text = self.show_label.text
337+
338+
self.addWidget(self.show_label)
339+
self.addWidget(self.lineedit)
340+
self.setCurrentIndex(0)
341+
342+
if text is not None:
343+
self.setText(text)
344+
self.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Maximum)
345+
346+
def setText(self, text):
347+
if isinstance(text, int):
348+
text = str(text)
349+
self.show_label.setText(text)
350+
self.lineedit.setText(text)
351+
352+
def keyPressEvent(self, e: QKeyEvent) -> None:
353+
super().keyPressEvent(e)
307354

355+
key = e.key()
356+
if key == Qt.Key.Key_Return:
357+
self.try_update_idx()
358+
359+
def try_update_idx(self):
360+
idx_str = self.lineedit.text().strip()
361+
if not idx_str:
362+
return
363+
if self.text() == idx_str:
364+
return
365+
try:
366+
idx = int(idx_str)
367+
self.lineedit.setReadOnly(True)
368+
self.submmit_idx.emit(idx)
369+
370+
except Exception as e:
371+
LOGGER.warning(f'Invalid index str: {idx}')
372+
373+
def mouseDoubleClickEvent(self, e: QMouseEvent) -> None:
374+
self.startEdit()
375+
return super().mouseDoubleClickEvent(e)
376+
377+
def startEdit(self) -> None:
378+
self.setCurrentIndex(1)
379+
self.lineedit.setReadOnly(False)
380+
self.lineedit.setFocus()
381+
382+
def on_lineedit_focusout(self):
383+
edited = not self.lineedit.isReadOnly()
384+
self.lineedit.setReadOnly(True)
385+
self.setCurrentIndex(0)
386+
if edited:
387+
self.try_update_idx()
388+
389+
def mousePressEvent(self, e: QMouseEvent) -> None:
390+
e.ignore()
391+
return super().mousePressEvent(e)
392+
308393

309394
class TransPairWidget(Widget):
310395

311396
check_state_changed = Signal(object, bool, bool)
312397
drag_move = Signal(int)
398+
idx_edited = Signal(int, int)
313399

314400
def __init__(self, textblock: TextBlock = None, idx: int = None, fold: bool = False, *args, **kwargs) -> None:
315401
super().__init__(*args, **kwargs)
316402
self.e_source = SourceTextEdit(idx, self, fold)
317403
self.e_trans = TransTextEdit(idx, self, fold)
318-
self.idx_label = IgnoreMouseLabel(self)
404+
self.idx_label = RowIndexLabel(idx, self)
319405
self.idx_label.setText(str(idx + 1).zfill(2)) # showed index start from 1!
406+
self.submmit_idx = self.idx_label.submmit_idx.connect(self.on_idx_edited)
320407
self.textblock = textblock
321408
self.idx = idx
322409
self.checked = False
@@ -339,6 +426,10 @@ def __init__(self, textblock: TextBlock = None, idx: int = None, fold: bool = Fa
339426

340427
self.setAcceptDrops(True)
341428

429+
def on_idx_edited(self, new_idx: int):
430+
new_idx -= 1
431+
self.idx_edited.emit(self.idx, new_idx)
432+
342433
def dragEnterEvent(self, e: QDragEnterEvent) -> None:
343434
if isinstance(e.source(), TransPairWidget):
344435
e.accept()
@@ -369,7 +460,7 @@ def _set_checked_state(self, checked: bool):
369460
else:
370461
self.setStyleSheet("")
371462

372-
def mouseReleaseEvent(self, e: QMouseEvent) -> None:
463+
def update_checkstate_by_mousevent(self, e: QMouseEvent):
373464
if e.button() == Qt.MouseButton.LeftButton:
374465
modifiers = e.modifiers()
375466
if modifiers & Qt.KeyboardModifier.ShiftModifier and modifiers & Qt.KeyboardModifier.ControlModifier:
@@ -378,7 +469,15 @@ def mouseReleaseEvent(self, e: QMouseEvent) -> None:
378469
shift_pressed = modifiers == Qt.KeyboardModifier.ShiftModifier
379470
ctrl_pressed = modifiers == Qt.KeyboardModifier.ControlModifier
380471
self.check_state_changed.emit(self, shift_pressed, ctrl_pressed)
381-
return super().mouseReleaseEvent(e)
472+
473+
def mousePressEvent(self, e: QMouseEvent) -> None:
474+
if not self.checked:
475+
self.update_checkstate_by_mousevent(e)
476+
return super().mousePressEvent(e)
477+
478+
def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
479+
print("double click")
480+
return super().mouseDoubleClickEvent(event)
382481

383482
def updateIndex(self, idx: int):
384483
if self.idx != idx:
@@ -464,7 +563,21 @@ def mouseMoveEvent(self, e: QMouseEvent) -> None:
464563
new_maps = np.where(drags != new_pos)
465564
if len(new_maps) == 0:
466565
return
566+
467567
drags_ori, drags_tgt = drags[new_maps], new_pos[new_maps]
568+
result_list = list(range(len(self.pairwidget_list)))
569+
to_insert = []
570+
for ii, src_idx in enumerate(drags_ori):
571+
pos = src_idx - ii
572+
to_insert.append(result_list.pop(pos))
573+
for ii, tgt_idx in enumerate(drags_tgt):
574+
result_list.insert(tgt_idx, to_insert[ii])
575+
drags_ori, drags_tgt = [], []
576+
for ii, idx in enumerate(result_list):
577+
if ii != idx:
578+
drags_ori.append(idx)
579+
drags_tgt.append(ii)
580+
468581
self.rearrange_blks.emit((drags_ori, drags_tgt))
469582

470583
return super().mouseMoveEvent(e)
@@ -486,12 +599,14 @@ def set_drag_style(self, pos: int, clear_style: bool = False):
486599
def clearDrag(self):
487600
self.drag_to_pos = -1
488601
if self.drag is not None:
489-
self.drag.cancel()
602+
try:
603+
self.drag.cancel()
604+
except RuntimeError:
605+
pass
490606
self.drag = None
491607

492608
def dragMoveEvent(self, e: QDragMoveEvent) -> None:
493609
e.accept()
494-
e.position()
495610
return super().dragMoveEvent(e)
496611

497612
def dragEnterEvent(self, e: QDragEnterEvent):
@@ -508,7 +623,27 @@ def handle_drag_pos(self, to_pos: int):
508623
self.set_drag_style(self.drag_to_pos, True)
509624
self.drag_to_pos = to_pos
510625
self.set_drag_style(to_pos)
626+
627+
def on_idx_edited(self, src_idx: int, tgt_idx: int):
628+
src_idx_ori = tgt_idx
629+
tgt_idx = max(min(tgt_idx, len(self.pairwidget_list) - 1), 0)
630+
if src_idx_ori != tgt_idx:
631+
self.pairwidget_list[src_idx].idx_label.setText(str(src_idx + 1).zfill(2))
632+
if src_idx == tgt_idx:
633+
return
634+
ids_ori, ids_tgt = [src_idx], [tgt_idx]
511635

636+
if src_idx < tgt_idx:
637+
for idx in range(src_idx+1, tgt_idx+1):
638+
ids_ori.append(idx)
639+
ids_tgt.append(idx-1)
640+
else:
641+
for idx in range(tgt_idx, src_idx):
642+
ids_ori.append(idx)
643+
ids_tgt.append(idx+1)
644+
self.rearrange_blks.emit((ids_ori, ids_tgt, (tgt_idx, src_idx)))
645+
# self.ensureVisible
646+
512647
def addPairWidget(self, pairwidget: TransPairWidget):
513648
self.vlayout.insertWidget(pairwidget.idx, pairwidget)
514649
pairwidget.check_state_changed.connect(self.on_widget_checkstate_changed)
@@ -612,7 +747,6 @@ def set_selected_list(self, selection_indices: List):
612747
if idx == 0:
613748
self.sel_anchor_widget = pw
614749

615-
616750
def clearAllSelected(self, emit_signal=True):
617751
self.sel_anchor_widget = None
618752
if len(self.checked_list) > 0:

0 commit comments

Comments
 (0)