From 4410536f694d36014b9742a1c48ea11744434459 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 27 Sep 2014 22:56:50 +0200 Subject: [PATCH] Refactor :undo and save/restore history. --- qutebrowser/browser/commands.py | 7 +++--- qutebrowser/utils/qtutils.py | 33 +++++++++++++++++++++++++++- qutebrowser/widgets/tabbedbrowser.py | 19 ++++++++++++---- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 5f27bc5fa..2b7b90ac5 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -511,10 +511,9 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher') def undo(self): """Re-open a closed tab (optionally skipping [count] closed tabs).""" - url_stack = objreg.get('url-stack', None) - if url_stack: - objreg.get('tabbed-browser').tabopen(url_stack.pop()) - else: + try: + objreg.get('tabbed-browser').undo() + except IndexError: raise cmdexc.CommandError("Nothing to undo!") @cmdutils.register(instance='command-dispatcher') diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index a2a3c4b6a..7f59e7a9e 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -32,7 +32,8 @@ import sys import operator from distutils.version import StrictVersion as Version -from PyQt5.QtCore import QEventLoop, qVersion +from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray, + QIODevice) MAXVALS = { @@ -128,6 +129,36 @@ def ensure_valid(obj): raise QtValueError(obj) +def _check_qdatastream(stream): + """Check the status of a QDataStream and raise IOError if it's not ok.""" + status_to_str = { + QDataStream.Ok: "The data stream is operating normally.", + QDataStream.ReadPastEnd: ("The data stream has read past the end of " + "the data in the underlying device."), + QDataStream.ReadCorruptData: "The data stream has read corrupt data.", + QDataStream.WriteFailed: ("The data stream cannot write to the " + "underlying device."), + } + if stream.status() != QDataStream.Ok: + raise IOError(status_to_str[stream.status()]) + + +def serialize(obj): + """Serialize an object into a QByteArray.""" + data = QByteArray() + stream = QDataStream(data, QIODevice.WriteOnly) + stream << obj + _check_qdatastream(stream) + return data + + +def deserialize(data, obj): + """Deserialize an object from a QByteArray.""" + stream = QDataStream(data, QIODevice.ReadOnly) + stream >> obj + _check_qdatastream(stream) + + class QtValueError(ValueError): """Exception which gets raised by ensure_valid.""" diff --git a/qutebrowser/widgets/tabbedbrowser.py b/qutebrowser/widgets/tabbedbrowser.py index 54dfb7c34..b969d3bf4 100644 --- a/qutebrowser/widgets/tabbedbrowser.py +++ b/qutebrowser/widgets/tabbedbrowser.py @@ -20,6 +20,7 @@ """The main tabbed browser widget.""" import functools +import collections from PyQt5.QtWidgets import QSizePolicy from PyQt5.QtCore import pyqtSignal, pyqtSlot, QSize, QTimer, QUrl @@ -34,6 +35,9 @@ from qutebrowser.browser import signalfilter, commands from qutebrowser.utils import log, message, usertypes, utils, qtutils, objreg +UndoEntry = collections.namedtuple('UndoEntry', ['url', 'history']) + + class TabbedBrowser(tabwidget.TabWidget): """A TabWidget with QWebViews inside. @@ -53,7 +57,7 @@ class TabbedBrowser(tabwidget.TabWidget): _tab_insert_idx_left: Where to insert a new tab with tabbar -> new-tab-position set to 'left'. _tab_insert_idx_right: Same as above, for 'right'. - _url_stack: Stack of URLs of closed tabs. + _undo_stack: List of UndoEntry namedtuples of closed tabs. Signals: cur_progress: Progress of the current tab changed (loadProgress). @@ -102,8 +106,7 @@ class TabbedBrowser(tabwidget.TabWidget): self.cur_load_started.connect(self.on_cur_load_started) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._tabs = [] - self._url_stack = [] - objreg.register('url-stack', self._url_stack) + self._undo_stack = [] self._filter = signalfilter.SignalFilter(self) dispatcher = commands.CommandDispatcher() objreg.register('command-dispatcher', dispatcher) @@ -244,12 +247,20 @@ class TabbedBrowser(tabwidget.TabWidget): objreg.delete('last-focused-tab') if not tab.cur_url.isEmpty(): qtutils.ensure_valid(tab.cur_url) - self._url_stack.append(tab.cur_url) + history_data = qtutils.serialize(tab.history()) + entry = UndoEntry(tab.cur_url, history_data) + self._undo_stack.append(entry) tab.shutdown() self._tabs.remove(tab) self.removeTab(idx) tab.deleteLater() + def undo(self): + """Undo removing of a tab.""" + url, history_data = self._undo_stack.pop() + newtab = self.tabopen(url) + qtutils.deserialize(history_data, newtab.history()) + @pyqtSlot('QUrl', bool) def openurl(self, url, newtab): """Open a URL, used as a slot.