Skip to content

Commit f25db99

Browse files
committed
Merge branch 'feat-toc-multi-move' of https://github.com/midrare/calibre
2 parents e4ab29a + d2deef5 commit f25db99

File tree

1 file changed

+207
-82
lines changed

1 file changed

+207
-82
lines changed

src/calibre/gui2/toc/main.py

+207-82
Original file line numberDiff line numberDiff line change
@@ -532,102 +532,227 @@ def highlight_item(self, item):
532532
self.setCurrentItem(item, 0, QItemSelectionModel.SelectionFlag.ClearAndSelect)
533533
self.scrollToItem(item)
534534

535-
def check_multi_selection(self):
536-
if len(self.selectedItems()) > 1:
537-
info_dialog(self, _('Multiple items selected'), _(
538-
'You are trying to move multiple items at once, this is not supported. Instead use'
539-
' Drag and Drop to move multiple items'), show=True)
540-
return False
541-
return True
535+
def _sort_items_by_index(self, items, reverse=False):
536+
def get_index(item):
537+
result = []
542538

543-
def move_left(self):
544-
if not self.check_multi_selection():
539+
parent = item.parent()
540+
while parent is not None:
541+
result.append(parent.indexOfChild(item))
542+
item, parent = parent, parent.parent()
543+
544+
result.reverse()
545+
return result
546+
547+
items.sort(key=get_index, reverse=reverse)
548+
549+
def _get_root_items(self, items):
550+
items_ = []
551+
for item in items:
552+
if item is None:
553+
continue
554+
555+
parent = item
556+
while (parent := parent.parent()) is not None:
557+
if parent in items:
558+
break
559+
else:
560+
items_.append(item)
561+
562+
return items_
563+
564+
def _move_indent_left(self, items):
565+
self._sort_items_by_index(items)
566+
567+
for item in items:
568+
old_parent = item.parent()
569+
if old_parent is None:
570+
return
571+
572+
old_index = old_parent.indexOfChild(item)
573+
was_expanded = item.isExpanded() or item.childCount() == 0
574+
575+
new_parent = old_parent.parent() or self.invisibleRootItem()
576+
new_index = new_parent.indexOfChild(old_parent) + 1
577+
578+
# all former lower siblings become children of indented item
579+
for _ in range(old_parent.childCount() - old_index - 1):
580+
lower_sibling = old_parent.child(old_index+1)
581+
old_parent.removeChild(lower_sibling)
582+
item.addChild(lower_sibling)
583+
584+
if lower_sibling not in items:
585+
was_expanded = True
586+
587+
old_parent.removeChild(item)
588+
new_parent.insertChild(new_index, item)
589+
590+
self.expandItem(new_parent)
591+
if was_expanded:
592+
self.expandItem(item)
593+
594+
def _move_indent_right(self, items):
595+
self._sort_items_by_index(items)
596+
597+
failed_parent = None
598+
for item in items:
599+
# indent right == become child of upper sibling
600+
old_parent = item.parent() or self.invisibleRootItem()
601+
old_idx = old_parent.indexOfChild(item)
602+
was_expanded = item.isExpanded()
603+
604+
if old_idx <= 0:
605+
# no upper sibling exists; cannot become child
606+
failed_parent = old_parent
607+
continue
608+
elif failed_parent and old_parent is failed_parent:
609+
# this prevents siblings at the same level from
610+
# nesting into each other forming a "staircase"
611+
continue
612+
else:
613+
failed_parent = None
614+
615+
new_parent = old_parent.child(old_idx-1)
616+
old_parent.removeChild(item)
617+
new_parent.addChild(item)
618+
619+
self.expandItem(new_parent)
620+
if was_expanded:
621+
self.expandItem(item)
622+
623+
def _move_indent(self, items, indent):
624+
if not items or indent == 0:
545625
return
626+
627+
# indent offsets are absolute (as opposed to relative to parent)
628+
# child indented with parent automatically, no need for manual
629+
items_ = self._get_root_items(items)
630+
if len(items_) <= 0 or indent == 0:
631+
return
632+
633+
focus_item = self.currentItem()
634+
546635
self.push_history()
547-
item = self.currentItem()
548-
if item is not None:
549-
parent = item.parent()
550-
if parent is not None:
551-
is_expanded = item.isExpanded() or item.childCount() == 0
552-
gp = parent.parent() or self.invisibleRootItem()
553-
idx = gp.indexOfChild(parent)
554-
for gc in [parent.child(i) for i in range(parent.indexOfChild(item)+1, parent.childCount())]:
555-
parent.removeChild(gc)
556-
item.addChild(gc)
557-
parent.removeChild(item)
558-
gp.insertChild(idx+1, item)
559-
if is_expanded:
560-
self.expandItem(item)
561-
self.highlight_item(item)
636+
if indent < 0:
637+
self._move_indent_left(items_)
638+
elif indent > 0:
639+
self._move_indent_right(items_)
640+
641+
# restore previous focused item
642+
if focus_item is None and items_:
643+
focus_item = items_[0]
644+
645+
if focus_item:
646+
self.setCurrentItem(
647+
focus_item,
648+
0,
649+
QItemSelectionModel.SelectionFlag.ClearAndSelect
650+
)
651+
652+
for item in items:
653+
item.setSelected(True)
654+
655+
def move_left(self):
656+
selected_items = self.selectedItems()
657+
if len(selected_items) <= 0:
658+
return
659+
660+
self._move_indent(selected_items, -1)
562661

