From e355672c7045f5a100e1fcd84e6a5694ab095bb9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Jun 2010 20:45:04 -0600 Subject: [PATCH] More GUI Refactoring. Cover browser can now be dynamically resized like the other GUI areas --- src/calibre/gui2/__init__.py | 7 + src/calibre/gui2/cover_flow.py | 99 ++++--- src/calibre/gui2/init.py | 123 +++++++- src/calibre/gui2/library/views.py | 21 ++ src/calibre/gui2/main.ui | 297 ------------------- src/calibre/gui2/pictureflow/pictureflow.cpp | 6 +- src/calibre/gui2/sidebar.py | 147 --------- src/calibre/gui2/tag_view.py | 33 ++- src/calibre/gui2/ui.py | 43 ++- src/calibre/gui2/widgets.py | 180 +++++++++-- src/calibre/gui2/wizard/__init__.py | 4 +- 11 files changed, 408 insertions(+), 552 deletions(-) delete mode 100644 src/calibre/gui2/sidebar.py diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index bddefe97f8..cbe9449f1f 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -99,6 +99,8 @@ def _config(): help=_('Limit max simultaneous jobs to number of CPUs')) c.add_opt('tag_browser_hidden_categories', default=set(), help=_('tag browser categories not to display')) + c.add_opt('gui_layout', choices=['wide', 'narrow'], + help=_('The layout of the user interface'), default='narrow') return ConfigProxy(c) config = _config() @@ -125,6 +127,11 @@ def available_width(): desktop = QCoreApplication.instance().desktop() return desktop.availableGeometry().width() +try: + is_widescreen = float(available_width())/available_height() > 1.4 +except: + is_widescreen = True + def extension(path): return os.path.splitext(path)[1][1:].lower() diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index 3bd554e891..c2705e5d9b 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -10,10 +10,11 @@ Module to implement the Cover Flow feature import sys, os, time from PyQt4.Qt import QImage, QSizePolicy, QTimer, QDialog, Qt, QSize, \ - QStackedLayout + QStackedLayout, QLabel from calibre import plugins from calibre.gui2 import config, available_height, available_width + pictureflow, pictureflowerror = plugins['pictureflow'] if pictureflow is not None: @@ -78,12 +79,15 @@ if pictureflow is not None: def __init__(self, parent=None): pictureflow.PictureFlow.__init__(self, parent, config['cover_flow_queue_length']+1) - self.setMinimumSize(QSize(10, 10)) + self.setMinimumSize(QSize(300, 150)) self.setFocusPolicy(Qt.WheelFocus) self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.setZoomFactor(150) + def sizeHint(self): + return self.minimumSize() + def wheelEvent(self, ev): ev.accept() if ev.delta() < 0: @@ -107,56 +111,58 @@ class CoverFlowMixin(object): self.cover_flow_sync_timer.timeout.connect(self.cover_flow_do_sync) self.cover_flow_sync_flag = True self.cover_flow = CoverFlow(parent=self) - self.cover_flow.setVisible(False) - if not config['separate_cover_flow']: - self.cb_layout.addWidget(self.cover_flow) self.cover_flow.currentChanged.connect(self.sync_listview_to_cf) self.library_view.selectionModel().currentRowChanged.connect( self.sync_cf_to_listview) self.db_images = DatabaseImages(self.library_view.model()) self.cover_flow.setImages(self.db_images) - ah, aw = available_height(), available_width() - self._cb_layout_is_horizontal = float(aw)/ah >= 1.4 - self.cb_layout.setDirection(self.cb_layout.LeftToRight if - self._cb_layout_is_horizontal else - self.cb_layout.TopToBottom) - - def toggle_cover_flow_visibility(self, show): + else: + self.cover_flow = QLabel('

'+_('Cover browser could not be loaded') + +'
'+pictureflowerror) + self.cover_flow.setWordWrap(True) if config['separate_cover_flow']: - if show: - d = QDialog(self) - ah, aw = available_height(), available_width() - d.resize(int(aw/1.5), ah-60) - d._layout = QStackedLayout() - d.setLayout(d._layout) - d.setWindowTitle(_('Browse by covers')) - d.layout().addWidget(self.cover_flow) - self.cover_flow.setVisible(True) - self.cover_flow.setFocus(Qt.OtherFocusReason) - d.show() - d.finished.connect(self.sidebar.external_cover_flow_finished) - self.cf_dialog = d - else: - cfd = getattr(self, 'cf_dialog', None) - if cfd is not None: - self.cover_flow.setVisible(False) - cfd.hide() - self.cf_dialog = None + self.cb_splitter.button.clicked.connect(self.toggle_cover_browser) + if CoverFlow is not None: + self.cover_flow.stop.connect(self.hide_cover_browser) else: - if show: - self.cover_flow.setVisible(True) - self.cover_flow.setFocus(Qt.OtherFocusReason) - else: - self.cover_flow.setVisible(False) + self.cb_splitter.insertWidget(self.cb_splitter.side_index, self.cover_flow) + if CoverFlow is not None: + self.cover_flow.stop.connect(self.cb_splitter.hide_side_pane) - def toggle_cover_flow(self, show): - if show: - self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row()) - self.library_view.setCurrentIndex( - self.library_view.currentIndex()) - self.cover_flow_sync_timer.start(500) - self.library_view.scroll_to_row(self.library_view.currentIndex().row()) + def toggle_cover_browser(self): + cbd = getattr(self, 'cb_dialog', None) + if cbd is not None: + self.hide_cover_browser() else: + self.show_cover_browser() + + def show_cover_browser(self): + if CoverFlow is not None: + self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row()) + self.cover_flow_sync_timer.start(500) + self.library_view.setCurrentIndex( + self.library_view.currentIndex()) + self.library_view.scroll_to_row(self.library_view.currentIndex().row()) + + if config['separate_cover_flow']: + d = QDialog(self) + ah, aw = available_height(), available_width() + d.resize(int(aw/1.5), ah-60) + d._layout = QStackedLayout() + d.setLayout(d._layout) + d.setWindowTitle(_('Browse by covers')) + d.layout().addWidget(self.cover_flow) + self.cover_flow.setVisible(True) + self.cover_flow.setFocus(Qt.OtherFocusReason) + d.show() + self.cb_splitter.button.set_state_to_hide() + d.finished.connect(self.cb_splitter.button.set_state_to_show) + self.cb_dialog = d + else: + self.cover_flow.setFocus(Qt.OtherFocusReason) + + def hide_cover_browser(self): + if CoverFlow is not None: self.cover_flow_sync_timer.stop() idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0) if idx.isValid(): @@ -164,7 +170,12 @@ class CoverFlowMixin(object): sm.select(idx, sm.ClearAndSelect|sm.Rows) self.library_view.setCurrentIndex(idx) self.library_view.scroll_to_row(idx.row()) - self.toggle_cover_flow_visibility(show) + + if config['separate_cover_flow']: + cbd = getattr(self, 'cb_dialog', None) + if cbd is not None: + cbd.accept() + self.cb_dialog = None def sync_cf_to_listview(self, current, previous): if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index a991c4d1f8..9ec37df8ff 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -7,12 +7,16 @@ __docformat__ = 'restructuredtext en' import functools -from PyQt4.Qt import QMenu, Qt, pyqtSignal, QToolButton, QIcon +from PyQt4.Qt import QMenu, Qt, pyqtSignal, QToolButton, QIcon, QStackedWidget, \ + QWidget, QHBoxLayout, QToolBar, QSize, QSizePolicy from calibre.utils.config import prefs from calibre.ebooks import BOOK_EXTENSIONS -from calibre.constants import isosx -from calibre.gui2 import config +from calibre.constants import isosx, __appname__ +from calibre.gui2 import config, is_widescreen +from calibre.gui2.library.views import BooksView, DeviceBooksView +from calibre.gui2.widgets import Splitter +from calibre.gui2.tag_view import TagBrowserWidget _keep_refs = [] @@ -279,3 +283,116 @@ class LibraryViewMixin(object): # {{{ # }}} +class LibraryWidget(Splitter): # {{{ + + def __init__(self, parent): + orientation = Qt.Vertical if config['gui_layout'] == 'narrow' and \ + not is_widescreen else Qt.Horizontal + #orientation = Qt.Vertical + idx = 0 if orientation == Qt.Vertical else 1 + Splitter.__init__(self, 'cover_browser_splitter', _('Cover Browser'), + I('cover_flow.svg'), + orientation=orientation, parent=parent, + connect_button=not config['separate_cover_flow'], + side_index=idx, initial_side_size=400, initial_show=False) + parent.library_view = BooksView(parent) + parent.library_view.setObjectName('library_view') + self.addWidget(parent.library_view) +# }}} + +class Stack(QStackedWidget): # {{{ + + def __init__(self, parent): + QStackedWidget.__init__(self, parent) + + parent.cb_splitter = LibraryWidget(parent) + self.tb_widget = TagBrowserWidget(parent) + parent.tb_splitter = Splitter('tag_browser_splitter', + _('Tag Browser'), I('tags.svg'), + parent=parent, side_index=0, initial_side_size=200) + parent.tb_splitter.addWidget(self.tb_widget) + parent.tb_splitter.addWidget(parent.cb_splitter) + parent.tb_splitter.setCollapsible(parent.tb_splitter.other_index, False) + + self.addWidget(parent.tb_splitter) + for x in ('memory', 'card_a', 'card_b'): + name = x+'_view' + w = DeviceBooksView(parent) + setattr(parent, name, w) + self.addWidget(w) + w.setObjectName(name) + + +# }}} + +class SideBar(QToolBar): # {{{ + + + def __init__(self, splitters, jobs_button, parent=None): + QToolBar.__init__(self, _('Side bar'), parent) + self.setOrientation(Qt.Vertical) + self.setMovable(False) + self.setFloatable(False) + self.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.setIconSize(QSize(48, 48)) + self.spacer = QWidget(self) + self.spacer.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) + for s in splitters: + self.addWidget(s.button) + self.addWidget(self.spacer) + self.addWidget(jobs_button) + + for ch in self.children(): + if isinstance(ch, QToolButton): + ch.setCursor(Qt.PointingHandCursor) + +# }}} + +class LayoutMixin(object): # {{{ + + def __init__(self): + self.setupUi(self) + self.setWindowTitle(__appname__) + + if config['gui_layout'] == 'narrow': + from calibre.gui2.status import StatusBar + self.status_bar = StatusBar(self) + self.stack = Stack(self) + self.bd_splitter = Splitter('book_details_splitter', + _('Book Details'), I('book.svg'), + orientation=Qt.Vertical, parent=self, side_index=1) + self._layout_mem = [QWidget(self), QHBoxLayout()] + self._layout_mem[0].setLayout(self._layout_mem[1]) + l = self._layout_mem[1] + l.addWidget(self.stack) + self.sidebar = SideBar([getattr(self, x+'_splitter') + for x in ('bd', 'tb', 'cb')], self.jobs_button, parent=self) + l.addWidget(self.sidebar) + self.bd_splitter.addWidget(self._layout_mem[0]) + self.bd_splitter.addWidget(self.status_bar) + self.bd_splitter.setCollapsible((self.bd_splitter.side_index+1)%2, False) + self.centralwidget.layout().addWidget(self.bd_splitter) + + def finalize_layout(self): + m = self.library_view.model() + if m.rowCount(None) > 0: + self.library_view.set_current_row(0) + m.current_changed(self.library_view.currentIndex(), + self.library_view.currentIndex()) + + + def save_layout_state(self): + for x in ('library', 'memory', 'card_a', 'card_b'): + getattr(self, x+'_view').save_state() + + for x in ('cb', 'tb', 'bd'): + getattr(self, x+'_splitter').save_state() + + def read_layout_settings(self): + # View states are restored automatically when set_database is called + + for x in ('cb', 'tb', 'bd'): + getattr(self, x+'_splitter').restore_state() + +# }}} + diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 109b001925..6d8efd68fa 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -26,6 +26,15 @@ class BooksView(QTableView): # {{{ def __init__(self, parent, modelcls=BooksModel): QTableView.__init__(self, parent) + + self.setDragEnabled(True) + self.setDragDropOverwriteMode(False) + self.setDragDropMode(self.DragDrop) + self.setAlternatingRowColors(True) + self.setSelectionBehavior(self.SelectRows) + self.setShowGrid(False) + self.setWordWrap(False) + self.rating_delegate = RatingDelegate(self) self.timestamp_delegate = DateDelegate(self) self.pubdate_delegate = PubDateDelegate(self) @@ -434,6 +443,18 @@ class BooksView(QTableView): # {{{ self.scrollTo(self.model().index(row, i)) break + def set_current_row(self, row, select=True): + if row > -1: + h = self.horizontalHeader() + for i in range(h.count()): + if not h.isSectionHidden(i): + index = self.model().index(row, i) + self.setCurrentIndex(index) + if select: + sm = self.selectionModel() + sm.select(index, sm.ClearAndSelect|sm.Rows) + break + def close(self): self._model.close() diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index b4bafc3b79..819bee7f63 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -304,270 +304,6 @@ - - - - - 0 - 100 - - - - Qt::Vertical - - - - - - - - 100 - 100 - - - - 0 - - - - - - - Qt::Horizontal - - - - - - - true - - - true - - - true - - - true - - - - - - - Sort by &popularity - - - - - - - 0 - - - - Match any - - - - - Match all - - - - - - - - Create, edit, and delete user categories - - - Manage &user categories - - - - - - - - - - - - 100 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - - - - - 100 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - 10 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - 10 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - 0 - 0 - - - - - 30 - 0 - - - - - - - - - @@ -804,26 +540,11 @@ - - BooksView - QTableView -

calibre/gui2/library/views.h
- LocationView QListView
widgets.h
- - DeviceBooksView - QTableView -
calibre/gui2/library/views.h
-
- - TagsView - QTreeView -
calibre/gui2/tag_view.h
-
SearchBox2 QComboBox @@ -834,24 +555,6 @@ QComboBox
calibre.gui2.search_box
- - StatusBar - QWidget -
calibre/gui2/status.h
- 1 -
- - Splitter - QSplitter -
calibre/gui2/widgets.h
- 1 -
- - SideBar - QWidget -
calibre/gui2/sidebar.h
- 1 -
diff --git a/src/calibre/gui2/pictureflow/pictureflow.cpp b/src/calibre/gui2/pictureflow/pictureflow.cpp index 9bb9a0954c..60985a1a12 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.cpp +++ b/src/calibre/gui2/pictureflow/pictureflow.cpp @@ -713,8 +713,9 @@ void PictureFlowPrivate::render() QPainter painter; painter.begin(&buffer); - QFont font("Arial", FONT_SIZE); + QFont font = QFont(); font.setBold(true); + font.setPointSize(FONT_SIZE); painter.setFont(font); painter.setPen(Qt::white); //painter.setPen(QColor(255,255,255,127)); @@ -763,8 +764,9 @@ void PictureFlowPrivate::render() QPainter painter; painter.begin(&buffer); - QFont font("Arial", FONT_SIZE); + QFont font = QFont(); font.setBold(true); + font.setPointSize(FONT_SIZE); painter.setFont(font); int leftTextIndex = (step>0) ? centerIndex : centerIndex-1; diff --git a/src/calibre/gui2/sidebar.py b/src/calibre/gui2/sidebar.py deleted file mode 100644 index 6710b5d471..0000000000 --- a/src/calibre/gui2/sidebar.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -__license__ = 'GPL v3' -__copyright__ = '2010, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - -from functools import partial - -from PyQt4.Qt import QToolBar, Qt, QIcon, QSizePolicy, QWidget, \ - QSize, QToolButton - -from calibre.gui2 import dynamic - -class SideBar(QToolBar): - - toggle_texts = { - 'book_info' : (_('Show Book Details'), _('Hide Book Details')), - 'tag_browser' : (_('Show Tag Browser'), _('Hide Tag Browser')), - 'cover_browser': (_('Show Cover Browser'), _('Hide Cover Browser')), - } - toggle_icons = { - 'book_info' : 'book.svg', - 'tag_browser' : 'tags.svg', - 'cover_browser': 'cover_flow.svg', - } - - - def __init__(self, parent=None): - QToolBar.__init__(self, _('Side bar'), parent) - self.setOrientation(Qt.Vertical) - self.setMovable(False) - self.setFloatable(False) - self.setToolButtonStyle(Qt.ToolButtonIconOnly) - self.setIconSize(QSize(48, 48)) - - for ac in ('book_info', 'tag_browser', 'cover_browser'): - action = self.addAction(QIcon(I(self.toggle_icons[ac])), - self.toggle_texts[ac][1], getattr(self, '_toggle_'+ac)) - setattr(self, 'action_toggle_'+ac, action) - w = self.widgetForAction(action) - w.setCheckable(True) - setattr(self, 'show_'+ac, partial(getattr(self, '_toggle_'+ac), - show=True)) - setattr(self, 'hide_'+ac, partial(getattr(self, '_toggle_'+ac), - show=False)) - - - self.spacer = QWidget(self) - self.spacer.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) - self.addWidget(self.spacer) - - self.show_cover_browser = partial(self._toggle_cover_browser, show=True) - self.hide_cover_browser = partial(self._toggle_cover_browser, - show=False) - for ch in self.children(): - if isinstance(ch, QToolButton): - ch.setCursor(Qt.PointingHandCursor) - - def initialize(self, jobs_button, cover_browser, toggle_cover_browser, - cover_browser_error, vertical_splitter, horizontal_splitter): - self.cover_browser, self.do_toggle_cover_browser = cover_browser, \ - toggle_cover_browser - if self.cover_browser is None: - self.action_toggle_cover_browser.setEnabled(False) - self.action_toggle_cover_browser.setText( - _('Cover browser could not be loaded: ') + cover_browser_error) - else: - self.cover_browser.stop.connect(self.hide_cover_browser) - self._toggle_cover_browser(dynamic.get('cover_flow_visible', False)) - - self.horizontal_splitter = horizontal_splitter - self.vertical_splitter = vertical_splitter - - tb_state = dynamic.get('tag_browser_state', None) - if tb_state is not None: - self.horizontal_splitter.restoreState(tb_state) - tb_last_open_state = dynamic.get('tag_browser_last_open_state', None) - if tb_last_open_state is not None and \ - not self.horizontal_splitter.is_side_index_hidden: - self.horizontal_splitter.restoreState(tb_last_open_state) - - bi_state = dynamic.get('book_info_state', None) - if bi_state is not None: - self.vertical_splitter.restoreState(bi_state) - bi_last_open_state = dynamic.get('book_info_last_open_state', None) - if bi_last_open_state is not None and \ - not self.vertical_splitter.is_side_index_hidden: - self.vertical_splitter.restoreState(bi_last_open_state) - - self.horizontal_splitter.initialize(name='tag_browser') - self.vertical_splitter.initialize(name='book_info') - self.view_status_changed('book_info', not - self.vertical_splitter.is_side_index_hidden) - self.view_status_changed('tag_browser', not - self.horizontal_splitter.is_side_index_hidden) - self.vertical_splitter.state_changed.connect(partial(self.view_status_changed, - 'book_info'), type=Qt.QueuedConnection) - self.horizontal_splitter.state_changed.connect(partial(self.view_status_changed, - 'tag_browser'), type=Qt.QueuedConnection) - self.addWidget(jobs_button) - - - - def view_status_changed(self, name, visible): - action = getattr(self, 'action_toggle_'+name) - texts = self.toggle_texts[name] - action.setText(texts[int(visible)]) - w = self.widgetForAction(action) - w.setCheckable(True) - w.setChecked(visible) - - def location_changed(self, location): - is_lib = location == 'library' - for ac in ('cover_browser', 'tag_browser'): - ac = getattr(self, 'action_toggle_'+ac) - ac.setEnabled(is_lib) - self.widgetForAction(ac).setVisible(is_lib) - - def save_state(self): - dynamic.set('cover_flow_visible', self.is_cover_browser_visible) - dynamic.set('tag_browser_state', - str(self.horizontal_splitter.saveState())) - dynamic.set('book_info_state', - str(self.vertical_splitter.saveState())) - - - @property - def is_cover_browser_visible(self): - return self.cover_browser is not None and self.cover_browser.isVisible() - - def _toggle_cover_browser(self, show=None): - if show is None: - show = not self.is_cover_browser_visible - self.do_toggle_cover_browser(show) - self.view_status_changed('cover_browser', show) - - def external_cover_flow_finished(self, *args): - self.view_status_changed('cover_browser', False) - - def _toggle_tag_browser(self, show=None): - self.horizontal_splitter.toggle_side_index() - - def _toggle_book_info(self, show=None): - self.vertical_splitter.toggle_side_index() - - diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index b5ced8d626..8c7d466b29 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -10,9 +10,11 @@ Browsing book collection by tags. from itertools import izip from functools import partial -from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \ - QFont, QSize, QIcon, QPoint, \ - QAbstractItemModel, QVariant, QModelIndex, QMenu +from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QCheckBox, \ + QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, \ + QAbstractItemModel, QVariant, QModelIndex, QMenu, \ + QPushButton, QWidget + from calibre.gui2 import config, NONE from calibre.utils.config import prefs from calibre.library.field_metadata import TagsIcons @@ -633,3 +635,28 @@ class TagBrowserMixin(object): # {{{ # }}} +class TagBrowserWidget(QWidget): # {{{ + + def __init__(self, parent): + QWidget.__init__(self, parent) + self._layout = QVBoxLayout() + self.setLayout(self._layout) + + parent.tags_view = TagsView(parent) + self._layout.addWidget(parent.tags_view) + + parent.popularity = QCheckBox(parent) + parent.popularity.setText(_('Sort by &popularity')) + self._layout.addWidget(parent.popularity) + + parent.tag_match = QComboBox(parent) + for x in (_('Match any'), _('Match all')): + parent.tag_match.addItem(x) + parent.tag_match.setCurrentIndex(0) + self._layout.addWidget(parent.tag_match) + + parent.edit_categories = QPushButton(_('Manage &user categories'), parent) + self._layout.addWidget(parent.edit_categories) + +# }}} + diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 9ff30aa768..17d3c4d627 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -37,7 +37,7 @@ from calibre.gui2 import warning_dialog, choose_files, error_dialog, \ Dispatcher, gprefs, \ max_available_height, config, info_dialog, \ GetMetadata -from calibre.gui2.cover_flow import pictureflowerror, CoverFlowMixin +from calibre.gui2.cover_flow import CoverFlowMixin from calibre.gui2.widgets import ProgressIndicator, IMAGE_EXTENSIONS from calibre.gui2.wizard import move_library from calibre.gui2.dialogs.scheduler import Scheduler @@ -61,7 +61,7 @@ from calibre.library.caches import CoverCache from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor from calibre.gui2.tag_view import TagBrowserMixin -from calibre.gui2.init import ToolbarMixin, LibraryViewMixin +from calibre.gui2.init import ToolbarMixin, LibraryViewMixin, LayoutMixin class Listener(Thread): # {{{ @@ -106,7 +106,7 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{ # }}} class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, - TagBrowserMixin, CoverFlowMixin, LibraryViewMixin): + TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, LayoutMixin): 'The main GUI' def set_default_thumbnail(self, height): @@ -143,8 +143,15 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, self.check_messages_timer.start(1000) Ui_MainWindow.__init__(self) - self.setupUi(self) - self.setWindowTitle(__appname__) + + # Jobs Button {{{ + self.job_manager = JobManager() + self.jobs_dialog = JobsDialog(self, self.job_manager) + self.jobs_button = JobsButton() + self.jobs_button.initialize(self.jobs_dialog, self.job_manager) + # }}} + + LayoutMixin.__init__(self) self.restriction_count_of_books_in_view = 0 self.restriction_count_of_books_in_library = 0 @@ -167,11 +174,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, self.progress_indicator = ProgressIndicator(self) self.verbose = opts.verbose self.get_metadata = GetMetadata() - self.read_settings() - self.job_manager = JobManager() self.emailer = Emailer() self.emailer.start() - self.jobs_dialog = JobsDialog(self, self.job_manager) self.upload_memory = {} self.delete_memory = {} self.conversion_jobs = {} @@ -326,17 +330,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, self.resize(self.width(), self._calculated_available_height) self.search.setMaximumWidth(self.width()-150) - # Jobs Button {{{ - self.jobs_button = JobsButton() - self.jobs_button.initialize(self.jobs_dialog, self.job_manager) - # }}} - - ####################### Side Bar ############################### - - self.sidebar.initialize(self.jobs_button, self.cover_flow, - self.toggle_cover_flow, pictureflowerror, - self.vertical_splitter, self.horizontal_splitter) - if config['autolaunch_server']: from calibre.library.server.main import start_threaded_server @@ -362,6 +355,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, self._add_filesystem_book = Dispatcher(self.__add_filesystem_book) self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection) + self.read_settings() + self.finalize_layout() def do_saved_search_edit(self, search): d = SavedSearchEditor(self, search) @@ -1934,7 +1929,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, page = 0 if location == 'library' else 1 if location == 'main' else 2 if location == 'carda' else 3 self.stack.setCurrentIndex(page) self.status_bar.reset_info() - self.sidebar.location_changed(location) + for x in ('tb', 'cb'): + splitter = getattr(self, x+'_splitter') + splitter.button.setEnabled(location == 'library') if location == 'library': self.action_edit.setEnabled(True) self.action_merge.setEnabled(True) @@ -2037,14 +2034,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, if geometry is not None: self.restoreGeometry(geometry) self.read_toolbar_settings() + self.read_layout_settings() def write_settings(self): config.set('main_window_geometry', self.saveGeometry()) dynamic.set('sort_history', self.library_view.model().sort_history) - self.sidebar.save_state() - for view in ('library_view', 'memory_view', 'card_a_view', - 'card_b_view'): - getattr(self, view).save_state() + self.save_layout_state() def restart(self): self.quit(restart=True) diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 093fa3fc5c..52bd8eda9a 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -4,16 +4,18 @@ __copyright__ = '2008, Kovid Goyal ' Miscellaneous widgets used in the GUI ''' import re, os, traceback + from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \ QListWidgetItem, QTextCharFormat, QApplication, \ QSyntaxHighlighter, QCursor, QColor, QWidget, \ - QPixmap, QPalette, QSplitterHandle, \ + QPixmap, QPalette, QSplitterHandle, QToolButton, \ QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \ QRegExp, QSettings, QSize, QModelIndex, QSplitter, \ QAbstractButton, QPainter, QLineEdit, QComboBox, \ - QMenu, QStringListModel, QCompleter, QStringList + QMenu, QStringListModel, QCompleter, QStringList, \ + QTimer -from calibre.gui2 import NONE, error_dialog, pixmap_to_data, dynamic +from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs from calibre.gui2.filename_pattern_ui import Ui_Form from calibre import fit_image, human_readable @@ -927,6 +929,7 @@ class SplitterHandle(QSplitterHandle): self.double_clicked.connect(splitter.double_clicked, type=Qt.QueuedConnection) self.highlight = False + self.setToolTip(_('Drag to resize')+' '+splitter.label) def splitter_moved(self, *args): oh = self.highlight @@ -944,20 +947,62 @@ class SplitterHandle(QSplitterHandle): def mouseDoubleClickEvent(self, ev): self.double_clicked.emit(self) +class LayoutButton(QToolButton): + + def __init__(self, icon, text, splitter, parent=None): + QToolButton.__init__(self, parent) + self.label = text + self.setIcon(QIcon(icon)) + self.setCheckable(True) + + self.splitter = splitter + splitter.state_changed.connect(self.update_state) + + def set_state_to_show(self, *args): + self.setChecked(False) + label =_('Show') + self.setText(label + ' ' + self.label) + + def set_state_to_hide(self, *args): + self.setChecked(True) + label = _('Hide') + self.setText(label + ' ' + self.label) + + def update_state(self, *args): + if self.splitter.is_side_index_hidden: + self.set_state_to_show() + else: + self.set_state_to_hide() + class Splitter(QSplitter): state_changed = pyqtSignal(object) - def __init__(self, *args): - QSplitter.__init__(self, *args) + def __init__(self, name, label, icon, initial_show=True, + initial_side_size=120, connect_button=True, + orientation=Qt.Horizontal, side_index=0, parent=None): + QSplitter.__init__(self, parent) + self.resize_timer = QTimer(self) + self.resize_timer.setSingleShot(True) + self.desired_side_size = initial_side_size + self.desired_show = initial_show + self.resize_timer.setInterval(5) + self.resize_timer.timeout.connect(self.do_resize) + self.setOrientation(orientation) + self.side_index = side_index + self._name = name + self.label = label + self.initial_side_size = initial_side_size + self.initial_show = initial_show self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection) + self.button = LayoutButton(icon, label, self) + if connect_button: + self.button.clicked.connect(self.double_clicked) def createHandle(self): return SplitterHandle(self.orientation(), self) - def initialize(self, name=None): - if name is not None: - self._name = name + def initialize(self): for i in range(self.count()): h = self.handle(i) if h is not None: @@ -965,40 +1010,115 @@ class Splitter(QSplitter): self.state_changed.emit(not self.is_side_index_hidden) def splitter_moved(self, *args): + self.desired_side_size = self.side_index_size self.state_changed.emit(not self.is_side_index_hidden) - @property - def side_index(self): - return 0 if self.orientation() == Qt.Horizontal else 1 - @property def is_side_index_hidden(self): sizes = list(self.sizes()) return sizes[self.side_index] == 0 - def toggle_side_index(self): - self.double_clicked(None) + @property + def save_name(self): + ori = 'horizontal' if self.orientation() == Qt.Horizontal \ + else 'vertical' + return self._name + '_' + ori - def double_clicked(self, handle): - visible = not self.is_side_index_hidden - sizes = list(self.sizes()) - if 0 in sizes: - idx = sizes.index(0) - sizes[idx] = 80 - else: - sizes[self.side_index] = 0 + def print_sizes(self): + if self.count() > 1: + print self.save_name, 'side:', self.side_index_size, 'other:', + print list(self.sizes())[self.other_index] - if visible: - dynamic.set(self._name + '_last_open_state', str(self.saveState())) + @dynamic_property + def side_index_size(self): + def fget(self): + if self.count() < 2: return 0 + return self.sizes()[self.side_index] + + def fset(self, val): + if self.count() < 2: return + if val == 0 and not self.is_side_index_hidden: + self.save_state() + sizes = list(self.sizes()) + for i in range(len(sizes)): + sizes[i] = val if i == self.side_index else 10 self.setSizes(sizes) + total = sum(self.sizes()) + sizes = list(self.sizes()) + for i in range(len(sizes)): + sizes[i] = val if i == self.side_index else total-val + self.setSizes(sizes) + self.initialize() + + return property(fget=fget, fset=fset) + + def do_resize(self, *args): + orig = self.desired_side_size + QSplitter.resizeEvent(self, self._resize_ev) + if orig > 20 and self.desired_show: + c = 0 + while abs(self.side_index_size - orig) > 10 and c < 5: + self.apply_state(self.get_state(), save_desired=False) + c += 1 + + def resizeEvent(self, ev): + if self.resize_timer.isActive(): + self.resize_timer.stop() + self._resize_ev = ev + self.resize_timer.start() + + def get_state(self): + if self.count() < 2: return (False, 200) + return (self.desired_show, self.desired_side_size) + + def apply_state(self, state, save_desired=True): + if state[0]: + self.side_index_size = state[1] + if save_desired: + self.desired_side_size = self.side_index_size else: - state = dynamic.get(self._name+ '_last_open_state', None) - if state is not None: - self.restoreState(state) - else: - self.setSizes(sizes) - self.initialize() + self.side_index_size = 0 + self.desired_show = state[0] + def default_state(self): + return (self.initial_show, self.initial_side_size) + # Public API {{{ + def save_state(self): + if self.count() > 1: + gprefs[self.save_name+'_state'] = self.get_state() + + @property + def other_index(self): + return (self.side_index+1)%2 + + def restore_state(self): + if self.count() > 1: + state = gprefs.get(self.save_name+'_state', + self.default_state()) + self.apply_state(state, save_desired=False) + self.desired_side_size = state[1] + + def toggle_side_pane(self, hide=None): + if hide is None: + action = 'show' if self.is_side_index_hidden else 'hide' + else: + action = 'hide' if hide else 'show' + getattr(self, action+'_side_pane')() + + def show_side_pane(self): + if self.count() < 2 or not self.is_side_index_hidden: + return + self.apply_state((True, self.desired_side_size)) + + def hide_side_pane(self): + if self.count() < 2 or self.is_side_index_hidden: + return + self.apply_state((False, self.desired_side_size)) + + def double_clicked(self, *args): + self.toggle_side_pane() + + # }}} diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 1fc57e8f4e..b831201f2d 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -96,14 +96,14 @@ class Kobo(Device): class Booq(Device): name = 'Booq Reader' manufacturer = 'Booq' - output_profile = 'prs505' + output_profile = 'sony' output_format = 'EPUB' id = 'booq' class TheBook(Device): name = 'The Book' manufacturer = 'Augen' - output_profile = 'prs505' + output_profile = 'sony' output_format = 'EPUB' id = 'thebook'