Implement support for icon themes

Now you can go to Preferences->Look & Feel and choose an icon theme
for calibre. There are already half a dozen icon themes available.
This commit is contained in:
Kovid Goyal 2015-08-25 11:36:22 +05:30
parent bf85705c9f
commit 3d25939050
3 changed files with 211 additions and 170 deletions

View File

@ -15,12 +15,12 @@ from threading import Thread
from PyQt5.Qt import (
QImageReader, QFormLayout, QVBoxLayout, QSplitter, QGroupBox, QListWidget,
QLineEdit, QSpinBox, QTextEdit, QSize, QListWidgetItem, QIcon, QImage,
QCursor, pyqtSignal, QStackedLayout, QWidget, QLabel, Qt, QComboBox,
QPixmap, QGridLayout, QStyledItemDelegate, QModelIndex, QApplication,
QStaticText, QStyle, QPen
pyqtSignal, QStackedLayout, QWidget, QLabel, Qt, QComboBox, QPixmap,
QGridLayout, QStyledItemDelegate, QModelIndex, QApplication, QStaticText,
QStyle, QPen
)
from calibre import walk, fit_image
from calibre import walk, fit_image, human_readable
from calibre.constants import cache_dir, config_dir
from calibre.customize.ui import interface_actions
from calibre.gui2 import must_use_qt, gprefs, choose_dir, error_dialog, choose_save_file, question_dialog
@ -415,13 +415,15 @@ class Delegate(QStyledItemDelegate):
bottom = option.rect.bottom() - 2
painter.drawLine(0, bottom, option.rect.right(), bottom)
if 'static-text' not in theme:
theme['static-text'] = QStaticText(
theme['static-text'] = QStaticText(_(
'''
<h1>{title}</h1>
<p>by <i>{author}</i> with <b>{number}</b> icons</p>
<p>by <i>{author}</i> with <b>{number}</b> icons [{size}]</p>
<p>{description}</p>
<p>Version: {version}</p>
'''.format(title=theme.get('title', _('Unknown')), author=theme.get('author', _('Unknown')),
number=theme.get('number', 0), description=theme.get('description', '')))
number=theme.get('number', 0), description=theme.get('description', ''),
size=human_readable(theme.get('compressed-size', 0)), version=theme.get('version', 1))))
painter.drawStaticText(COVER_SIZE[0] + self.SPACING, option.rect.top() + self.SPACING, theme['static-text'])
painter.restore()
@ -457,11 +459,12 @@ class ChooseTheme(Dialog):
self.current_theme = json.loads(I('icon-theme.json', data=True))['title']
except Exception:
self.current_theme = None
self.changed = False
Dialog.__init__(self, _('Choose an icon theme'), 'choose-icon-theme-dialog', parent)
self.themes_downloaded.connect(self.show_themes, type=Qt.QueuedConnection)
self.cover_downloaded.connect(self.set_cover, type=Qt.QueuedConnection)
self.keep_downloading = True
self.commit_changes = None
self.new_theme_title = None
def sizeHint(self):
desktop = QApplication.instance().desktop()
@ -603,8 +606,7 @@ class ChooseTheme(Dialog):
'Are you sure you want to remove the <b>%s</b> icon theme'
' and return to the stock icons?') % self.current_theme):
return
self.changed = True
remove_icon_theme()
self.commit_changes = remove_icon_theme
Dialog.accept(self)
def accept(self):
@ -614,7 +616,7 @@ class ChooseTheme(Dialog):
theme = self.theme_list.currentItem().data(Qt.UserRole)
url = BASE_URL + theme['icons-url']
size = theme['compressed-size']
theme = {k:theme.get(k, '') for k in 'name title'.split()}
theme = {k:theme.get(k, '') for k in 'name title version'.split()}
self.keep_downloading = True
d = DownloadProgress(self, size)
d.canceled_signal.connect(lambda : setattr(self, 'keep_downloading', False))
@ -626,7 +628,7 @@ class ChooseTheme(Dialog):
try:
response = get_https_resource_securely(url, get_response=True)
while self.keep_downloading:
raw = response.read(100)
raw = response.read(1024)
if not raw:
break
buf.write(raw)
@ -647,23 +649,17 @@ class ChooseTheme(Dialog):
'Failed to download icon theme, click "Show Details" for more information.'), show=True, det_msg=self.downloaded_theme)
if ret == d.Rejected or not self.keep_downloading or d.canceled or self.downloaded_theme is None:
return
self.changed = True
with BusyCursor():
self.downloaded_theme.seek(0)
f = decompress(self.downloaded_theme)
dt = self.downloaded_theme
def commit_changes():
dt.seek(0)
f = decompress(dt)
f.seek(0)
remove_icon_theme()
install_icon_theme(theme, f)
self.commit_changes = commit_changes
self.new_theme_title = theme['title']
return Dialog.accept(self)
class BusyCursor(object):
def __enter__(self):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
def __exit__(self, *args):
QApplication.restoreOverrideCursor()
# }}}
def remove_icon_theme():
@ -709,5 +705,7 @@ if __name__ == '__main__':
from calibre.gui2 import Application
app = Application([])
# create_theme('.')
ChooseTheme().exec_()
d = ChooseTheme()
if d.exec_() == d.Accepted and d.commit_changes is not None:
d.commit_changes()
del app

