Fix #780484 (Intermittent crash when disconnect with dialog open)

This commit is contained in:
Kovid Goyal 2011-05-18 20:02:00 -06:00
parent 5317f8bb9c
commit b0c0d40e6f
6 changed files with 336 additions and 273 deletions

309
src/calibre/gui2/bars.py Normal file
View File

@ -0,0 +1,309 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QObject, QToolBar, Qt, QSize, QToolButton, QVBoxLayout,
QLabel, QWidget, QAction, QMenuBar, QMenu)
from calibre.constants import isosx
from calibre.gui2 import gprefs
class ToolBar(QToolBar): # {{{
def __init__(self, donate, location_manager, parent):
QToolBar.__init__(self, parent)
self.setContextMenuPolicy(Qt.PreventContextMenu)
self.setMovable(False)
self.setFloatable(False)
self.setOrientation(Qt.Horizontal)
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
self.preferred_width = self.sizeHint().width()
self.gui = parent
self.donate_button = donate
self.added_actions = []
self.location_manager = location_manager
donate.setAutoRaise(True)
donate.setCursor(Qt.PointingHandCursor)
self.setAcceptDrops(True)
self.showing_donate = False
def resizeEvent(self, ev):
QToolBar.resizeEvent(self, ev)
style = self.get_text_style()
self.setToolButtonStyle(style)
if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'):
self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly)
def get_text_style(self):
style = Qt.ToolButtonTextUnderIcon
s = gprefs['toolbar_icon_size']
if s != 'off':
p = gprefs['toolbar_text']
if p == 'never':
style = Qt.ToolButtonIconOnly
elif p == 'auto' and self.preferred_width > self.width()+35:
style = Qt.ToolButtonIconOnly
return style
def contextMenuEvent(self, *args):
pass
def update_lm_actions(self):
for ac in self.added_actions:
if ac in self.location_manager.all_actions:
ac.setVisible(ac in self.location_manager.available_actions)
def init_bar(self, actions):
self.showing_donate = False
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.added_actions = []
bar = self
for what in actions:
if what is None:
bar.addSeparator()
elif what == 'Location Manager':
for ac in self.location_manager.all_actions:
bar.addAction(ac)
bar.added_actions.append(ac)
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
ac.setVisible(False)
elif what == 'Donate':
self.d_widget = QWidget()
self.d_widget.setLayout(QVBoxLayout())
self.d_widget.layout().addWidget(self.donate_button)
if isosx:
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
self.d_widget.layout().setContentsMargins(0,0,0,0)
self.d_widget.setContentsMargins(0,0,0,0)
self.d_widget.filler = QLabel(u'\u00a0')
self.d_widget.layout().addWidget(self.d_widget.filler)
bar.addWidget(self.d_widget)
self.showing_donate = True
elif what in self.gui.iactions:
action = self.gui.iactions[what]
bar.addAction(action.qaction)
self.added_actions.append(action.qaction)
self.setup_tool_button(bar, action.qaction, action.popup_type)
self.preferred_width = self.sizeHint().width()
def setup_tool_button(self, bar, ac, menu_mode=None):
ch = bar.widgetForAction(ac)
if ch is None:
ch = self.child_bar.widgetForAction(ac)
ch.setCursor(Qt.PointingHandCursor)
ch.setAutoRaise(True)
if ac.menu() is not None and menu_mode is not None:
ch.setPopupMode(menu_mode)
return ch
#support drag&drop from/to library from/to reader/card
def dragEnterEvent(self, event):
md = event.mimeData()
if md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device"):
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
allowed = False
md = event.mimeData()
#Drop is only allowed in the location manager widget's different from the selected one
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None:
if ( md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device") ) and \
w.geometry().contains(event.pos()) and \
isinstance(w, QToolButton) and not w.isChecked():
allowed = True
break
if allowed:
event.acceptProposedAction()
else:
event.ignore()
def dropEvent(self, event):
data = event.mimeData()
mime = 'application/calibre+from_library'
if data.hasFormat(mime):
ids = list(map(int, str(data.data(mime)).split()))
tgt = None
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None and w.geometry().contains(event.pos()):
tgt = ac.calibre_name
if tgt is not None:
if tgt == 'main':
tgt = None
self.gui.sync_to_device(tgt, False, send_ids=ids)
event.accept()
mime = 'application/calibre+from_device'
if data.hasFormat(mime):
paths = [unicode(u.toLocalFile()) for u in data.urls()]
if paths:
self.gui.iactions['Add Books'].add_books_from_device(
self.gui.current_view(), paths=paths)
event.accept()
# }}}
class MenuAction(QAction): # {{{
def __init__(self, clone, parent):
QAction.__init__(self, clone.text(), parent)
self.clone = clone
clone.changed.connect(self.clone_changed)
def clone_changed(self):
self.setText(self.clone.text())
# }}}
class MenuBar(QMenuBar): # {{{
def __init__(self, location_manager, parent):
QMenuBar.__init__(self, parent)
self.gui = parent
self.setNativeMenuBar(True)
self.location_manager = location_manager
self.added_actions = []
self.donate_action = QAction(_('Donate'), self)
self.donate_menu = QMenu()
self.donate_menu.addAction(self.gui.donate_action)
self.donate_action.setMenu(self.donate_menu)
def update_lm_actions(self):
for ac in self.added_actions:
if ac in self.location_manager.all_actions:
ac.setVisible(ac in self.location_manager.available_actions)
def init_bar(self, actions):
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.added_actions = []
for what in actions:
if what is None:
continue
elif what == 'Location Manager':
for ac in self.location_manager.all_actions:
ac = self.build_menu(ac)
self.addAction(ac)
self.added_actions.append(ac)
ac.setVisible(False)
elif what == 'Donate':
self.addAction(self.donate_action)
elif what in self.gui.iactions:
action = self.gui.iactions[what]
ac = self.build_menu(action.qaction)
self.addAction(ac)
self.added_actions.append(ac)
def build_menu(self, action):
m = action.menu()
ac = MenuAction(action, self)
if m is None:
m = QMenu()
m.addAction(action)
ac.setMenu(m)
return ac
# }}}
class BarsManager(QObject):
def __init__(self, donate_button, location_manager, parent):
QObject.__init__(self, parent)
self.donate_button, self.location_manager = (donate_button,
location_manager)
bars = [ToolBar(donate_button, location_manager, parent) for i in
range(3)]
self.main_bars = tuple(bars[:2])
self.child_bars = tuple(bars[2:])
self.apply_settings()
self.init_bars()
def database_changed(self, db):
pass
@property
def bars(self):
for x in self.main_bars + self.child_bars:
yield x
@property
def showing_donate(self):
for b in self.bars:
if b.isVisible() and b.showing_donate:
return True
return False
def init_bars(self):
self.bar_actions = tuple(
[gprefs['action-layout-toolbar'+x] for x in ('', '-device')] +
[gprefs['action-layout-toolbar-child']] +
[gprefs['action-layout-menubar']] +
[gprefs['action-layout-menubar-device']]
)
for bar, actions in zip(self.bars, self.bar_actions[:3]):
bar.init_bar(actions)
def update_bars(self):
showing_device = self.location_manager.has_device
main_bar = self.main_bars[1 if showing_device else 0]
child_bar = self.child_bars[0]
for bar in self.bars:
bar.setVisible(False)
bar.update_lm_actions()
if main_bar.added_actions:
main_bar.setVisible(True)
if child_bar.added_actions:
child_bar.setVisible(True)
self.menu_bar = MenuBar(self.location_manager, self.parent())
self.menu_bar.init_bar(self.bar_actions[4 if showing_device else 3])
self.menu_bar.update_lm_actions()
self.menu_bar.setVisible(bool(self.menu_bar.added_actions))
self.parent().setMenuBar(self.menu_bar)
def apply_settings(self):
sz = gprefs['toolbar_icon_size']
sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz]
style = Qt.ToolButtonTextUnderIcon
if sz > 0 and gprefs['toolbar_text'] == 'never':
style = Qt.ToolButtonIconOnly
for bar in self.bars:
bar.setIconSize(QSize(sz, sz))
bar.setToolButtonStyle(style)
self.donate_button.set_normal_icon_size(sz, sz)

