Skip to content

Commit

Permalink
ToC Edit tool: Add an undo button
Browse files Browse the repository at this point in the history
  • Loading branch information
kovidgoyal committed Apr 12, 2017
1 parent b0a721f commit 29ea838
Showing 1 changed file with 82 additions and 13 deletions.
95 changes: 82 additions & 13 deletions src/calibre/gui2/toc/main.py
Expand Up @@ -10,6 +10,7 @@
import sys, os, textwrap
from threading import Thread
from functools import partial
from future_builtins import map

from PyQt5.Qt import (QPushButton, QFrame, QMenu, QInputDialog,
QDialog, QVBoxLayout, QDialogButtonBox, QSize, QStackedWidget, QWidget,
Expand Down Expand Up @@ -354,12 +355,17 @@ def data_changed(self, item):
# }}}


NODE_FLAGS = (Qt.ItemIsDragEnabled|Qt.ItemIsEditable|Qt.ItemIsEnabled|
Qt.ItemIsSelectable|Qt.ItemIsDropEnabled)


class TreeWidget(QTreeWidget): # {{{

edit_item = pyqtSignal()

def __init__(self, parent):
QTreeWidget.__init__(self, parent)
self.history = []
self.setHeaderLabel(_('Table of Contents'))
self.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
self.setDragEnabled(True)
Expand All @@ -378,6 +384,13 @@ def __init__(self, parent):
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_context_menu)

def push_history(self):
self.history.append(self.serialize_tree())

def pop_history(self):
if self.history:
self.unserialize_tree(self.history.pop())

def iteritems(self, parent=None):
if parent is None:
parent = self.invisibleRootItem()
Expand All @@ -387,8 +400,54 @@ def iteritems(self, parent=None):
for gc in self.iteritems(parent=child):
yield gc

def update_status_tip(self, item):
c = item.data(0, Qt.UserRole)
if c is not None:
frag = c.frag or ''
if frag:
frag = '#'+frag
item.setStatusTip(0, _('<b>Title</b>: {0} <b>Dest</b>: {1}{2}').format(
c.title, c.dest, frag))

def serialize_tree(self):

def serialize_node(node):
return {
'title': node.data(0, Qt.DisplayRole),
'toc_node': node.data(0, Qt.UserRole),
'icon': node.data(0, Qt.DecorationRole),
'tooltip': node.data(0, Qt.ToolTipRole),
'is_selected': node.isSelected(),
'is_expanded': node.isExpanded(),
'children': list(map(serialize_node, (node.child(i) for i in range(node.childCount())))),
}

node = self.invisibleRootItem()
return {'children': list(map(serialize_node, (node.child(i) for i in range(node.childCount()))))}

def unserialize_tree(self, serialized):

def unserialize_node(dict_node, parent):
n = QTreeWidgetItem(parent)
n.setData(0, Qt.DisplayRole, dict_node['title'])
n.setData(0, Qt.UserRole, dict_node['toc_node'])
n.setFlags(NODE_FLAGS)
n.setData(0, Qt.DecorationRole, dict_node['icon'])
n.setData(0, Qt.ToolTipRole, dict_node['tooltip'])
self.update_status_tip(n)
n.setExpanded(dict_node['is_expanded'])
n.setSelected(dict_node['is_selected'])
for c in dict_node['children']:
unserialize_node(c, n)

i = self.invisibleRootItem()
i.takeChildren()
for child in serialized['children']:
unserialize_node(child, i)

def dropEvent(self, event):
self.in_drop_event = True
self.push_history()
try:
super(TreeWidget, self).dropEvent(event)
finally:
Expand Down Expand Up @@ -418,6 +477,7 @@ def check_multi_selection(self):
def move_left(self):
if not self.check_multi_selection():
return
self.push_history()
item = self.currentItem()
if item is not None:
parent = item.parent()
Expand All @@ -437,6 +497,7 @@ def move_left(self):
def move_right(self):
if not self.check_multi_selection():
return
self.push_history()
item = self.currentItem()
if item is not None:
parent = item.parent() or self.invisibleRootItem()
Expand All @@ -453,6 +514,7 @@ def move_right(self):
def move_down(self):
if not self.check_multi_selection():
return
self.push_history()
item = self.currentItem()
if item is None:
if self.root.childCount() == 0:
Expand All @@ -478,6 +540,7 @@ def move_down(self):
def move_up(self):
if not self.check_multi_selection():
return
self.push_history()
item = self.currentItem()
if item is None:
if self.root.childCount() == 0:
Expand All @@ -501,17 +564,20 @@ def move_up(self):
self.highlight_item(item)

def del_items(self):
self.push_history()
for item in self.selectedItems():
p = item.parent() or self.root
p.removeChild(item)

