mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Switch to using the new custom recipe dialog
This commit is contained in:
parent
63ba84eed1
commit
f89b974b14
Binary file not shown.
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 30 KiB |
@ -32,10 +32,10 @@ blog into an ebook, we rely on the :term:`RSS` feed of the blog::
|
|||||||
http://blog.calibre-ebook.com/feeds/posts/default
|
http://blog.calibre-ebook.com/feeds/posts/default
|
||||||
|
|
||||||
I got the RSS URL by looking under "Subscribe to" at the bottom of the blog
|
I got the RSS URL by looking under "Subscribe to" at the bottom of the blog
|
||||||
page and choosing Posts->Atom. To make calibre download the feeds and convert
|
page and choosing :guilabel:`Posts->Atom`. To make calibre download the feeds and convert
|
||||||
them into an ebook, you should right click the :guilabel:`Fetch news` button
|
them into an ebook, you should right click the :guilabel:`Fetch news` button
|
||||||
and then the :guilabel:`Add a custom news source` menu item. A dialog similar
|
and then the :guilabel:`Add a custom news source` menu item and then the
|
||||||
to that shown below should open up.
|
:guilabel:`New Recipe` button. A dialog similar to that shown below should open up.
|
||||||
|
|
||||||
.. image:: images/custom_news.png
|
.. image:: images/custom_news.png
|
||||||
:align: center
|
:align: center
|
||||||
@ -44,7 +44,9 @@ First enter ``calibre Blog`` into the :guilabel:`Recipe title` field. This will
|
|||||||
|
|
||||||
The next two fields (:guilabel:`Oldest article` and :guilabel:`Max. number of articles`) allow you some control over how many articles should be downloaded from each feed, and they are pretty self explanatory.
|
The next two fields (:guilabel:`Oldest article` and :guilabel:`Max. number of articles`) allow you some control over how many articles should be downloaded from each feed, and they are pretty self explanatory.
|
||||||
|
|
||||||
To add the feeds to the recipe, enter the feed title and the feed URL and click the :guilabel:`Add feed` button. Once you have added the feed, simply click the :guilabel:`Add/update recipe` button and you're done! Close the dialog.
|
To add the feeds to the recipe, enter the feed title and the feed URL and click
|
||||||
|
the :guilabel:`Add feed` button. Once you have added the feed, simply click the
|
||||||
|
:guilabel:`Save` button and you're done! Close the dialog.
|
||||||
|
|
||||||
To test your new :term:`recipe`, click the :guilabel:`Fetch news` button and in the :guilabel:`Custom news sources` sub-menu click :guilabel:`calibre Blog`. After a couple of minutes, the newly downloaded ebook of blog posts will appear in the main library view (if you have your reader connected, it will be put onto the reader instead of into the library). Select it and hit the :guilabel:`View` button to read!
|
To test your new :term:`recipe`, click the :guilabel:`Fetch news` button and in the :guilabel:`Custom news sources` sub-menu click :guilabel:`calibre Blog`. After a couple of minutes, the newly downloaded ebook of blog posts will appear in the main library view (if you have your reader connected, it will be put onto the reader instead of into the library). Select it and hit the :guilabel:`View` button to read!
|
||||||
|
|
||||||
|
@ -489,11 +489,10 @@ class Scheduler(QObject):
|
|||||||
self.lock.unlock()
|
self.lock.unlock()
|
||||||
|
|
||||||
def customize_feeds(self, *args):
|
def customize_feeds(self, *args):
|
||||||
from calibre.gui2.dialogs.user_profiles import UserProfiles
|
from calibre.gui2.dialogs.custom_recipes import CustomRecipes
|
||||||
d = UserProfiles(self._parent, self.recipe_model)
|
d = CustomRecipes(self.recipe_model, self._parent)
|
||||||
try:
|
try:
|
||||||
d.exec_()
|
d.exec_()
|
||||||
d.break_cycles()
|
|
||||||
finally:
|
finally:
|
||||||
d.deleteLater()
|
d.deleteLater()
|
||||||
|
|
||||||
|
@ -1,449 +0,0 @@
|
|||||||
__license__ = 'GPL v3'
|
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|
||||||
|
|
||||||
import time, os
|
|
||||||
|
|
||||||
from PyQt5.Qt import (QUrl, QAbstractListModel, Qt, QFont)
|
|
||||||
|
|
||||||
from calibre.web.feeds.recipes import compile_recipe, custom_recipes
|
|
||||||
from calibre.web.feeds.news import AutomaticNewsRecipe
|
|
||||||
from calibre.gui2.dialogs.user_profiles_ui import Ui_Dialog
|
|
||||||
from calibre.gui2 import (
|
|
||||||
error_dialog, question_dialog, open_url, choose_files, ResizableDialog,
|
|
||||||
open_local_file)
|
|
||||||
from calibre.gui2.widgets import PythonHighlighter
|
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
|
||||||
from calibre.utils.icu import sort_key
|
|
||||||
|
|
||||||
class CustomRecipeModel(QAbstractListModel):
|
|
||||||
|
|
||||||
def __init__(self, recipe_model):
|
|
||||||
QAbstractListModel.__init__(self)
|
|
||||||
self.recipe_model = recipe_model
|
|
||||||
|
|
||||||
def title(self, index):
|
|
||||||
row = index.row()
|
|
||||||
if row > -1 and row < self.rowCount():
|
|
||||||
return self.recipe_model.custom_recipe_collection[row].get('title', '')
|
|
||||||
|
|
||||||
def script(self, index):
|
|
||||||
row = index.row()
|
|
||||||
if row > -1 and row < self.rowCount():
|
|
||||||
urn = self.recipe_model.custom_recipe_collection[row].get('id')
|
|
||||||
return self.recipe_model.get_recipe(urn)
|
|
||||||
|
|
||||||
def has_title(self, title):
|
|
||||||
for x in self.recipe_model.custom_recipe_collection:
|
|
||||||
if x.get('title', False) == title:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def rowCount(self, *args):
|
|
||||||
try:
|
|
||||||
return len(self.recipe_model.custom_recipe_collection)
|
|
||||||
except:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def data(self, index, role):
|
|
||||||
if role == Qt.DisplayRole:
|
|
||||||
ans = self.title(index)
|
|
||||||
if ans is not None:
|
|
||||||
return (ans)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def replace_by_title(self, title, script):
|
|
||||||
urn = None
|
|
||||||
for x in self.recipe_model.custom_recipe_collection:
|
|
||||||
if x.get('title', False) == title:
|
|
||||||
urn = x.get('id')
|
|
||||||
if urn is not None:
|
|
||||||
self.beginResetModel()
|
|
||||||
self.recipe_model.update_custom_recipe(urn, title, script)
|
|
||||||
self.endResetModel()
|
|
||||||
|
|
||||||
def replace_many_by_title(self, scriptmap):
|
|
||||||
script_urn_map = {}
|
|
||||||
for title, script in scriptmap.iteritems():
|
|
||||||
urn = None
|
|
||||||
for x in self.recipe_model.custom_recipe_collection:
|
|
||||||
if x.get('title', False) == title:
|
|
||||||
urn = x.get('id')
|
|
||||||
if urn is not None:
|
|
||||||
script_urn_map.update({urn: (title, script)})
|
|
||||||
|
|
||||||
if script_urn_map:
|
|
||||||
self.beginResetModel()
|
|
||||||
self.recipe_model.update_custom_recipes(script_urn_map)
|
|
||||||
self.endResetModel()
|
|
||||||
|
|
||||||
def add(self, title, script):
|
|
||||||
self.beginResetModel()
|
|
||||||
self.recipe_model.add_custom_recipe(title, script)
|
|
||||||
self.endResetModel()
|
|
||||||
|
|
||||||
def add_many(self, scriptmap):
|
|
||||||
self.beginResetModel()
|
|
||||||
self.recipe_model.add_custom_recipes(scriptmap)
|
|
||||||
self.endResetModel()
|
|
||||||
|
|
||||||
def remove(self, rows):
|
|
||||||
urns = []
|
|
||||||
for r in rows:
|
|
||||||
try:
|
|
||||||
urn = self.recipe_model.custom_recipe_collection[r].get('id')
|
|
||||||
urns.append(urn)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.beginResetModel()
|
|
||||||
self.recipe_model.remove_custom_recipes(urns)
|
|
||||||
self.endResetModel()
|
|
||||||
|
|
||||||
class UserProfiles(ResizableDialog, Ui_Dialog):
|
|
||||||
|
|
||||||
def __init__(self, parent, recipe_model):
|
|
||||||
ResizableDialog.__init__(self, parent)
|
|
||||||
|
|
||||||
self._model = self.model = CustomRecipeModel(recipe_model)
|
|
||||||
self.available_profiles.setModel(self._model)
|
|
||||||
self.available_profiles.currentChanged = self.current_changed
|
|
||||||
f = QFont()
|
|
||||||
f.setStyleHint(f.Monospace)
|
|
||||||
self.source_code.setFont(f)
|
|
||||||
|
|
||||||
self.remove_feed_button.clicked[(bool)].connect(self.added_feeds.remove_selected_items)
|
|
||||||
self.remove_profile_button.clicked[(bool)].connect(self.remove_selected_items)
|
|
||||||
self.add_feed_button.clicked[(bool)].connect(self.add_feed)
|
|
||||||
self.load_button.clicked.connect(self.load)
|
|
||||||
self.opml_button.clicked.connect(self.opml_import)
|
|
||||||
self.builtin_recipe_button.clicked.connect(self.add_builtin_recipe)
|
|
||||||
self.share_button.clicked.connect(self.share)
|
|
||||||
self.show_recipe_files_button.clicked.connect(self.show_recipe_files)
|
|
||||||
self.down_button.clicked.connect(self.down)
|
|
||||||
self.up_button.clicked.connect(self.up)
|
|
||||||
self.add_profile_button.clicked[(bool)].connect(self.add_profile)
|
|
||||||
self.feed_url.returnPressed[()].connect(self.add_feed)
|
|
||||||
self.feed_title.returnPressed[()].connect(self.add_feed)
|
|
||||||
self.toggle_mode_button.clicked[(bool)].connect(self.toggle_mode)
|
|
||||||
self.clear()
|
|
||||||
|
|
||||||
def show_recipe_files(self, *args):
|
|
||||||
bdir = os.path.dirname(custom_recipes.file_path)
|
|
||||||
if not os.path.exists(bdir):
|
|
||||||
return error_dialog(self, _('No recipes'),
|
|
||||||
_('No custom recipes created.'), show=True)
|
|
||||||
open_local_file(bdir)
|
|
||||||
|
|
||||||
def break_cycles(self):
|
|
||||||
self.recipe_model = self._model.recipe_model = None
|
|
||||||
self.available_profiles = None
|
|
||||||
self.model = self._model = None
|
|
||||||
|
|
||||||
def remove_selected_items(self):
|
|
||||||
indices = self.available_profiles.selectionModel().selectedRows()
|
|
||||||
self._model.remove([i.row() for i in indices])
|
|
||||||
self.clear()
|
|
||||||
|
|
||||||
def up(self):
|
|
||||||
row = self.added_feeds.currentRow()
|
|
||||||
item = self.added_feeds.takeItem(row)
|
|
||||||
if item is not None:
|
|
||||||
self.added_feeds.insertItem(max(row-1, 0), item)
|
|
||||||
self.added_feeds.setCurrentItem(item)
|
|
||||||
|
|
||||||
def down(self):
|
|
||||||
row = self.added_feeds.currentRow()
|
|
||||||
item = self.added_feeds.takeItem(row)
|
|
||||||
if item is not None:
|
|
||||||
self.added_feeds.insertItem(row+1, item)
|
|
||||||
self.added_feeds.setCurrentItem(item)
|
|
||||||
|
|
||||||
def share(self):
|
|
||||||
index = self.available_profiles.currentIndex()
|
|
||||||
title, src = self._model.title(index), self._model.script(index)
|
|
||||||
if not title or not src:
|
|
||||||
error_dialog(self, _('No recipe selected'), _('No recipe selected')).exec_()
|
|
||||||
return
|
|
||||||
pt = PersistentTemporaryFile(suffix='.recipe')
|
|
||||||
pt.write(src.encode('utf-8'))
|
|
||||||
pt.close()
|
|
||||||
body = _('The attached file: %(fname)s is a '
|
|
||||||
'recipe to download %(title)s.')%dict(
|
|
||||||
fname=os.path.basename(pt.name), title=title)
|
|
||||||
subject = _('Recipe for ')+title
|
|
||||||
url = QUrl('mailto:')
|
|
||||||
url.addQueryItem('subject', subject)
|
|
||||||
url.addQueryItem('body', body)
|
|
||||||
url.addQueryItem('attachment', pt.name)
|
|
||||||
open_url(url)
|
|
||||||
|
|
||||||
def current_changed(self, current, previous):
|
|
||||||
if not current.isValid():
|
|
||||||
return
|
|
||||||
src = self._model.script(current)
|
|
||||||
if src is None:
|
|
||||||
return
|
|
||||||
if 'class BasicUserRecipe' in src:
|
|
||||||
recipe = compile_recipe(src)
|
|
||||||
self.populate_options(recipe)
|
|
||||||
self.stacks.setCurrentIndex(0)
|
|
||||||
self.toggle_mode_button.setText(_('Switch to Advanced mode'))
|
|
||||||
self.source_code.setPlainText('')
|
|
||||||
else:
|
|
||||||
self.source_code.setPlainText(src)
|
|
||||||
self.highlighter = PythonHighlighter(self.source_code.document())
|
|
||||||
self.stacks.setCurrentIndex(1)
|
|
||||||
self.toggle_mode_button.setText(_('Switch to Basic mode'))
|
|
||||||
|
|
||||||
def toggle_mode(self, *args):
|
|
||||||
if self.stacks.currentIndex() == 1:
|
|
||||||
self.stacks.setCurrentIndex(0)
|
|
||||||
self.toggle_mode_button.setText(_('Switch to Advanced mode'))
|
|
||||||
else:
|
|
||||||
self.stacks.setCurrentIndex(1)
|
|
||||||
self.toggle_mode_button.setText(_('Switch to Basic mode'))
|
|
||||||
if not unicode(self.source_code.toPlainText()).strip():
|
|
||||||
src = self.options_to_profile()[0].replace('AutomaticNewsRecipe', 'BasicNewsRecipe')
|
|
||||||
self.source_code.setPlainText(src.replace('BasicUserRecipe', 'AdvancedUserRecipe'))
|
|
||||||
self.highlighter = PythonHighlighter(self.source_code.document())
|
|
||||||
|
|
||||||
def add_feed(self, *args):
|
|
||||||
title = unicode(self.feed_title.text()).strip()
|
|
||||||
if not title:
|
|
||||||
error_dialog(self, _('Feed must have a title'),
|
|
||||||
_('The feed must have a title')).exec_()
|
|
||||||
return
|
|
||||||
url = unicode(self.feed_url.text()).strip()
|
|
||||||
if not url:
|
|
||||||
error_dialog(self, _('Feed must have a URL'),
|
|
||||||
_('The feed %s must have a URL')%title).exec_()
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self.added_feeds.add_item(title+' - '+url, (title, url))
|
|
||||||
except ValueError:
|
|
||||||
error_dialog(self, _('Already exists'),
|
|
||||||
_('This feed has already been added to the recipe')).exec_()
|
|
||||||
return
|
|
||||||
self.feed_title.setText('')
|
|
||||||
self.feed_url.setText('')
|
|
||||||
|
|
||||||
def options_to_profile(self, **kw):
|
|
||||||
classname = 'BasicUserRecipe'+str(int(time.time()))
|
|
||||||
title = kw.get('title', self.profile_title.text())
|
|
||||||
title = unicode(title).strip()
|
|
||||||
if not title:
|
|
||||||
title = classname
|
|
||||||
self.profile_title.setText(title)
|
|
||||||
oldest_article = kw.get('oldest_article', self.oldest_article.value())
|
|
||||||
max_articles = kw.get('max_articles', self.max_articles.value())
|
|
||||||
feeds = kw.get('feeds',
|
|
||||||
[i.user_data for i in self.added_feeds.items()])
|
|
||||||
|
|
||||||
src = '''\
|
|
||||||
class %(classname)s(%(base_class)s):
|
|
||||||
title = %(title)s
|
|
||||||
oldest_article = %(oldest_article)d
|
|
||||||
max_articles_per_feed = %(max_articles)d
|
|
||||||
auto_cleanup = True
|
|
||||||
|
|
||||||
feeds = %(feeds)s
|
|
||||||
'''%dict(classname=classname, title=repr(title),
|
|
||||||
feeds=repr(feeds), oldest_article=oldest_article,
|
|
||||||
max_articles=max_articles,
|
|
||||||
base_class='AutomaticNewsRecipe')
|
|
||||||
return src, title
|
|
||||||
|
|
||||||
def populate_source_code(self):
|
|
||||||
src = self.options_to_profile().replace('BasicUserRecipe', 'AdvancedUserRecipe')
|
|
||||||
self.source_code.setPlainText(src)
|
|
||||||
self.highlighter = PythonHighlighter(self.source_code.document())
|
|
||||||
|
|
||||||
def add_profile(self, clicked):
|
|
||||||
if self.stacks.currentIndex() == 0:
|
|
||||||
src, title = self.options_to_profile()
|
|
||||||
|
|
||||||
try:
|
|
||||||
compile_recipe(src)
|
|
||||||
except Exception as err:
|
|
||||||
error_dialog(self, _('Invalid input'),
|
|
||||||
_('<p>Could not create recipe. Error:<br>%s')%str(err)).exec_()
|
|
||||||
return
|
|
||||||
profile = src
|
|
||||||
else:
|
|
||||||
src = unicode(self.source_code.toPlainText())
|
|
||||||
try:
|
|
||||||
title = compile_recipe(src).title
|
|
||||||
except Exception as err:
|
|
||||||
error_dialog(self, _('Invalid input'),
|
|
||||||
_('<p>Could not create recipe. Error:<br>%s')%str(err)).exec_()
|
|
||||||
return
|
|
||||||
profile = src.replace('BasicUserRecipe', 'AdvancedUserRecipe')
|
|
||||||
if self._model.has_title(title):
|
|
||||||
if question_dialog(self, _('Replace recipe?'),
|
|
||||||
_('A custom recipe named %s already exists. Do you want to '
|
|
||||||
'replace it?')%title):
|
|
||||||
self._model.replace_by_title(title, profile)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.model.add(title, profile)
|
|
||||||
self.clear()
|
|
||||||
|
|
||||||
def add_builtin_recipe(self):
|
|
||||||
from calibre.web.feeds.recipes.collection import \
|
|
||||||
get_builtin_recipe_collection, get_builtin_recipe_by_id
|
|
||||||
from PyQt5.Qt import QDialog, QVBoxLayout, QListWidgetItem, \
|
|
||||||
QListWidget, QDialogButtonBox, QSize
|
|
||||||
|
|
||||||
d = QDialog(self)
|
|
||||||
d.l = QVBoxLayout()
|
|
||||||
d.setLayout(d.l)
|
|
||||||
d.list = QListWidget(d)
|
|
||||||
d.list.doubleClicked.connect(lambda x: d.accept())
|
|
||||||
d.l.addWidget(d.list)
|
|
||||||
d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel,
|
|
||||||
Qt.Horizontal, d)
|
|
||||||
d.bb.accepted.connect(d.accept)
|
|
||||||
d.bb.rejected.connect(d.reject)
|
|
||||||
d.l.addWidget(d.bb)
|
|
||||||
d.setWindowTitle(_('Choose builtin recipe'))
|
|
||||||
items = []
|
|
||||||
for r in get_builtin_recipe_collection():
|
|
||||||
id_ = r.get('id', '')
|
|
||||||
title = r.get('title', '')
|
|
||||||
lang = r.get('language', '')
|
|
||||||
if id_ and title:
|
|
||||||
items.append((title + ' [%s]'%lang, id_))
|
|
||||||
|
|
||||||
items.sort(key=lambda x:sort_key(x[0]))
|
|
||||||
for title, id_ in items:
|
|
||||||
item = QListWidgetItem(title)
|
|
||||||
item.setData(Qt.UserRole, id_)
|
|
||||||
d.list.addItem(item)
|
|
||||||
|
|
||||||
d.resize(QSize(450, 400))
|
|
||||||
ret = d.exec_()
|
|
||||||
d.list.doubleClicked.disconnect()
|
|
||||||
if ret != d.Accepted:
|
|
||||||
return
|
|
||||||
|
|
||||||
items = list(d.list.selectedItems())
|
|
||||||
if not items:
|
|
||||||
return
|
|
||||||
item = items[-1]
|
|
||||||
id_ = unicode(item.data(Qt.UserRole) or '')
|
|
||||||
title = unicode(item.data(Qt.DisplayRole) or '').rpartition(' [')[0]
|
|
||||||
profile = get_builtin_recipe_by_id(id_, download_recipe=True)
|
|
||||||
if profile is None:
|
|
||||||
raise Exception('Something weird happened')
|
|
||||||
|
|
||||||
if self._model.has_title(title):
|
|
||||||
if question_dialog(self, _('Replace recipe?'),
|
|
||||||
_('A custom recipe named %s already exists. Do you want to '
|
|
||||||
'replace it?')%title):
|
|
||||||
self._model.replace_by_title(title, profile)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.model.add(title, profile)
|
|
||||||
|
|
||||||
self.clear()
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
files = choose_files(self, 'recipe loader dialog',
|
|
||||||
_('Choose a recipe file'),
|
|
||||||
filters=[(_('Recipes'), ['.py', '.recipe'])],
|
|
||||||
all_files=False, select_only_single_file=True)
|
|
||||||
if files:
|
|
||||||
file = files[0]
|
|
||||||
try:
|
|
||||||
profile = open(file, 'rb').read().decode('utf-8')
|
|
||||||
title = compile_recipe(profile).title
|
|
||||||
except Exception as err:
|
|
||||||
error_dialog(self, _('Invalid input'),
|
|
||||||
_('<p>Could not create recipe. Error:<br>%s')%str(err)).exec_()
|
|
||||||
return
|
|
||||||
if self._model.has_title(title):
|
|
||||||
if question_dialog(self, _('Replace recipe?'),
|
|
||||||
_('A custom recipe named %s already exists. Do you want to '
|
|
||||||
'replace it?')%title):
|
|
||||||
self._model.replace_by_title(title, profile)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.model.add(title, profile)
|
|
||||||
self.clear()
|
|
||||||
|
|
||||||
def opml_import(self):
|
|
||||||
from calibre.gui2.dialogs.opml import ImportOPML
|
|
||||||
d = ImportOPML(parent=self)
|
|
||||||
if d.exec_() != d.Accepted:
|
|
||||||
return
|
|
||||||
oldest_article, max_articles_per_feed, replace_existing = d.oldest_article, d.articles_per_feed, d.replace_existing
|
|
||||||
failed_recipes, replace_recipes, add_recipes = {}, {}, {}
|
|
||||||
|
|
||||||
for group in d.recipes:
|
|
||||||
title = base_title = group.title or _('Unknown')
|
|
||||||
if not replace_existing:
|
|
||||||
c = 0
|
|
||||||
while self._model.has_title(title):
|
|
||||||
c += 1
|
|
||||||
title = u'%s %d' % (base_title, c)
|
|
||||||
src, title = self.options_to_profile(**{
|
|
||||||
'title':title,
|
|
||||||
'feeds':group.feeds,
|
|
||||||
'oldest_article':oldest_article,
|
|
||||||
'max_articles':max_articles_per_feed,
|
|
||||||
})
|
|
||||||
try:
|
|
||||||
compile_recipe(src)
|
|
||||||
except Exception:
|
|
||||||
import traceback
|
|
||||||
failed_recipes[title] = traceback.format_exc()
|
|
||||||
continue
|
|
||||||
|
|
||||||
if replace_existing and self._model.has_title(title):
|
|
||||||
replace_recipes[title] = src
|
|
||||||
else:
|
|
||||||
add_recipes[title] = src
|
|
||||||
|
|
||||||
if add_recipes:
|
|
||||||
self.model.add_many(add_recipes)
|
|
||||||
if replace_recipes:
|
|
||||||
self.model.replace_many_by_title(replace_recipes)
|
|
||||||
if failed_recipes:
|
|
||||||
det_msg = '\n'.join('%s\n%s\n' % (title, tb) for title, tb in failed_recipes.iteritems())
|
|
||||||
error_dialog(self, _('Failed to create recipes'), _(
|
|
||||||
'Failed to create some recipes, click "Show details" for details'), show=True,
|
|
||||||
det_msg=det_msg)
|
|
||||||
self.clear()
|
|
||||||
|
|
||||||
def populate_options(self, profile):
|
|
||||||
self.oldest_article.setValue(profile.oldest_article)
|
|
||||||
self.max_articles.setValue(profile.max_articles_per_feed)
|
|
||||||
self.profile_title.setText(profile.title)
|
|
||||||
self.added_feeds.clear()
|
|
||||||
feeds = [] if profile.feeds is None else profile.feeds
|
|
||||||
for title, url in feeds:
|
|
||||||
self.added_feeds.add_item(title+' - '+url, (title, url))
|
|
||||||
self.feed_title.setText('')
|
|
||||||
self.feed_url.setText('')
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.populate_options(AutomaticNewsRecipe)
|
|
||||||
self.source_code.setText('')
|
|
||||||
|
|
||||||
def reject(self):
|
|
||||||
if question_dialog(self, _('Are you sure?'),
|
|
||||||
_('You will lose any unsaved changes. To save your'
|
|
||||||
' changes, click the Add/Update recipe button.'
|
|
||||||
' Continue?'), show_copy_button=False):
|
|
||||||
ResizableDialog.reject(self)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from PyQt5.Qt import QApplication
|
|
||||||
app = QApplication([])
|
|
||||||
from calibre.web.feeds.recipes.model import RecipeModel
|
|
||||||
d=UserProfiles(None, RecipeModel())
|
|
||||||
d.exec_()
|
|
||||||
del app
|
|
||||||
|
|
@ -1,515 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>Dialog</class>
|
|
||||||
<widget class="QDialog" name="Dialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>738</width>
|
|
||||||
<height>640</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Add custom news source</string>
|
|
||||||
</property>
|
|
||||||
<property name="windowIcon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/user_profile.png</normaloff>:/images/user_profile.png</iconset>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
|
||||||
<item>
|
|
||||||
<widget class="QScrollArea" name="scrollArea">
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::NoFrame</enum>
|
|
||||||
</property>
|
|
||||||
<property name="lineWidth">
|
|
||||||
<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>726</width>
|
|
||||||
<height>595</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
|
||||||
<property name="margin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QWidget" name="central_widget" native="true">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>580</width>
|
|
||||||
<height>550</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<property name="margin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
|
||||||
<horstretch>1</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="title">
|
|
||||||
<string>Available user recipes</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
||||||
<item>
|
|
||||||
<widget class="QListView" name="available_profiles"/>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="add_profile_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>Add/Update &recipe</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/plus.png</normaloff>:/images/plus.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="remove_profile_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Remove recipe</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/list_remove.png</normaloff>:/images/list_remove.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="share_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Share recipe</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/forward.png</normaloff>:/images/forward.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="show_recipe_files_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>S&how recipe files</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="builtin_recipe_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>Customize &builtin recipe</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/news.png</normaloff>:/images/news.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="load_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Load recipe from file</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="opml_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Import a collection of RSS feeds in OPML format
|
|
||||||
Many RSS readers can export their subscribed RSS feeds
|
|
||||||
in OPML format</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Import &OPML</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/mimetypes/opml.png</normaloff>:/images/mimetypes/opml.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QFrame" name="frame">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
|
||||||
<horstretch>10</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::StyledPanel</enum>
|
|
||||||
</property>
|
|
||||||
<property name="frameShadow">
|
|
||||||
<enum>QFrame::Raised</enum>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="toggle_mode_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>Switch to Advanced mode</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QStackedWidget" name="stacks">
|
|
||||||
<property name="currentIndex">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<widget class="QWidget" name="page">
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string><html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
|
||||||
p, li { white-space: pre-wrap; }
|
|
||||||
</style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
|
||||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Create a basic news recipe, by adding RSS feeds to it. <br />For most feeds, you will have to use the "Advanced mode" to further customize the fetch process.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::RichText</enum>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QGridLayout">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Recipe &title:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>profile_title</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1" colspan="2">
|
|
||||||
<widget class="EnLineEdit" name="profile_title">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="label_6">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Oldest article:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>oldest_article</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="2">
|
|
||||||
<widget class="QSpinBox" name="oldest_article">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>The oldest article to download</string>
|
|
||||||
</property>
|
|
||||||
<property name="suffix">
|
|
||||||
<string> days</string>
|
|
||||||
</property>
|
|
||||||
<property name="minimum">
|
|
||||||
<number>1</number>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>36500</number>
|
|
||||||
</property>
|
|
||||||
<property name="value">
|
|
||||||
<number>7</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="label_7">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Max. number of articles per feed:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>max_articles</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="2">
|
|
||||||
<widget class="QSpinBox" name="max_articles">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Maximum number of articles to download per feed.</string>
|
|
||||||
</property>
|
|
||||||
<property name="minimum">
|
|
||||||
<number>5</number>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>100</number>
|
|
||||||
</property>
|
|
||||||
<property name="value">
|
|
||||||
<number>10</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
|
||||||
<horstretch>100</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="title">
|
|
||||||
<string>Feeds in recipe</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QHBoxLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="BasicList" name="added_feeds">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
|
||||||
<horstretch>100</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="selectionMode">
|
|
||||||
<enum>QAbstractItemView::MultiSelection</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="up_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="remove_feed_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Remove feed from recipe</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/list_remove.png</normaloff>:/images/list_remove.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="down_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox_3">
|
|
||||||
<property name="title">
|
|
||||||
<string>Add feed to recipe</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label_4">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Feed title:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>feed_title</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="EnLineEdit" name="feed_title"/>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_5">
|
|
||||||
<property name="text">
|
|
||||||
<string>Feed &URL:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>feed_url</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLineEdit" name="feed_url"/>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0" colspan="2">
|
|
||||||
<widget class="QPushButton" name="add_feed_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Add feed to recipe</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Add feed</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/plus.png</normaloff>:/images/plus.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="page_2">
|
|
||||||
<layout class="QVBoxLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_8">
|
|
||||||
<property name="text">
|
|
||||||
<string>For help with writing advanced news recipes, please visit <a href="http://manual.calibre-ebook.com/news.html">User Recipes</a></string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="openExternalLinks">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox_4">
|
|
||||||
<property name="title">
|
|
||||||
<string>Recipe source code (python)</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QTextEdit" name="source_code">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
|
||||||
<horstretch>100</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="lineWrapMode">
|
|
||||||
<enum>QTextEdit::NoWrap</enum>
|
|
||||||
</property>
|
|
||||||
<property name="acceptRichText">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Close</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<customwidgets>
|
|
||||||
<customwidget>
|
|
||||||
<class>BasicList</class>
|
|
||||||
<extends>QListWidget</extends>
|
|
||||||
<header>calibre/gui2/widgets.h</header>
|
|
||||||
</customwidget>
|
|
||||||
<customwidget>
|
|
||||||
<class>EnLineEdit</class>
|
|
||||||
<extends>QLineEdit</extends>
|
|
||||||
<header>calibre/gui2/widgets.h</header>
|
|
||||||
</customwidget>
|
|
||||||
</customwidgets>
|
|
||||||
<resources>
|
|
||||||
<include location="../../../../resources/images.qrc"/>
|
|
||||||
</resources>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>accepted()</signal>
|
|
||||||
<receiver>Dialog</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>446</x>
|
|
||||||
<y>649</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>0</x>
|
|
||||||
<y>632</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>rejected()</signal>
|
|
||||||
<receiver>Dialog</receiver>
|
|
||||||
<slot>reject()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>175</x>
|
|
||||||
<y>643</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>176</x>
|
|
||||||
<y>636</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
Loading…
x
Reference in New Issue
Block a user