mirror of
git://github.com/kovidgoyal/calibre.git
synced 2026-05-09 04:03:52 +02:00
merge from trunk
This commit is contained in:
commit
b0d45bab5f
20 changed files with 803 additions and 454 deletions
|
|
@ -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 @@ def scheduled_recipe_fetched(self, job):
|
|||
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 @@
|
|||
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 @@ def __init__(self, recipe_model, parent=None):
|
|||
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 @@ def commit(self, urn=None):
|
|||
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 @@ def hm(x): return (x-x%3600)//3600, (x%3600 - (x%3600)%60)//60
|
|||
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 @@ def do_download(self, urn):
|
|||
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 @@ def do_download(self, urn):
|
|||
'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 @@ def add_category(self):
|
|||
'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 @@ def set_search_string(self, txt, store_in_history=False, emit_changed=True):
|
|||
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 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 @@ def paint(self, painter, option, index):
|
|||
|
||||
# }}}
|
||||
|
||||
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 @@ def reread_collapse_parameters(self):
|
|||
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 @@ def add_node_tree(tree_dict, m, path):
|
|||
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 @@ def __init__(self, data=None, category_icon=None, icon_map=None,
|
|||
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 @@ def __init__(self, data=None, category_icon=None, icon_map=None,
|
|||
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 @@ def category_data(self, role):
|
|||
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 @@ def toggle(self, set_to=None):
|
|||
'''
|
||||
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 @@ def __init__(self, db, parent, hidden_categories=None,
|
|||
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 @@ def __init__(self, db, parent, hidden_categories=None,
|
|||
|
||||
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 @@ def __init__(self, db, parent, hidden_categories=None,
|
|||
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 @@ def __init__(self, db, parent, hidden_categories=None,
|
|||
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 @@ def set_search_restriction(self, s):
|
|||
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 @@ def get_node_tree(self, sort):
|
|||
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 @@ def process_one_node(category, state_map):
|
|||
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 @@ def process_one_node(category, state_map):
|
|||
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 @@ def setData(self, index, value, role=Qt.EditRole):
|
|||
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 @@ def setData(self, index, value, role=Qt.EditRole):
|
|||
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 @@ def rowCount(self, parent):
|
|||
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 @@ def toggle(self, index, exclusive, set_to=None):
|
|||
'''
|
||||
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 @@ def tokens(self):
|
|||
# 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 @@ def get_numeric_matches(self, location, query, candidates, val_func = None):
|
|||
|
||||
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 @@ def clean_user_categories(self):
|
|||
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 @@ def get_categories(self, sort='name', ids=None, icon_map=None):
|
|||
|
||||
############# 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 @@ def schedule_recipe(self, recipe, schedule_type, schedule, last_downloaded=None)
|
|||
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 @@ def get_account_info(self, urn):
|
|||
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 @@ def ok(urn):
|
|||
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 @@ def ok(urn):
|
|||
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 @@ def schedule_recipe(self, urn, sched_type, schedule):
|
|||
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…
Reference in a new issue