563662
def move_right(self):
564-
if not self.check_multi_selection():
663+
selected_items = self.selectedItems()
664+
if len(selected_items) <= 0:
565665
return
566-
self.push_history()
567-
item = self.currentItem()
568-
if item is not None:
569-
parent = item.parent() or self.invisibleRootItem()
570-
idx = parent.indexOfChild(item)
571-
if idx > 0:
572-
is_expanded = item.isExpanded()
573-
np = parent.child(idx-1)
574-
parent.removeChild(item)
575-
np.addChild(item)
576-
if is_expanded:
577-
self.expandItem(item)
578-
self.highlight_item(item)
666+
667+
self._move_indent(selected_items, 1)
579668

580669
def move_down(self):
581-
if not self.check_multi_selection():
670+
selected_items = self.selectedItems()
671+
if len(selected_items) <= 0:
582672
return
673+
583674
self.push_history()
584-
item = self.currentItem()
585-
if item is None:
586-
if self.root.childCount() == 0:
587-
return
588-
item = self.root.child(0)
589-
self.highlight_item(item)
590-
return
591-
parent = item.parent() or self.root
592-
idx = parent.indexOfChild(item)
593-
if idx == parent.childCount() - 1:
594-
# At end of parent, need to become sibling of parent
595-
if parent is self.root:
596-
return
597-
gp = parent.parent() or self.root
598-
parent.removeChild(item)
599-
gp.insertChild(gp.indexOfChild(parent)+1, item)
600-
else:
601-
sibling = parent.child(idx+1)
602-
parent.removeChild(item)
603-
sibling.insertChild(0, item)
604-
self.highlight_item(item)
675+
676+
items_ = self._get_root_items(selected_items)
677+
self._sort_items_by_index(items_)
678+
679+
focus_item = self.currentItem()
680+
681+
for item in reversed(items_):
682+
old_parent = item.parent() or self.invisibleRootItem()
683+
old_index = old_parent.indexOfChild(item)
684+
was_expanded = item.isExpanded() or item.childCount() == 0
685+
686+
new_parent = None
687+
if old_index + 1 < old_parent.childCount():
688+
# there is still space in parent; move down in same parent
689+
old_parent.removeChild(item)
690+
old_parent.insertChild(old_index + 1, item)
691+
elif old_parent is not self.invisibleRootItem():
692+
# move down past bottom of parent, become child of grandparent
693+
new_parent = old_parent.parent() or self.invisibleRootItem()
694+
old_parent.removeChild(item)
695+
new_index = new_parent.indexOfChild(old_parent) + 1
696+
new_parent.insertChild(new_index, item)
697+
self.expandItem(new_parent)
698+
699+
self.expandItem(new_parent or old_parent)
700+
if was_expanded:
701+
self.expandItem(item)
702+
703+
if focus_item:
704+
self.setCurrentItem(
705+
focus_item,
706+
0,
707+
QItemSelectionModel.SelectionFlag.ClearAndSelect
708+
)
709+
710+
for item in selected_items:
711+
item.setSelected(True)
605712

606713
def move_up(self):
607-
if not self.check_multi_selection():
714+
selected_items = self.selectedItems()
715+
if len(selected_items) <= 0:
608716
return
717+
609718
self.push_history()
610-
item = self.currentItem()
611-
if item is None:
612-
if self.root.childCount() == 0:
613-
return
614-
item = self.root.child(self.root.childCount()-1)
615-
self.highlight_item(item)
616-
return
617-
parent = item.parent() or self.root
618-
idx = parent.indexOfChild(item)
619-
if idx == 0:
620-
# At end of parent, need to become sibling of parent
621-
if parent is self.root:
622-
return
623-
gp = parent.parent() or self.root
624-
parent.removeChild(item)
625-
gp.insertChild(gp.indexOfChild(parent), item)
626-
else:
627-
sibling = parent.child(idx-1)
628-
parent.removeChild(item)
629-
sibling.addChild(item)
630-
self.highlight_item(item)
719+
720+
items_ = self._get_root_items(selected_items)
721+
self._sort_items_by_index(items_)
722+
723+
focus_item = self.currentItem()
724+
725+
for item in items_:
726+
old_parent = item.parent() or self.invisibleRootItem()
727+
old_index = old_parent.indexOfChild(item)
728+
was_expanded = item.isExpanded() or item.childCount() == 0
729+
730+
new_parent = None
731+
if old_index - 1 >= 0:
732+
# there is still space in parent; move up within parent
733+
old_parent.removeChild(item)
734+
old_parent.insertChild(old_index - 1, item)
735+
elif old_parent is not self.invisibleRootItem():
736+
# move up past top of parent, become upper sibling of parent
737+
new_parent = old_parent.parent() or self.invisibleRootItem()
738+
old_parent.removeChild(item)
739+
new_index = new_parent.indexOfChild(old_parent)
740+
new_parent.insertChild(new_index, item)
741+
self.expandItem(new_parent)
742+
743+
self.expandItem(new_parent or old_parent)
744+
if was_expanded:
745+
self.expandItem(item)
746+
747+
if focus_item:
748+
self.setCurrentItem(
749+
focus_item,
750+
0,
751+
QItemSelectionModel.SelectionFlag.ClearAndSelect
752+
)
753+
754+
for item in selected_items:
755+
item.setSelected(True)
631756

632757
def del_items(self):
633758
self.push_history()

0 commit comments

Comments
 (0)