mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
merge from trunk
This commit is contained in:
commit
b0d45bab5f
@ -92,8 +92,8 @@
|
||||
- title: "Various Romanian news sources"
|
||||
author: Silviu Coatara
|
||||
|
||||
- title: "Osnews.pl and SwiatKindle"
|
||||
author: Mori
|
||||
- title: "Osnews.pl and SwiatCzytnikow"
|
||||
author: Tomasz Dlugosz
|
||||
|
||||
- title: "Roger Ebert Journal"
|
||||
author: Shane Erstad
|
||||
|
BIN
resources/images/minusminus.png
Normal file
BIN
resources/images/minusminus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
resources/images/news/bucataras.png
Normal file
BIN
resources/images/news/bucataras.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 765 B |
BIN
resources/images/news/historiaro.png
Normal file
BIN
resources/images/news/historiaro.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 521 B |
BIN
resources/images/plusplus.png
Normal file
BIN
resources/images/plusplus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
56
resources/recipes/bucataras.recipe
Normal file
56
resources/recipes/bucataras.recipe
Normal file
@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = u'2011, Silviu Cotoar\u0103'
|
||||
'''
|
||||
bucataras.ro
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Bucataras(BasicNewsRecipe):
|
||||
title = u'Bucataras'
|
||||
__author__ = u'Silviu Cotoar\u0103'
|
||||
description = ''
|
||||
publisher = 'Bucataras'
|
||||
oldest_article = 5
|
||||
language = 'ro'
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
category = 'Ziare,Bucatarie,Retete'
|
||||
encoding = 'utf-8'
|
||||
cover_url = 'http://www.bucataras.ro/templates/default/images/pink/logo.jpg'
|
||||
|
||||
conversion_options = {
|
||||
'comments' : description
|
||||
,'tags' : category
|
||||
,'language' : language
|
||||
,'publisher' : publisher
|
||||
}
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='h1', attrs={'class':'titlu'})
|
||||
, dict(name='div', attrs={'class':'contentL'})
|
||||
, dict(name='div', attrs={'class':'contentBottom'})
|
||||
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['sociale']})
|
||||
, dict(name='div', attrs={'class':['contentR']})
|
||||
, dict(name='a', attrs={'target':['_self']})
|
||||
, dict(name='div', attrs={'class':['comentarii']})
|
||||
]
|
||||
|
||||
remove_tags_after = [
|
||||
dict(name='div', attrs={'class':['comentarii']})
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Feeds', u'http://www.bucataras.ro/rss/retete/')
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
return self.adeify_images(soup)
|
56
resources/recipes/buffalo_news.recipe
Normal file
56
resources/recipes/buffalo_news.recipe
Normal file
@ -0,0 +1,56 @@
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'Todd Chapman'
|
||||
__copyright__ = 'Todd Chapman'
|
||||
__version__ = 'v0.1'
|
||||
__date__ = '26 February 2011'
|
||||
|
||||
'''
|
||||
http://www.buffalonews.com/RSS/
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1298680852(BasicNewsRecipe):
|
||||
title = u'Buffalo News'
|
||||
__author__ = 'ChappyOnIce'
|
||||
language = 'en'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 20
|
||||
encoding = 'utf-8'
|
||||
remove_javascript = True
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':['main-content-left']})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'id':['commentCount']}),
|
||||
dict(name='div', attrs={'class':['story-list-links']})
|
||||
]
|
||||
|
||||
remove_tags_after = dict(name='div', attrs={'class':['body storyContent']})
|
||||
conversion_options = {
|
||||
'base_font_size' : 14,
|
||||
}
|
||||
feeds = [(u'City of Buffalo', u'http://www.buffalonews.com/city/communities/buffalo/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Southern Erie County', u'http://www.buffalonews.com/city/communities/southern-erie/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Eastern Erie County', u'http://www.buffalonews.com/city/communities/eastern-erie/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Southern Tier', u'http://www.buffalonews.com/city/communities/southern-tier/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Niagara County', u'http://www.buffalonews.com/city/communities/niagara-county/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Business', u'http://www.buffalonews.com/business/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'MoneySmart', u'http://www.buffalonews.com/business/moneysmart/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Bills & NFL', u'http://www.buffalonews.com/sports/bills-nfl/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Sabres & NHL', u'http://www.buffalonews.com/sports/sabres-nhl/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Bob DiCesare', u'http://www.buffalonews.com/sports/columns/bob-dicesare/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Bucky Gleason', u'http://www.buffalonews.com/sports/columns/bucky-gleason/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Mark Gaughan', u'http://www.buffalonews.com/sports/bills-nfl/inside-the-nfl/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Mike Harrington', u'http://www.buffalonews.com/sports/columns/mike-harrington/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Jerry Sullivan', u'http://www.buffalonews.com/sports/columns/jerry-sullivan/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Other Sports Columns', u'http://www.buffalonews.com/sports/columns/other-sports-columns/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Life', u'http://www.buffalonews.com/life/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Bruce Andriatch', u'http://www.buffalonews.com/city/columns/bruce-andriatch/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Donn Esmonde', u'http://www.buffalonews.com/city/columns/donn-esmonde/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Rod Watson', u'http://www.buffalonews.com/city/columns/rod-watson/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Entertainment', u'http://www.buffalonews.com/entertainment/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Off Main Street', u'http://www.buffalonews.com/city/columns/off-main-street/?widget=rssfeed&view=feed&contentId=77944'),
|
||||
(u'Editorials', u'http://www.buffalonews.com/editorial-page/buffalo-news-editorials/?widget=rssfeed&view=feed&contentId=77944')
|
||||
]
|
27
resources/recipes/dotpod.recipe
Normal file
27
resources/recipes/dotpod.recipe
Normal file
@ -0,0 +1,27 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011-2011, Federico Escalada <fedeescalada at gmail.com>'
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Dotpod(BasicNewsRecipe):
|
||||
__author__ = 'Federico Escalada'
|
||||
description = 'Tecnologia y Comunicacion Audiovisual'
|
||||
encoding = 'utf-8'
|
||||
language = 'es'
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
oldest_article = 7
|
||||
publication_type = 'blog'
|
||||
title = 'Dotpod'
|
||||
authors = 'Federico Picone'
|
||||
|
||||
conversion_options = {
|
||||
'authors' : authors
|
||||
,'comments' : description
|
||||
,'language' : language
|
||||
}
|
||||
|
||||
feeds = [('Dotpod', 'http://www.dotpod.com.ar/feed/')]
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':'feedflare'})]
|
||||
|
51
resources/recipes/historiaro.recipe
Normal file
51
resources/recipes/historiaro.recipe
Normal file
@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = u'2011, Silviu Cotoar\u0103'
|
||||
'''
|
||||
historia.ro
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class HistoriaRo(BasicNewsRecipe):
|
||||
title = u'Historia'
|
||||
__author__ = u'Silviu Cotoar\u0103'
|
||||
description = ''
|
||||
publisher = 'Historia'
|
||||
oldest_article = 5
|
||||
language = 'ro'
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
category = 'Ziare,Reviste,Istorie'
|
||||
encoding = 'utf-8'
|
||||
cover_url = 'http://www.historia.ro/sites/all/themes/historia/images/historia.png'
|
||||
|
||||
conversion_options = {
|
||||
'comments' : description
|
||||
,'tags' : category
|
||||
,'language' : language
|
||||
,'publisher' : publisher
|
||||
}
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'c_antet_title'})
|
||||
, dict(name='a', attrs={'class':'overlaybox'})
|
||||
, dict(name='div', attrs={'class':'art_content'})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['fl_left']})
|
||||
, dict(name='div', attrs={'id':['article_toolbar']})
|
||||
, dict(name='div', attrs={'class':['zoom_cont']})
|
||||
]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Feeds', u'http://www.historia.ro/rss.xml')
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
return self.adeify_images(soup)
|
@ -58,6 +58,21 @@ class FetchNewsAction(InterfaceAction):
|
||||
self.scheduler.recipe_download_failed(arg)
|
||||
return self.gui.job_exception(job)
|
||||
id = self.gui.library_view.model().add_news(pt.name, arg)
|
||||
|
||||
# Arg may contain a "keep_issues" variable. If it is non-zero,
|
||||
# delete all but newest x issues.
|
||||
try:
|
||||
keep_issues = int(arg['keep_issues'])
|
||||
except:
|
||||
keep_issues = 0
|
||||
if keep_issues > 0:
|
||||
ids_with_tag = list(sorted(self.gui.library_view.model().
|
||||
db.tags_older_than(arg['title'],
|
||||
None, must_have_tag=_('News')), reverse=True))
|
||||
ids_to_delete = ids_with_tag[keep_issues:]
|
||||
if ids_to_delete:
|
||||
self.gui.library_view.model().delete_books_by_id(ids_to_delete)
|
||||
|
||||
self.gui.library_view.model().reset()
|
||||
sync = self.gui.news_to_be_synced
|
||||
sync.add(id)
|
||||
|
@ -10,11 +10,9 @@ Scheduler for automated recipe downloads
|
||||
from datetime import timedelta
|
||||
|
||||
from PyQt4.Qt import QDialog, SIGNAL, Qt, QTime, QObject, QMenu, \
|
||||
QAction, QIcon, QMutex, QTimer, pyqtSignal, QWidget, QHBoxLayout, \
|
||||
QLabel
|
||||
QAction, QIcon, QMutex, QTimer, pyqtSignal
|
||||
|
||||
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
|
||||
from calibre.gui2.search_box import SearchBox2
|
||||
from calibre.gui2 import config as gconf, error_dialog
|
||||
from calibre.web.feeds.recipes.model import RecipeModel
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
@ -28,18 +26,12 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self.setupUi(self)
|
||||
self.recipe_model = recipe_model
|
||||
self.recipe_model.do_refresh()
|
||||
self.count_label.setText(
|
||||
_('%s news sources') %
|
||||
self.recipe_model.showing_count)
|
||||
|
||||
self._cont = QWidget(self)
|
||||
self._cont.l = QHBoxLayout()
|
||||
self._cont.setLayout(self._cont.l)
|
||||
self._cont.la = QLabel(_('&Search:'))
|
||||
self._cont.l.addWidget(self._cont.la, 1)
|
||||
self.search = SearchBox2(self)
|
||||
self._cont.l.addWidget(self.search, 100)
|
||||
self._cont.la.setBuddy(self.search)
|
||||
self.search.setMinimumContentsLength(25)
|
||||
self.search.initialize('scheduler_search_history')
|
||||
self.recipe_box.layout().insertWidget(0, self._cont)
|
||||
self.search.setMinimumContentsLength(15)
|
||||
self.search.search.connect(self.recipe_model.search)
|
||||
self.recipe_model.searched.connect(self.search.search_done,
|
||||
type=Qt.QueuedConnection)
|
||||
@ -153,9 +145,12 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self.recipe_model.un_schedule_recipe(urn)
|
||||
|
||||
add_title_tag = self.add_title_tag.isChecked()
|
||||
keep_issues = u'0'
|
||||
if self.keep_issues.isEnabled():
|
||||
keep_issues = unicode(self.keep_issues.value())
|
||||
custom_tags = unicode(self.custom_tags.text()).strip()
|
||||
custom_tags = [x.strip() for x in custom_tags.split(',')]
|
||||
self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags)
|
||||
self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags, keep_issues)
|
||||
return True
|
||||
|
||||
def initialize_detail_box(self, urn):
|
||||
@ -215,9 +210,16 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
if d < timedelta(days=366):
|
||||
self.last_downloaded.setText(_('Last downloaded')+': '+tm)
|
||||
|
||||
add_title_tag, custom_tags = customize_info
|
||||
add_title_tag, custom_tags, keep_issues = customize_info
|
||||
self.add_title_tag.setChecked(add_title_tag)
|
||||
self.custom_tags.setText(u', '.join(custom_tags))
|
||||
try:
|
||||
keep_issues = int(keep_issues)
|
||||
except:
|
||||
keep_issues = 0
|
||||
self.keep_issues.setValue(keep_issues)
|
||||
self.keep_issues.setEnabled(self.add_title_tag.isChecked())
|
||||
|
||||
|
||||
|
||||
class Scheduler(QObject):
|
||||
@ -299,7 +301,7 @@ class Scheduler(QObject):
|
||||
un = pw = None
|
||||
if account_info is not None:
|
||||
un, pw = account_info
|
||||
add_title_tag, custom_tags = customize_info
|
||||
add_title_tag, custom_tags, keep_issues = customize_info
|
||||
script = self.recipe_model.get_recipe(urn)
|
||||
pt = PersistentTemporaryFile('_builtin.recipe')
|
||||
pt.write(script)
|
||||
@ -312,6 +314,7 @@ class Scheduler(QObject):
|
||||
'recipe':pt.name,
|
||||
'title':recipe.get('title',''),
|
||||
'urn':urn,
|
||||
'keep_issues':keep_issues
|
||||
}
|
||||
self.download_queue.add(urn)
|
||||
self.start_recipe_fetch.emit(arg)
|
||||
|
@ -14,358 +14,403 @@
|
||||
<string>Schedule news download</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/scheduler.png</normaloff>:/images/scheduler.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" rowspan="3">
|
||||
<widget class="QGroupBox" name="recipe_box">
|
||||
<property name="title">
|
||||
<string>Recipes</string>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>&Search:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>search</cstring>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="recipes">
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="animated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="download_all_button">
|
||||
<property name="toolTip">
|
||||
<string>Download all scheduled recipes at once</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Download &all scheduled</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="rnumber">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
<widget class="SearchBox2" name="search"/>
|
||||
</item>
|
||||
<item row="0" column="2" rowspan="3">
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>469</width>
|
||||
<height>504</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>375</width>
|
||||
<height>502</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="margin">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="detail_box">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="detail_box">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>100</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>&Schedule</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>&Schedule</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="blurb">
|
||||
<property name="text">
|
||||
<string>blurb</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="schedule">
|
||||
<property name="text">
|
||||
<string>&Schedule for download:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="blurb">
|
||||
<widget class="QRadioButton" name="daily_button">
|
||||
<property name="text">
|
||||
<string>blurb</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
<string>Every </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="schedule">
|
||||
<widget class="QComboBox" name="day">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>day</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Monday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Tuesday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Wednesday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Thursday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Friday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Saturday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Sunday</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>&Schedule for download:</string>
|
||||
<string>at</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="daily_button">
|
||||
<property name="text">
|
||||
<string>Every </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="day">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>day</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Monday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Tuesday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Wednesday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Thursday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Friday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Saturday</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Sunday</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>at</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTimeEdit" name="time"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QTimeEdit" name="time"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="interval_button">
|
||||
<property name="text">
|
||||
<string>Every </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="interval">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Interval at which to download this recipe. A value of zero means that the recipe will be downloaded every hour.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> days</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>365.100000000000023</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="last_downloaded">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="account">
|
||||
<property name="title">
|
||||
<string>&Account</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="username"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Username:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>username</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>&Password:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>password</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="show_password">
|
||||
<property name="text">
|
||||
<string>&Show password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>For the scheduling to work, you must leave calibre running.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>&Advanced</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="add_title_tag">
|
||||
<property name="text">
|
||||
<string>Add &title as tag</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>&Extra tags:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>custom_tags</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="custom_tags"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="download_button">
|
||||
<property name="text">
|
||||
<string>&Download now</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="interval_button">
|
||||
<property name="text">
|
||||
<string>Every </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="interval">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Interval at which to download this recipe. A value of zero means that the recipe will be downloaded every hour.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> days</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>365.100000000000023</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="last_downloaded">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="account">
|
||||
<property name="title">
|
||||
<string>&Account</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="username"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Username:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>username</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>&Password:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>password</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="show_password">
|
||||
<property name="text">
|
||||
<string>&Show password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>For the scheduling to work, you must leave calibre running.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>&Advanced</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="add_title_tag">
|
||||
<property name="text">
|
||||
<string>Add &title as tag</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>&Extra tags:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>custom_tags</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="toolTip">
|
||||
<string>Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep all (disable).</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Keep at most:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>keep_issues</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QSpinBox" name="keep_issues">
|
||||
<property name="toolTip">
|
||||
<string><p>When set, this option will cause calibre to keep, at most, the specified number of issues of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the total is larger than this number.
|
||||
<p>Note that this feature only works if you have the option to add the title as tag checked, above.
|
||||
<p>Also, the setting for deleting periodicals older than a number of days, below, takes priority over this setting.</string>
|
||||
</property>
|
||||
<property name="specialValueText">
|
||||
<string>all issues</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> issues</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<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="2">
|
||||
<widget class="QLineEdit" name="custom_tags"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="download_button">
|
||||
<property name="text">
|
||||
<string>&Download now</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QTreeView" name="recipes">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="animated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>&Delete downloaded news older than:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>old_news</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="old_news">
|
||||
<property name="toolTip">
|
||||
<string><p>Delete downloaded news older than the specified number of days. Set to zero to disable.
|
||||
<p>You can also control the maximum number of issues of a specific periodical that are kept by clicking the Advanced tab for that periodical above.</string>
|
||||
</property>
|
||||
<property name="specialValueText">
|
||||
<string>never delete</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> days</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="4" column="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -375,24 +420,35 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="old_news">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="download_all_button">
|
||||
<property name="toolTip">
|
||||
<string>Delete downloaded news older than the specified number of days. Set to zero to disable.</string>
|
||||
<string>Download all scheduled news sources at once</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> days</string>
|
||||
<property name="text">
|
||||
<string>Download &all scheduled</string>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>Delete downloaded news older than </string>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLabel" name="count_label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SearchBox2</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre/gui2/search_box.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../../resources/images.qrc"/>
|
||||
</resources>
|
||||
@ -436,12 +492,12 @@
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>456</x>
|
||||
<y>173</y>
|
||||
<x>458</x>
|
||||
<y>155</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>537</x>
|
||||
<y>176</y>
|
||||
<x>573</x>
|
||||
<y>158</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
@ -452,12 +508,12 @@
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>456</x>
|
||||
<y>173</y>
|
||||
<x>458</x>
|
||||
<y>155</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>647</x>
|
||||
<y>176</y>
|
||||
<x>684</x>
|
||||
<y>157</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
@ -468,12 +524,28 @@
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>456</x>
|
||||
<y>239</y>
|
||||
<x>458</x>
|
||||
<y>212</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>495</x>
|
||||
<y>218</y>
|
||||
<x>752</x>
|
||||
<y>215</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>add_title_tag</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>keep_issues</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>508</x>
|
||||
<y>42</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>577</x>
|
||||
<y>108</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
|
@ -178,8 +178,10 @@ class TagCategories(QDialog, Ui_TagCategories):
|
||||
'multiple periods in a row or spaces before '
|
||||
'or after periods.')).exec_()
|
||||
return False
|
||||
for c in self.categories:
|
||||
if strcmp(c, cat_name) == 0:
|
||||
for c in sorted(self.categories.keys(), key=sort_key):
|
||||
if strcmp(c, cat_name) == 0 or \
|
||||
(icu_lower(cat_name).startswith(icu_lower(c) + '.') and\
|
||||
not cat_name.startswith(c + '.')):
|
||||
error_dialog(self, _('Name already used'),
|
||||
_('That name is already used, perhaps with different case.')).exec_()
|
||||
return False
|
||||
|
@ -217,11 +217,15 @@ class SearchBox2(QComboBox): # {{{
|
||||
self.clear()
|
||||
else:
|
||||
self.normalize_state()
|
||||
self.lineEdit().setCompleter(None)
|
||||
self.setEditText(txt)
|
||||
self.line_edit.end(False)
|
||||
if emit_changed:
|
||||
self.changed.emit()
|
||||
self._do_search(store_in_history=store_in_history)
|
||||
c = QCompleter()
|
||||
self.lineEdit().setCompleter(c)
|
||||
c.setCompletionMode(c.PopupCompletion)
|
||||
self.focus_to_library.emit()
|
||||
finally:
|
||||
if not store_in_history:
|
||||
|
@ -21,6 +21,7 @@ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QFont, QSize, \
|
||||
from calibre.ebooks.metadata import title_sort
|
||||
from calibre.gui2 import config, NONE, gprefs
|
||||
from calibre.library.field_metadata import TagsIcons, category_icon_map
|
||||
from calibre.library.database2 import Tag
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.utils.icu import sort_key, lower, strcmp
|
||||
from calibre.utils.search_query_parser import saved_searches
|
||||
@ -69,7 +70,8 @@ class TagDelegate(QItemDelegate): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_minus': 2}
|
||||
TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_plusplus': 2,
|
||||
'mark_minus': 3, 'mark_minusminus': 4}
|
||||
|
||||
class TagsView(QTreeView): # {{{
|
||||
|
||||
@ -127,13 +129,17 @@ class TagsView(QTreeView): # {{{
|
||||
self.set_new_model(self._model.get_filter_categories_by())
|
||||
|
||||
def set_database(self, db, tag_match, sort_by):
|
||||
self.hidden_categories = db.prefs.get('tag_browser_hidden_categories', None)
|
||||
hidden_cats = db.prefs.get('tag_browser_hidden_categories', None)
|
||||
self.hidden_categories = []
|
||||
# migrate from config to db prefs
|
||||
if self.hidden_categories is None:
|
||||
self.hidden_categories = config['tag_browser_hidden_categories']
|
||||
db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories))
|
||||
else:
|
||||
self.hidden_categories = set(self.hidden_categories)
|
||||
if hidden_cats is None:
|
||||
hidden_cats = config['tag_browser_hidden_categories']
|
||||
# strip out any non-existence field keys
|
||||
for cat in hidden_cats:
|
||||
if cat in db.field_metadata:
|
||||
self.hidden_categories.append(cat)
|
||||
db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories))
|
||||
self.hidden_categories = set(self.hidden_categories)
|
||||
|
||||
old = getattr(self, '_model', None)
|
||||
if old is not None:
|
||||
@ -370,14 +376,15 @@ class TagsView(QTreeView): # {{{
|
||||
action='delete_user_category', key=key))
|
||||
self.context_menu.addSeparator()
|
||||
# Hide/Show/Restore categories
|
||||
if not key.startswith('@') or key.find('.') < 0:
|
||||
self.context_menu.addAction(_('Hide category %s') % category,
|
||||
partial(self.context_menu_handler, action='hide',
|
||||
category=category))
|
||||
#if not key.startswith('@') or key.find('.') < 0:
|
||||
self.context_menu.addAction(_('Hide category %s') % category,
|
||||
partial(self.context_menu_handler, action='hide',
|
||||
category=key))
|
||||
if self.hidden_categories:
|
||||
m = self.context_menu.addMenu(_('Show category'))
|
||||
for col in sorted(self.hidden_categories, key=sort_key):
|
||||
m.addAction(col,
|
||||
for col in sorted(self.hidden_categories,
|
||||
key=lambda x: sort_key(self.db.field_metadata[x]['name'])):
|
||||
m.addAction(self.db.field_metadata[col]['name'],
|
||||
partial(self.context_menu_handler, action='show', category=col))
|
||||
|
||||
# search by category
|
||||
@ -540,6 +547,7 @@ class TagTreeItem(object): # {{{
|
||||
self.id_set = set()
|
||||
self.is_gst = False
|
||||
self.boxed = False
|
||||
self.icon_state_map = list(map(QVariant, icon_map))
|
||||
if self.parent is not None:
|
||||
self.parent.append(self)
|
||||
if data is None:
|
||||
@ -554,9 +562,11 @@ class TagTreeItem(object): # {{{
|
||||
self.bold_font = QVariant(self.bold_font)
|
||||
self.category_key = category_key
|
||||
self.temporary = temporary
|
||||
self.tag = Tag(data)
|
||||
self.tag.is_hierarchical = category_key.startswith('@')
|
||||
elif self.type == self.TAG:
|
||||
icon_map[0] = data.icon
|
||||
self.tag, self.icon_state_map = data, list(map(QVariant, icon_map))
|
||||
self.tag = data
|
||||
if tooltip:
|
||||
self.tooltip = tooltip + ' '
|
||||
else:
|
||||
@ -593,6 +603,8 @@ class TagTreeItem(object): # {{{
|
||||
if role == Qt.EditRole:
|
||||
return QVariant(self.py_name)
|
||||
if role == Qt.DecorationRole:
|
||||
if self.tag.state:
|
||||
return self.icon_state_map[self.tag.state]
|
||||
return self.icon
|
||||
if role == Qt.FontRole:
|
||||
return self.bold_font
|
||||
@ -642,11 +654,21 @@ class TagTreeItem(object): # {{{
|
||||
'''
|
||||
set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES
|
||||
'''
|
||||
if self.type == self.TAG:
|
||||
if set_to is None:
|
||||
self.tag.state = (self.tag.state + 1)%3
|
||||
else:
|
||||
self.tag.state = set_to
|
||||
if set_to is None:
|
||||
while True:
|
||||
self.tag.state = (self.tag.state + 1)%5
|
||||
if self.tag.state == TAG_SEARCH_STATES['mark_plus'] or \
|
||||
self.tag.state == TAG_SEARCH_STATES['mark_minus']:
|
||||
if self.tag.is_editable:
|
||||
break
|
||||
elif self.tag.state == TAG_SEARCH_STATES['mark_plusplus'] or\
|
||||
self.tag.state == TAG_SEARCH_STATES['mark_minusminus']:
|
||||
if self.tag.is_hierarchical and len(self.children):
|
||||
break
|
||||
else:
|
||||
break
|
||||
else:
|
||||
self.tag.state = set_to
|
||||
|
||||
def child_tags(self):
|
||||
res = []
|
||||
@ -677,7 +699,8 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags']
|
||||
self.drag_drop_finished = drag_drop_finished
|
||||
|
||||
self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('minus.png'))]
|
||||
self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('plusplus.png')),
|
||||
QIcon(I('minus.png')), QIcon(I('minusminus.png'))]
|
||||
self.db = db
|
||||
self.tags_view = parent
|
||||
self.hidden_categories = hidden_categories
|
||||
@ -691,26 +714,33 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
|
||||
data = self.get_node_tree(config['sort_tags_by'])
|
||||
gst = db.prefs.get('grouped_search_terms', {})
|
||||
self.root_item = TagTreeItem()
|
||||
self.root_item = TagTreeItem(icon_map=self.icon_state_map)
|
||||
self.category_nodes = []
|
||||
|
||||
last_category_node = None
|
||||
category_node_map = {}
|
||||
self.category_node_tree = {}
|
||||
for i, r in enumerate(self.row_map):
|
||||
if self.hidden_categories and self.categories[i] in self.hidden_categories:
|
||||
continue
|
||||
for i, key in enumerate(self.row_map):
|
||||
if self.hidden_categories:
|
||||
if key in self.hidden_categories:
|
||||
continue
|
||||
found = False
|
||||
for cat in self.hidden_categories:
|
||||
if cat.startswith('@') and key.startswith(cat + '.'):
|
||||
found = True
|
||||
if found:
|
||||
continue
|
||||
is_gst = False
|
||||
if r.startswith('@') and r[1:] in gst:
|
||||
tt = _(u'The grouped search term name is "{0}"').format(r[1:])
|
||||
if key.startswith('@') and key[1:] in gst:
|
||||
tt = _(u'The grouped search term name is "{0}"').format(key[1:])
|
||||
is_gst = True
|
||||
elif r == 'news':
|
||||
elif key == 'news':
|
||||
tt = ''
|
||||
else:
|
||||
tt = _(u'The lookup/search name is "{0}"').format(r)
|
||||
tt = _(u'The lookup/search name is "{0}"').format(key)
|
||||
|
||||
if r.startswith('@'):
|
||||
path_parts = [p for p in r.split('.')]
|
||||
if key.startswith('@'):
|
||||
path_parts = [p for p in key.split('.')]
|
||||
path = ''
|
||||
last_category_node = self.root_item
|
||||
tree_root = self.category_node_tree
|
||||
@ -719,9 +749,10 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
if path not in category_node_map:
|
||||
node = TagTreeItem(parent=last_category_node,
|
||||
data=p[1:] if i == 0 else p,
|
||||
category_icon=self.category_icon_map[r],
|
||||
tooltip=tt if path == r else path,
|
||||
category_key=path)
|
||||
category_icon=self.category_icon_map[key],
|
||||
tooltip=tt if path == key else path,
|
||||
category_key=path,
|
||||
icon_map=self.icon_state_map)
|
||||
last_category_node = node
|
||||
category_node_map[path] = node
|
||||
self.category_nodes.append(node)
|
||||
@ -736,11 +767,12 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
path += '.'
|
||||
else:
|
||||
node = TagTreeItem(parent=self.root_item,
|
||||
data=self.categories[i],
|
||||
category_icon=self.category_icon_map[r],
|
||||
tooltip=tt, category_key=r)
|
||||
data=self.categories[key],
|
||||
category_icon=self.category_icon_map[key],
|
||||
tooltip=tt, category_key=key,
|
||||
icon_map=self.icon_state_map)
|
||||
node.is_gst = False
|
||||
category_node_map[r] = node
|
||||
category_node_map[key] = node
|
||||
last_category_node = node
|
||||
self.category_nodes.append(node)
|
||||
self.refresh(data=data)
|
||||
@ -1015,7 +1047,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
def get_node_tree(self, sort):
|
||||
old_row_map = self.row_map[:]
|
||||
self.row_map = []
|
||||
self.categories = []
|
||||
self.categories = {}
|
||||
|
||||
# Get the categories
|
||||
if self.search_restriction:
|
||||
@ -1062,7 +1094,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
for category in tb_categories:
|
||||
if category in data: # The search category can come and go
|
||||
self.row_map.append(category)
|
||||
self.categories.append(tb_categories[category]['name'])
|
||||
self.categories[category] = tb_categories[category]['name']
|
||||
|
||||
if len(old_row_map) != 0 and len(old_row_map) != len(self.row_map):
|
||||
# A category has been added or removed. We must force a rebuild of
|
||||
@ -1163,7 +1195,8 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
sub_cat = TagTreeItem(parent=category, data = name,
|
||||
tooltip = None, temporary=True,
|
||||
category_icon = category_node.icon,
|
||||
category_key=category_node.category_key)
|
||||
category_key=category_node.category_key,
|
||||
icon_map=self.icon_state_map)
|
||||
self.endInsertRows()
|
||||
else: # by 'first letter'
|
||||
cl = cl_list[idx]
|
||||
@ -1173,7 +1206,8 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
data = collapse_letter,
|
||||
category_icon = category_node.icon,
|
||||
tooltip = None, temporary=True,
|
||||
category_key=category_node.category_key)
|
||||
category_key=category_node.category_key,
|
||||
icon_map=self.icon_state_map)
|
||||
node_parent = sub_cat
|
||||
else:
|
||||
node_parent = category
|
||||
@ -1284,16 +1318,19 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
return False
|
||||
|
||||
user_cats = self.db.prefs.get('user_categories', {})
|
||||
user_cat_keys_lower = [icu_lower(k) for k in user_cats]
|
||||
ckey = item.category_key[1:]
|
||||
ckey_lower = icu_lower(ckey)
|
||||
dotpos = ckey.rfind('.')
|
||||
if dotpos < 0:
|
||||
nkey = val
|
||||
else:
|
||||
nkey = ckey[:dotpos+1] + val
|
||||
for c in user_cats:
|
||||
if c.startswith(ckey):
|
||||
nkey_lower = icu_lower(nkey)
|
||||
for c in sorted(user_cats.keys(), key=sort_key):
|
||||
if icu_lower(c).startswith(ckey_lower):
|
||||
if len(c) == len(ckey):
|
||||
if nkey in user_cats:
|
||||
if nkey_lower in user_cat_keys_lower:
|
||||
error_dialog(self.tags_view, _('Rename user category'),
|
||||
_('The name %s is already used')%nkey, show=True)
|
||||
return False
|
||||
@ -1301,7 +1338,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
del user_cats[ckey]
|
||||
elif c[len(ckey)] == '.':
|
||||
rest = c[len(ckey):]
|
||||
if (nkey + rest) in user_cats:
|
||||
if icu_lower(nkey + rest) in user_cat_keys_lower:
|
||||
error_dialog(self.tags_view, _('Rename user category'),
|
||||
_('The name %s is already used')%(nkey+rest), show=True)
|
||||
return False
|
||||
@ -1477,16 +1514,15 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
def reset_all_states(self, except_=None):
|
||||
update_list = []
|
||||
def process_tag(tag_item):
|
||||
if tag_item.type != TagTreeItem.CATEGORY:
|
||||
tag = tag_item.tag
|
||||
if tag is except_:
|
||||
tag_index = self.createIndex(tag_item.row(), 0, tag_item)
|
||||
self.dataChanged.emit(tag_index, tag_index)
|
||||
elif tag.state != 0 or tag in update_list:
|
||||
tag_index = self.createIndex(tag_item.row(), 0, tag_item)
|
||||
tag.state = 0
|
||||
update_list.append(tag)
|
||||
self.dataChanged.emit(tag_index, tag_index)
|
||||
tag = tag_item.tag
|
||||
if tag is except_:
|
||||
tag_index = self.createIndex(tag_item.row(), 0, tag_item)
|
||||
self.dataChanged.emit(tag_index, tag_index)
|
||||
elif tag.state != 0 or tag in update_list:
|
||||
tag_index = self.createIndex(tag_item.row(), 0, tag_item)
|
||||
tag.state = 0
|
||||
update_list.append(tag)
|
||||
self.dataChanged.emit(tag_index, tag_index)
|
||||
for t in tag_item.children:
|
||||
process_tag(t)
|
||||
|
||||
@ -1503,13 +1539,11 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
'''
|
||||
if not index.isValid(): return False
|
||||
item = index.internalPointer()
|
||||
if item.type == TagTreeItem.TAG:
|
||||
item.toggle(set_to=set_to)
|
||||
if exclusive:
|
||||
self.reset_all_states(except_=item.tag)
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
return False
|
||||
item.toggle(set_to=set_to)
|
||||
if exclusive:
|
||||
self.reset_all_states(except_=item.tag)
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
|
||||
def tokens(self):
|
||||
ans = []
|
||||
@ -1523,19 +1557,31 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
# into the search string only once. The nodes_seen set helps us do that
|
||||
nodes_seen = set()
|
||||
|
||||
node_searches = {TAG_SEARCH_STATES['mark_plus'] : 'true',
|
||||
TAG_SEARCH_STATES['mark_plusplus'] : '.true',
|
||||
TAG_SEARCH_STATES['mark_minus'] : 'false',
|
||||
TAG_SEARCH_STATES['mark_minusminus'] : '.false'}
|
||||
|
||||
for node in self.category_nodes:
|
||||
if node.tag.state:
|
||||
ans.append('%s:%s'%(node.category_key, node_searches[node.tag.state]))
|
||||
|
||||
key = node.category_key
|
||||
for tag_item in node.child_tags():
|
||||
tag = tag_item.tag
|
||||
if tag.state != TAG_SEARCH_STATES['clear']:
|
||||
prefix = ' not ' if tag.state == TAG_SEARCH_STATES['mark_minus'] \
|
||||
else ''
|
||||
if tag.state == TAG_SEARCH_STATES['mark_minus'] or \
|
||||
tag.state == TAG_SEARCH_STATES['mark_minusminus']:
|
||||
prefix = ' not '
|
||||
else:
|
||||
prefix = ''
|
||||
category = tag.category if key != 'news' else 'tag'
|
||||
if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating
|
||||
ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
|
||||
else:
|
||||
name = original_name(tag)
|
||||
use_prefix = tag.is_hierarchical
|
||||
use_prefix = tag.state in [TAG_SEARCH_STATES['mark_plusplus'],
|
||||
TAG_SEARCH_STATES['mark_minusminus']]
|
||||
if category == 'tags':
|
||||
if name in tags_seen:
|
||||
continue
|
||||
|
@ -419,28 +419,23 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
|
||||
def get_user_category_matches(self, location, query, candidates):
|
||||
res = set([])
|
||||
if self.db_prefs is None:
|
||||
if self.db_prefs is None or len(query) < 2:
|
||||
return res
|
||||
user_cats = self.db_prefs.get('user_categories', [])
|
||||
c = set(candidates)
|
||||
l = location.rfind('.')
|
||||
if l > 0:
|
||||
alt_loc = location[0:l]
|
||||
alt_item = location[l+1:]
|
||||
|
||||
if query.startswith('.'):
|
||||
check_subcats = True
|
||||
query = query[1:]
|
||||
else:
|
||||
alt_loc = None
|
||||
check_subcats = False
|
||||
|
||||
for key in user_cats:
|
||||
if key == location or key.startswith(location + '.'):
|
||||
if key == location or (check_subcats and key.startswith(location + '.')):
|
||||
for (item, category, ign) in user_cats[key]:
|
||||
s = self.get_matches(category, '=' + item, candidates=c)
|
||||
c -= s
|
||||
res |= s
|
||||
elif key == alt_loc:
|
||||
for (item, category, ign) in user_cats[key]:
|
||||
if item == alt_item:
|
||||
s = self.get_matches(category, '=' + item, candidates=c)
|
||||
c -= s
|
||||
res |= s
|
||||
if query == 'false':
|
||||
return candidates - res
|
||||
return res
|
||||
|
@ -1195,7 +1195,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
i += 1
|
||||
else:
|
||||
new_cats['.'.join(comps)] = user_cats[k]
|
||||
self.prefs.set('user_categories', new_cats)
|
||||
try:
|
||||
self.prefs.set('user_categories', new_cats)
|
||||
except:
|
||||
pass
|
||||
return new_cats
|
||||
|
||||
def get_categories(self, sort='name', ids=None, icon_map=None):
|
||||
@ -1500,18 +1503,30 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
|
||||
############# End get_categories
|
||||
|
||||
def tags_older_than(self, tag, delta):
|
||||
def tags_older_than(self, tag, delta, must_have_tag=None):
|
||||
'''
|
||||
Return the ids of all books having the tag ``tag`` that are older than
|
||||
than the specified time. tag comparison is case insensitive.
|
||||
|
||||
:param delta: A timedelta object or None. If None, then all ids with
|
||||
the tag are returned.
|
||||
:param must_have_tag: If not None the list of matches will be
|
||||
restricted to books that have this tag
|
||||
'''
|
||||
tag = tag.lower().strip()
|
||||
mht = must_have_tag.lower().strip() if must_have_tag else None
|
||||
now = nowf()
|
||||
tindex = self.FIELD_MAP['timestamp']
|
||||
gindex = self.FIELD_MAP['tags']
|
||||
iindex = self.FIELD_MAP['id']
|
||||
for r in self.data._data:
|
||||
if r is not None:
|
||||
if (now - r[tindex]) > delta:
|
||||
if delta is None or (now - r[tindex]) > delta:
|
||||
tags = r[gindex]
|
||||
if tags and tag in [x.strip() for x in
|
||||
tags.lower().split(',')]:
|
||||
yield r[self.FIELD_MAP['id']]
|
||||
if tags:
|
||||
tags = [x.strip() for x in tags.lower().split(',')]
|
||||
if tag in tags and (mht is None or mht in tags):
|
||||
yield r[iindex]
|
||||
|
||||
def get_next_series_num_for(self, series):
|
||||
series_id = self.conn.get('SELECT id from series WHERE name=?',
|
||||
|
@ -436,25 +436,26 @@ Tag Browser
|
||||
.. image:: images/tag_browser.png
|
||||
:class: float-left-img
|
||||
|
||||
The Tag Browser allows you to easily browse your collection by Author/Tags/Series/etc. If you click on any Item in the Tag Browser, for example, the Author name, Isaac Asimov, then the list of books to the right is restricted to books by that author. Clicking once again on Isaac Asimov will restrict the list of books to books not by Isaac Asimov. A third click will remove the restriction. If you hold down the Ctrl or Shift keys and click on multiple items, then restrictions based on multiple items are created. For example you could Hold Ctrl and click on the tags History and Europe for find books on European history. The Tag Browser works by constructing search expressions that are automatically entered into the Search bar. It is a good way to learn how to construct basic search expressions.
|
||||
The Tag Browser allows you to easily browse your collection by Author/Tags/Series/etc. If you click on any item in the Tag Browser, for example the author name Isaac Asimov, then the list of books to the right is restricted to showing books by that author. You can click on category names as well. For example, clicking on "Series" will show you all books in any series.
|
||||
|
||||
There is a search bar at the top of the Tag Browser that allows you to easily find any item in the Tag Browser. In addition, you can right click on any item and choose to hide it or rename it or open a "Manage x" dialog that allows you to manage items of that kind. For example the "Manage Authors" dialog allows you to rename authors and control how their names are sorted.
|
||||
The first click on an item will restrict the list of books to those that contain/match the item. Continuing the above example, clicking on Isaac Asimov will show books by that author. Clicking again on the item will change what is shown, depending on whether the item has children (see sub-categories and hierarchical items below). Continuing the Isaac Asimov example, clicking again on Isaac Asimov will restrict the list of books to those not by Isaac Asimov. A third click will remove the restriction, showing all books. If you hold down the Ctrl or Shift keys and click on multiple items, then restrictions based on multiple items are created. For example you could hold Ctrl and click on the tags History and Europe for find books on European history. The Tag Browser works by constructing search expressions that are automatically entered into the Search bar. Looking at what the Tag Browser generates is a good way to learn how to construct basic search expressions.
|
||||
|
||||
Items in the Tag browser have their icons partially colored. The amount of color depends on the average rating of the books in that category. So for example if the books by Isaac Asimov have an average of four stars, the icon for Isaac Asimov in the Tag Browser will be 4/5th colored. You can hover your mouse over the icon to see the average rating.
|
||||
|
||||
For convenience, you can drag and drop books from the book list to items in the Tag Browser and that item will be automatically applied to the dropped books. For example, dragging a book to Isaac Asimov will set the author of that book to Isaac Asimov or dragging it to the tag History will add the tag History to its tags.
|
||||
The outer-level items in the tag browser such as Authors and Series are called categories. You can create your own categories, called User Categories, which are useful for organizing items. For example, you can use the User Categories Editor (push the Manage User Categories button) to create a user category called Favorite Authors, then put the items for your favorites into the category. User categories can have sub-categories. For example, the user category Favorites.Authors is a sub-category of Favorites. You might also have Favorites.Series, in which case there will be two sub-categories under Favorites. Sub-categories can be created by right-clicking on a user category, choosing "Add sub-category to ...", and entering the sub-category name; or by using the User Categories Editor by entering names like the Favorites example above.
|
||||
|
||||
The outer-level items in the tag browser such as Authors and Series are called categories. You can create your own categories, called User Categories, which are useful for organizing items. For example, you can use the user categories editor (push the Manage User Categories button) to create a user category called Favorite Authors, then put the items for your favorites into the category. User categories act like built-in categories; you can click on items to search for them. You can search for all items in a category by right-clicking on the category name and choosing "Search for books in ...".
|
||||
You can search user categories in the same way as built-in categories, by clicking on them. There are four different searches cycled through by clicking: "everything matching an item in the category" indicated by a single green plus sign, "everything matching an item in the category or its sub-categories" indicated by two green plus signs, "everything not matching an item in the category" shown by a single red minus sign, and "everything not matching an item in the category or its sub-categories" shown by two red minus signs.
|
||||
|
||||
User categories can have sub-categories. For example, the user category Favorites.Authors is a sub-category of Favorites. You might also have Favorites.Series, in which case there will be two sub-categories under Favorites. Sub-categories can be created using Manage User Categories by entering names like the Favorites example. They can also be created by right-clicking on a user category, choosing "Add sub-category to ...", and entering the category name.
|
||||
It is also possible to create hierarchies inside some of the text categories such as tags, series, and custom columns. These hierarchies show with the small triangle, permitting the sub-items to be hidden. To use hierarchies of items in a category, you must first go to Preferences / Look & Feel and enter the category name(s) into the "Categories with hierarchical items" box. Once this is done, items in that category that contain periods will be shown using the small triangle. For example, assume you create a custom column called "Genre" and indicate that it contains hierarchical items. Once done, items such as Mystery.Thriller and Mystery.English will display as Mystery with the small triangle next to it. Clicking on the triangle will show Thriller and English as sub-items.
|
||||
|
||||
It is also possible to create hierarchies inside some of the built-in categories (the text categories). These hierarchies show with the small triangle permitting the sub-items to be hidden. To use hierarchies in a category, you must first go to Preferences / Look & Feel and enter the category name(s) into the "Categories with hierarchical items" box. Once this is done, items in that category that contain periods will be shown using the small triangle. For example, assume you create a custom column called "Genre" and indicate that it contains hierarchical items. Once done, items such as Mystery.Thriller and Mystery.English will display as Mystery with the small triangle next to it. Clicking on the triangle will show Thriller and English as sub-items.
|
||||
Hierarchical items (items with children) use the same four 'click-on' searches as user categories. Items that do not have children use two of the searches: "everything matching" and "everything not matching".
|
||||
|
||||
You can drag and drop items in the Tag browser onto user categories to add them to that category.
|
||||
You can drag and drop items in the Tag browser onto user categories to add them to that category. If the source is a user category, holding the shift key while dragging will move the item to the new category. You can also drag and drop books from the book list onto items in the Tag Browser; dropping a book on an item causes that item to be automatically applied to the dropped books. For example, dragging a book onto Isaac Asimov will set the author of that book to Isaac Asimov. Dropping it onto the tag History will add the tag History to the book's tags.
|
||||
|
||||
There is a search bar at the top of the Tag Browser that allows you to easily find any item in the Tag Browser. In addition, you can right click on any item and choose one of several operations. Some examples are to hide the it, rename it, or open a "Manage x" dialog that allows you to manage items of that kind. For example, the "Manage Authors" dialog allows you to rename authors and control how their names are sorted.
|
||||
|
||||
You can control how items are sorted in the Tag browser via the box at the bottom of the Tag Browser. You can choose to sort by name, average rating or popularity (popularity is the number of books with an item in your library; for example; the popularity of Isaac Asimov is the number of book sin your library by Isaac Asimov).
|
||||
|
||||
|
||||
Jobs
|
||||
-----
|
||||
.. image:: images/jobs.png
|
||||
|
@ -201,12 +201,14 @@ class SchedulerConfig(object):
|
||||
self.root.append(sr)
|
||||
self.write_scheduler_file()
|
||||
|
||||
def customize_recipe(self, urn, add_title_tag, custom_tags):
|
||||
# 'keep_issues' argument for recipe-specific number of copies to keep
|
||||
def customize_recipe(self, urn, add_title_tag, custom_tags, keep_issues):
|
||||
with self.lock:
|
||||
for x in list(self.iter_customization()):
|
||||
if x.get('id') == urn:
|
||||
self.root.remove(x)
|
||||
cs = E.recipe_customization({
|
||||
'keep_issues' : keep_issues,
|
||||
'id' : urn,
|
||||
'add_title_tag' : 'yes' if add_title_tag else 'no',
|
||||
'custom_tags' : ','.join(custom_tags),
|
||||
@ -317,16 +319,18 @@ class SchedulerConfig(object):
|
||||
return x.get('username', ''), x.get('password', '')
|
||||
|
||||
def get_customize_info(self, urn):
|
||||
keep_issues = 0
|
||||
add_title_tag = True
|
||||
custom_tags = []
|
||||
with self.lock:
|
||||
for x in self.iter_customization():
|
||||
if x.get('id', False) == urn:
|
||||
keep_issues = x.get('keep_issues', '0')
|
||||
add_title_tag = x.get('add_title_tag', 'yes') == 'yes'
|
||||
custom_tags = [i.strip() for i in x.get('custom_tags',
|
||||
'').split(',')]
|
||||
break
|
||||
return add_title_tag, custom_tags
|
||||
return add_title_tag, custom_tags, keep_issues
|
||||
|
||||
def get_schedule_info(self, urn):
|
||||
with self.lock:
|
||||
|
@ -196,6 +196,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
lang_map = {}
|
||||
self.all_urns = set([])
|
||||
self.showing_count = 0
|
||||
self.builtin_count = 0
|
||||
for x in self.custom_recipe_collection:
|
||||
urn = x.get('id')
|
||||
self.all_urns.add(urn)
|
||||
@ -211,6 +212,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
lang_map[lang] = factory(NewsCategory, new_root, lang)
|
||||
factory(NewsItem, lang_map[lang], urn, x.get('title'))
|
||||
self.showing_count += 1
|
||||
self.builtin_count += 1
|
||||
for x in self.scheduler_config.iter_recipes():
|
||||
urn = x.get('id')
|
||||
if urn not in self.all_urns:
|
||||
@ -354,9 +356,9 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
self.scheduler_config.schedule_recipe(self.recipe_from_urn(urn),
|
||||
sched_type, schedule)
|
||||
|
||||
def customize_recipe(self, urn, add_title_tag, custom_tags):
|
||||
def customize_recipe(self, urn, add_title_tag, custom_tags, keep_issues):
|
||||
self.scheduler_config.customize_recipe(urn, add_title_tag,
|
||||
custom_tags)
|
||||
custom_tags, keep_issues)
|
||||
|
||||
def get_to_be_downloaded_recipes(self):
|
||||
ans = self.scheduler_config.get_to_be_downloaded_recipes()
|
||||
|
Loading…
x
Reference in New Issue
Block a user