View File

@ -5,12 +5,14 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import json
from threading import Thread
from functools import partial
from PyQt5.Qt import (
QApplication, QFont, QFontInfo, QFontDialog, QColorDialog, QPainter,
QAbstractListModel, Qt, QIcon, QKeySequence, QColor, pyqtSignal,
QAbstractListModel, Qt, QIcon, QKeySequence, QColor, pyqtSignal, QCursor,
QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout)
from calibre import human_readable
@ -25,6 +27,14 @@ from calibre.gui2.book_details import get_field_list
from calibre.gui2.preferences.coloring import EditRules
from calibre.gui2.library.alternate_views import auto_height, CM_TO_INCH
class BusyCursor(object):
def __enter__(self):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
def __exit__(self, *args):
QApplication.restoreOverrideCursor()
class DisplayedFields(QAbstractListModel): # {{{
def __init__(self, db, parent=None):
@ -136,6 +146,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r = self.register
try:
self.icon_theme_title = json.loads(I('icon-theme.json', data=True))['name']
except Exception:
self.icon_theme_title = _('Default icons')
self.icon_theme.setText(_('Icon theme: <b>%s</b>') % self.icon_theme_title)
self.commit_icon_theme = None
self.icon_theme_button.clicked.connect(self.choose_icon_theme)
r('gui_layout', config, restart_required=True, choices=[(_('Wide'), 'wide'), (_('Narrow'), 'narrow')])
r('ui_style', gprefs, restart_required=True, choices=[(_('System default'), 'system'), (_('Calibre style'),
'calibre')])
@ -207,8 +224,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
choices = set([k for k in db.field_metadata.all_field_keys()
if (db.field_metadata[k]['is_category'] and
(db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration']) and
not db.field_metadata[k]['display'].get('is_names', False))
or
not db.field_metadata[k]['display'].get('is_names', False)) or
(db.field_metadata[k]['datatype'] in ['composite'] and
db.field_metadata[k]['display'].get('make_category', False))])
choices -= set(['authors', 'publisher', 'formats', 'news', 'identifiers'])
@ -281,6 +297,15 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.opt_cover_grid_width.valueChanged.connect(self.update_aspect_ratio)
self.opt_cover_grid_height.valueChanged.connect(self.update_aspect_ratio)
def choose_icon_theme(self):
from calibre.gui2.icon_theme import ChooseTheme
d = ChooseTheme(self)
if d.exec_() == d.Accepted:
self.commit_icon_theme = d.commit_changes
self.icon_theme_title = d.new_theme_title or _('Default icons')
self.icon_theme.setText(_('Icon theme: <b>%s</b>') % self.icon_theme_title)
self.changed_signal.emit()
@property
def current_cover_size(self):
cval = self.opt_cover_grid_height.value()
@ -448,20 +473,24 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.changed_signal.emit()
def commit(self, *args):
rr = ConfigWidgetBase.commit(self, *args)
if self.current_font != self.initial_font:
gprefs['font'] = (self.current_font[:4] if self.current_font else
None)
gprefs['font_stretch'] = (self.current_font[4] if self.current_font
is not None else QFont.Unstretched)
QApplication.setFont(self.font_display.font())
rr = True
self.display_model.commit()
self.edit_rules.commit(self.gui.current_db.prefs)
self.icon_rules.commit(self.gui.current_db.prefs)
self.grid_rules.commit(self.gui.current_db.prefs)
gprefs['cover_grid_color'] = tuple(self.cg_bg_widget.bcol.getRgb())[:3]
gprefs['cover_grid_texture'] = self.cg_bg_widget.btex
with BusyCursor():
rr = ConfigWidgetBase.commit(self, *args)
if self.current_font != self.initial_font:
gprefs['font'] = (self.current_font[:4] if self.current_font else
None)
gprefs['font_stretch'] = (self.current_font[4] if self.current_font
is not None else QFont.Unstretched)
QApplication.setFont(self.font_display.font())
rr = True
self.display_model.commit()
self.edit_rules.commit(self.gui.current_db.prefs)
self.icon_rules.commit(self.gui.current_db.prefs)
self.grid_rules.commit(self.gui.current_db.prefs)
gprefs['cover_grid_color'] = tuple(self.cg_bg_widget.bcol.getRgb())[:3]
gprefs['cover_grid_texture'] = self.cg_bg_widget.btex
if self.commit_icon_theme is not None:
self.commit_icon_theme()
rr = True
return rr
def refresh_gui(self, gui):

View File