def title_case(self):
self.push_history()
from calibre.utils.titlecase import titlecase
for item in self.selectedItems():
t = unicode(item.data(0, Qt.DisplayRole) or '')
item.setData(0, Qt.DisplayRole, titlecase(t))

def upper_case(self):
self.push_history()
for item in self.selectedItems():
t = unicode(item.data(0, Qt.DisplayRole) or '')
item.setData(0, Qt.DisplayRole, icu_upper(t))
Expand All @@ -523,6 +589,7 @@ def bulk_rename(self):
fmt, num = get_bulk_rename_settings(self, len(items), prefix=_('Chapter '), msg=_(
'All selected items will be renamed to the form prefix-number'), sanitize=lambda x:x, leading_zeros=False)
if fmt is not None and num is not None:
self.push_history()
for i, item in enumerate(items):
item.setData(0, Qt.DisplayRole, fmt % (num + i))

Expand Down Expand Up @@ -663,6 +730,7 @@ def del_items(self):
def delete_current_item(self):
item = self.tocw.currentItem()
if item is not None:
self.tocw.push_history()
p = item.parent() or self.root
p.removeChild(item)

Expand All @@ -671,6 +739,7 @@ def iteritems(self, parent=None):
yield item

def flatten_toc(self):
self.tocw.push_history()
found = True
while found:
found = False
Expand All @@ -681,6 +750,7 @@ def flatten_toc(self):
break

def flatten_item(self):
self.tocw.push_history()
self._flatten_item(self.tocw.currentItem())

def _flatten_item(self, item):
Expand All @@ -704,15 +774,6 @@ def move_up(self):
def move_down(self):
self.tocw.move_down()

def update_status_tip(self, item):
c = item.data(0, Qt.UserRole)
if c is not None:
frag = c.frag or ''
if frag:
frag = '#'+frag
item.setStatusTip(0, _('<b>Title</b>: {0} <b>Dest</b>: {1}{2}').format(
c.title, c.dest, frag))

def data_changed(self, top_left, bottom_right):
for r in xrange(top_left.row(), bottom_right.row()+1):
idx = self.tocw.model().index(r, 0, top_left.parent())
Expand All @@ -721,7 +782,7 @@ def data_changed(self, top_left, bottom_right):
if toc is not None:
toc.title = new_title or _('(Untitled)')
item = self.tocw.itemFromIndex(idx)
self.update_status_tip(item)
self.tocw.update_status_tip(item)
self.item_view.data_changed(item)

def create_item(self, parent, child, idx=-1):
Expand All @@ -736,8 +797,7 @@ def create_item(self, parent, child, idx=-1):
def populate_item(self, c, child):
c.setData(0, Qt.DisplayRole, child.title or _('(Untitled)'))
c.setData(0, Qt.UserRole, child)
c.setFlags(Qt.ItemIsDragEnabled|Qt.ItemIsEditable|Qt.ItemIsEnabled|
Qt.ItemIsSelectable|Qt.ItemIsDropEnabled)
c.setFlags(NODE_FLAGS)
c.setData(0, Qt.DecorationRole, self.icon_map[child.dest_exists])
if child.dest_exists is False:
c.setData(0, Qt.ToolTipRole, _(
Expand All @@ -746,7 +806,7 @@ def populate_item(self, c, child):
else:
c.setData(0, Qt.ToolTipRole, None)

self.update_status_tip(c)
self.tocw.update_status_tip(c)

def __call__(self, ebook):
self.ebook = ebook
Expand Down Expand Up @@ -780,6 +840,7 @@ def update_item(self, item, where, name, frag, title):
frag = add_id(self.ebook, name, *frag)
child = TOC(title, name, frag)
child.dest_exists = True
self.tocw.push_history()
if item is None:
# New entry at root level
c = self.create_item(self.root, child)
Expand Down Expand Up @@ -825,6 +886,7 @@ def process_node(root, tocparent, added):
added.append(item)
process_node(item, child, added)

self.tocw.push_history()
nodes = []
process_node(self.root, toc, nodes)
self.highlight_item(nodes[0])
Expand All @@ -850,6 +912,9 @@ def create_from_files(self):
_('No files were found that could be added to the Table of Contents.'), show=True)
self.insert_toc_fragment(toc)

def undo(self):
self.tocw.pop_history()


# }}}

Expand Down Expand Up @@ -897,6 +962,10 @@ def __init__(self, pathtobook, title=None, parent=None, prefs=None):
l.addWidget(bb)
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
self.undo_button = b = bb.addButton(_('&Undo'), bb.ActionRole)
b.setToolTip(_('Undo the last action, if any'))
b.setIcon(QIcon(I('edit-undo.png')))
b.clicked.connect(self.toc_view.undo)

self.explode_done.connect(self.read_toc, type=Qt.QueuedConnection)
self.writing_done.connect(self.really_accept, type=Qt.QueuedConnection)
Expand Down

0 comments on commit 29ea838

Please sign in to comment.