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
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
@@ -804,26 +540,11 @@
-
- BooksView
- QTableView
- calibre/gui2/library/views.h
-
LocationView
QListView
-
- DeviceBooksView
- QTableView
- calibre/gui2/library/views.h
-
-
- TagsView
- QTreeView
-
-
SearchBox2
QComboBox
@@ -834,24 +555,6 @@
QComboBox
-
- StatusBar
- QWidget
-
- 1
-
-
- Splitter
- QSplitter
-
- 1
-
-
- SideBar
- QWidget
-
- 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'