View File

@ -751,6 +751,7 @@ class DeviceMixin(object): # {{{
if self.current_view() != self.library_view: if self.current_view() != self.library_view:
self.book_details.reset_info() self.book_details.reset_info()
self.location_manager.update_devices() self.location_manager.update_devices()
self.bars_manager.update_bars()
self.library_view.set_device_connected(self.device_connected) self.library_view.set_device_connected(self.device_connected)
self.refresh_ondevice() self.refresh_ondevice()
device_signals.device_connection_changed.emit(connected) device_signals.device_connection_changed.emit(connected)
@ -1198,6 +1199,7 @@ class DeviceMixin(object): # {{{
cp, fs = job.result cp, fs = job.result
self.location_manager.update_devices(cp, fs, self.location_manager.update_devices(cp, fs,
self.device_manager.device.icon) self.device_manager.device.icon)
self.bars_manager.update_bars()
# reset the views so that up-to-date info is shown. These need to be # reset the views so that up-to-date info is shown. These need to be
# here because some drivers update collections in sync_booklists # here because some drivers update collections in sync_booklists
self.memory_view.reset() self.memory_view.reset()

View File

@ -7,15 +7,15 @@ __docformat__ = 'restructuredtext en'
from functools import partial from functools import partial
from PyQt4.Qt import (QIcon, Qt, QWidget, QToolBar, QSize, from PyQt4.Qt import (QIcon, Qt, QWidget, QSize,
pyqtSignal, QToolButton, QMenu, QMenuBar, QAction, pyqtSignal, QToolButton, QMenu,
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup) QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup)
from calibre.constants import __appname__, isosx from calibre.constants import __appname__
from calibre.gui2.search_box import SearchBox2, SavedSearchBox from calibre.gui2.search_box import SearchBox2, SavedSearchBox
from calibre.gui2.throbber import ThrobbingButton from calibre.gui2.throbber import ThrobbingButton
from calibre.gui2 import gprefs from calibre.gui2.bars import BarsManager
from calibre.gui2.widgets import ComboBoxWithHelp from calibre.gui2.widgets import ComboBoxWithHelp
from calibre import human_readable from calibre import human_readable
@ -35,6 +35,8 @@ class LocationManager(QObject): # {{{
self._mem = [] self._mem = []
self.tooltips = {} self.tooltips = {}
self.all_actions = []
def ac(name, text, icon, tooltip): def ac(name, text, icon, tooltip):
icon = QIcon(I(icon)) icon = QIcon(I(icon))
ac = self.location_actions.addAction(icon, text) ac = self.location_actions.addAction(icon, text)
@ -59,6 +61,7 @@ class LocationManager(QObject): # {{{
ac.setMenu(m) ac.setMenu(m)
ac.calibre_name = name ac.calibre_name = name
self.all_actions.append(ac)
return ac return ac
self.library_action = ac('library', _('Library'), 'lt.png', self.library_action = ac('library', _('Library'), 'lt.png',
@ -234,259 +237,6 @@ class Spacer(QWidget): # {{{
self.l.addStretch(10) self.l.addStretch(10)
# }}} # }}}
class MenuAction(QAction): # {{{
def __init__(self, clone, parent):
QAction.__init__(self, clone.text(), parent)
self.clone = clone
clone.changed.connect(self.clone_changed)
def clone_changed(self):
self.setText(self.clone.text())
# }}}
class MenuBar(QMenuBar): # {{{
def __init__(self, location_manager, parent):
QMenuBar.__init__(self, parent)
self.gui = parent
self.setNativeMenuBar(True)
self.location_manager = location_manager
self.location_manager.locations_changed.connect(self.build_bar)
self.added_actions = []
self.donate_action = QAction(_('Donate'), self)
self.donate_menu = QMenu()
self.donate_menu.addAction(self.gui.donate_action)
self.donate_action.setMenu(self.donate_menu)
self.build_bar()
def build_bar(self, changed_action=None):
showing_device = self.location_manager.has_device
actions = '-device' if showing_device else ''
actions = gprefs['action-layout-menubar'+actions]
show_main = len(actions) > 0
self.setVisible(show_main)
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.added_actions = []
self.action_map = {}
for what in actions:
if what is None:
continue
elif what == 'Location Manager':
for ac in self.location_manager.available_actions:
ac = self.build_menu(ac)
self.addAction(ac)
self.added_actions.append(ac)
elif what == 'Donate':
self.addAction(self.donate_action)
elif what in self.gui.iactions:
action = self.gui.iactions[what]
ac = self.build_menu(action.qaction)
self.addAction(ac)
self.added_actions.append(ac)
def build_menu(self, action):
m = action.menu()
ac = MenuAction(action, self)
if m is None:
m = QMenu()
m.addAction(action)
ac.setMenu(m)
return ac
# }}}
class BaseToolBar(QToolBar): # {{{
def __init__(self, parent):
QToolBar.__init__(self, parent)
self.setContextMenuPolicy(Qt.PreventContextMenu)
self.setMovable(False)
self.setFloatable(False)
self.setOrientation(Qt.Horizontal)
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
self.preferred_width = self.sizeHint().width()
def resizeEvent(self, ev):
QToolBar.resizeEvent(self, ev)
style = self.get_text_style()
self.setToolButtonStyle(style)
if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'):
self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly)
def get_text_style(self):
style = Qt.ToolButtonTextUnderIcon
s = gprefs['toolbar_icon_size']
if s != 'off':
p = gprefs['toolbar_text']
if p == 'never':
style = Qt.ToolButtonIconOnly
elif p == 'auto' and self.preferred_width > self.width()+35:
style = Qt.ToolButtonIconOnly
return style
def contextMenuEvent(self, *args):
pass
# }}}
class ToolBar(BaseToolBar): # {{{
def __init__(self, donate, location_manager, child_bar, parent):
BaseToolBar.__init__(self, parent)
self.gui = parent
self.child_bar = child_bar
self.donate_button = donate
self.apply_settings()
self.location_manager = location_manager
self.location_manager.locations_changed.connect(self.build_bar)
donate.setAutoRaise(True)
donate.setCursor(Qt.PointingHandCursor)
self.added_actions = []
self.build_bar()
self.setAcceptDrops(True)
def apply_settings(self):
sz = gprefs['toolbar_icon_size']
sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz]
self.setIconSize(QSize(sz, sz))
self.child_bar.setIconSize(QSize(sz, sz))
style = Qt.ToolButtonTextUnderIcon
if sz > 0 and gprefs['toolbar_text'] == 'never':
style = Qt.ToolButtonIconOnly
self.setToolButtonStyle(style)
self.child_bar.setToolButtonStyle(style)
self.donate_button.set_normal_icon_size(sz, sz)
def build_bar(self):
self.showing_donate = False
showing_device = self.location_manager.has_device
mactions = '-device' if showing_device else ''
mactions = gprefs['action-layout-toolbar'+mactions]
cactions = gprefs['action-layout-toolbar-child']
show_main = len(mactions) > 0
self.setVisible(show_main)
show_child = len(cactions) > 0
self.child_bar.setVisible(show_child)
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.child_bar.clear()
self.added_actions = []
for bar, actions in ((self, mactions), (self.child_bar, cactions)):
for what in actions:
if what is None:
bar.addSeparator()
elif what == 'Location Manager':
for ac in self.location_manager.available_actions:
bar.addAction(ac)
bar.added_actions.append(ac)
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
elif what == 'Donate':
self.d_widget = QWidget()
self.d_widget.setLayout(QVBoxLayout())
self.d_widget.layout().addWidget(self.donate_button)
if isosx:
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
self.d_widget.layout().setContentsMargins(0,0,0,0)
self.d_widget.setContentsMargins(0,0,0,0)
self.d_widget.filler = QLabel(u'\u00a0')
self.d_widget.layout().addWidget(self.d_widget.filler)
bar.addWidget(self.d_widget)
self.showing_donate = True
elif what in self.gui.iactions:
action = self.gui.iactions[what]
bar.addAction(action.qaction)
self.added_actions.append(action.qaction)
self.setup_tool_button(bar, action.qaction, action.popup_type)
self.preferred_width = self.sizeHint().width()
self.child_bar.preferred_width = self.child_bar.sizeHint().width()
def setup_tool_button(self, bar, ac, menu_mode=None):
ch = bar.widgetForAction(ac)
if ch is None:
ch = self.child_bar.widgetForAction(ac)
ch.setCursor(Qt.PointingHandCursor)
ch.setAutoRaise(True)
if ac.menu() is not None and menu_mode is not None:
ch.setPopupMode(menu_mode)
return ch
def database_changed(self, db):
pass
#support drag&drop from/to library from/to reader/card
def dragEnterEvent(self, event):
md = event.mimeData()
if md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device"):
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
allowed = False
md = event.mimeData()
#Drop is only allowed in the location manager widget's different from the selected one
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None:
if ( md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device") ) and \
w.geometry().contains(event.pos()) and \
isinstance(w, QToolButton) and not w.isChecked():
allowed = True
break
if allowed:
event.acceptProposedAction()
else:
event.ignore()
def dropEvent(self, event):
data = event.mimeData()
mime = 'application/calibre+from_library'
if data.hasFormat(mime):
ids = list(map(int, str(data.data(mime)).split()))
tgt = None
for ac in self.location_manager.available_actions:
w = self.widgetForAction(ac)
if w is not None and w.geometry().contains(event.pos()):
tgt = ac.calibre_name
if tgt is not None:
if tgt == 'main':
tgt = None
self.gui.sync_to_device(tgt, False, send_ids=ids)
event.accept()
mime = 'application/calibre+from_device'
if data.hasFormat(mime):
paths = [unicode(u.toLocalFile()) for u in data.urls()]
if paths:
self.gui.iactions['Add Books'].add_books_from_device(
self.gui.current_view(), paths=paths)
event.accept()
# }}}
class MainWindowMixin(object): # {{{ class MainWindowMixin(object): # {{{
@ -507,13 +257,13 @@ class MainWindowMixin(object): # {{{
self.iactions['Fetch News'].init_scheduler(db) self.iactions['Fetch News'].init_scheduler(db)
self.search_bar = SearchBar(self) self.search_bar = SearchBar(self)
self.child_bar = BaseToolBar(self) self.bars_manager = BarsManager(self.donate_button,
self.tool_bar = ToolBar(self.donate_button, self.location_manager, self)
self.location_manager, self.child_bar, self) for bar in self.bars_manager.main_bars:
self.addToolBar(Qt.TopToolBarArea, self.tool_bar) self.addToolBar(Qt.TopToolBarArea, bar)
self.addToolBar(Qt.BottomToolBarArea, self.child_bar) for bar in self.bars_manager.child_bars:
self.menu_bar = MenuBar(self.location_manager, self) self.addToolBar(Qt.BottomToolBarArea, bar)
self.setMenuBar(self.menu_bar) self.bars_manager.update_bars()
self.setUnifiedTitleAndToolBarOnMac(True) self.setUnifiedTitleAndToolBarOnMac(True)
l = self.centralwidget.layout() l = self.centralwidget.layout()

View File

@ -361,10 +361,9 @@ class Preferences(QMainWindow):
self.gui.tags_view.recount() self.gui.tags_view.recount()
self.gui.create_device_menu() self.gui.create_device_menu()
self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
self.gui.tool_bar.build_bar() self.gui.bars_manager.apply_settings()
self.gui.menu_bar.build_bar() self.gui.bars_manager.update_bars()
self.gui.build_context_menus() self.gui.build_context_menus()
self.gui.tool_bar.apply_settings()
return QMainWindow.closeEvent(self, *args) return QMainWindow.closeEvent(self, *args)

View File

@ -317,6 +317,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
am.restore_defaults() am.restore_defaults()
self.changed_signal.emit() self.changed_signal.emit()
def refresh_gui(self, gui):
gui.bars_manager.init_bars()
if __name__ == '__main__': if __name__ == '__main__':
from PyQt4.Qt import QApplication from PyQt4.Qt import QApplication

View File

@ -288,8 +288,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.db_images.reset() self.db_images.reset()
self.library_view.model().count_changed() self.library_view.model().count_changed()
self.tool_bar.database_changed(self.library_view.model().db) self.bars_manager.database_changed(self.library_view.model().db)
self.library_view.model().database_changed.connect(self.tool_bar.database_changed, self.library_view.model().database_changed.connect(self.bars_manager.database_changed,
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
########################### Tags Browser ############################## ########################### Tags Browser ##############################
@ -324,7 +324,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.read_settings() self.read_settings()
self.finalize_layout() self.finalize_layout()
if self.tool_bar.showing_donate: if self.bars_manager.showing_donate:
self.donate_button.start_animation() self.donate_button.start_animation()
self.set_window_title() self.set_window_title()