@ -28,125 +28,27 @@
<string>Main Interface</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_9">
<item row="2" column="1">
<widget class="QComboBox" name="opt_language">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>20</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="opt_systray_icon">
<property name="text">
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item row="9" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_17">
<widget class="QLabel" name="label_4">
<property name="text">
<string>User Interface &amp;layout (needs restart):</string>
<string>E&amp;xtra spacing to add between rows in the book list (can be negative):</string>
</property>
<property name="buddy">
<cstring>opt_gui_layout</cstring>
<cstring>opt_book_list_extra_row_spacing</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_gui_layout">
<property name="maximumSize">
<size>
<width>250</width>
<height>16777215</height>
</size>
<item row="9" column="1">
<widget class="QSpinBox" name="opt_book_list_extra_row_spacing">
<property name="suffix">
<string> px</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>20</number>
<property name="minimum">
<number>-20</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="opt_disable_animations">
<property name="toolTip">
<string>Disable all animations. Useful if you have a slow/old computer.</string>
</property>
<property name="text">
<string>Disable &amp;animations</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="opt_disable_tray_notification">
<property name="text">
<string>Disable &amp;notifications in system tray</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="opt_show_splash_screen">
<property name="text">
<string>Show &amp;splash screen at startup</string>
</property>
</widget>
</item>
<item row="6" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Interface font:</string>
</property>
<property name="buddy">
<cstring>font_display</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="font_display">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="change_font_button">
<property name="text">
<string>Change &amp;font (needs restart)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_widget_style">
<property name="text">
<string>User interface &amp;style (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_ui_style</cstring>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<item row="8" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>&amp;Toolbar</string>
@ -181,7 +83,60 @@
</layout>
</widget>
</item>
<item row="2" column="0">
<item row="0" column="1">
<widget class="QComboBox" name="opt_ui_style"/>
</item>
<item row="7" column="1">
<widget class="QPushButton" name="change_font_button">
<property name="text">
<string>Change &amp;font (needs restart)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="opt_gui_layout">
<property name="maximumSize">
<size>
<width>250</width>
<height>16777215</height>
</size>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>20</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="opt_book_list_tooltips">
<property name="text">
<string>Show &amp;tooltips in the book list</string>
</property>
</widget>
</item>
<item row="10" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="opt_systray_icon">
<property name="text">
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Choose &amp;language (requires restart):</string>
@ -191,33 +146,92 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="opt_ui_style"/>
<item row="7" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Interface font:</string>
</property>
<property name="buddy">
<cstring>font_display</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="font_display">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="icon_theme">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="opt_book_list_tooltips">
<widget class="QCheckBox" name="opt_disable_tray_notification">
<property name="text">
<string>Show &amp;tooltips in the book list</string>
<string>Disable &amp;notifications in system tray</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_4">
<item row="3" column="1">
<widget class="QComboBox" name="opt_language">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
<property name="minimumContentsLength">
<number>20</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Extra &amp;spacing to add between rows in the book list (can be negative):</string>
<string>&amp;User Interface layout (needs restart):</string>
</property>
<property name="buddy">
<cstring>opt_book_list_extra_row_spacing</cstring>
<cstring>opt_gui_layout</cstring>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QSpinBox" name="opt_book_list_extra_row_spacing">
<property name="suffix">
<string> px</string>
<item row="0" column="0">
<widget class="QLabel" name="label_widget_style">
<property name="text">
<string>User interface style (&amp;needs restart):</string>
</property>
<property name="minimum">
<number>-20</number>
<property name="buddy">
<cstring>opt_ui_style</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="opt_disable_animations">
<property name="toolTip">
<string>Disable all animations. Useful if you have a slow/old computer.</string>
</property>
<property name="text">
<string>Disable &amp;animations</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="opt_show_splash_screen">
<property name="text">
<string>Show &amp;splash screen at startup</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="icon_theme_button">
<property name="text">
<string>Change &amp;icon theme (needs restart)</string>
</property>
</widget>
</item>
@ -274,7 +288,7 @@
<item row="5" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>&amp;Field to show under the covers:</string>
<string>Field to show &amp;under the covers:</string>
</property>
<property name="buddy">
<cstring>opt_field_under_covers_in_grid</cstring>
@ -468,7 +482,7 @@ A value of zero means calculate automatically.</string>
<item row="11" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>&amp;Size of the emblems (if any) shown next to the covers: </string>
<string>Size of the emblems (if any) shown &amp;next to the covers: </string>
</property>
<property name="buddy">
<cstring>opt_emblem_size</cstring>
@ -757,7 +771,7 @@ A value of zero means calculate automatically.</string>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Default author link template:</string>
<string>Default author &amp;link template:</string>
</property>
<property name="buddy">
<cstring>opt_default_author_link</cstring>
@ -804,7 +818,7 @@ Manage Authors. You can use the values {author} and
<item row="2" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>&amp;Collapse when more items than:</string>
<string>Co&amp;llapse when more items than:</string>
</property>
<property name="buddy">
<cstring>opt_tags_browser_collapse_at</cstring>
@ -825,7 +839,7 @@ up into subcategories. If the partition method is set to disable, this value is
<item row="3" column="0">
<widget class="QLabel" name="label_8111">
<property name="text">
<string>Categories not to partition:</string>
<string>Categories &amp;not to partition:</string>
</property>
<property name="buddy">
<cstring>opt_tag_browser_dont_collapse</cstring>