Пример #1
0
class CreateVirtualLibrary(QDialog):  # {{{
    def __init__(self, gui, existing_names, editing=None):
        QDialog.__init__(self, gui)

        self.gui = gui
        self.existing_names = existing_names

        if editing:
            self.setWindowTitle(_('Edit virtual library'))
        else:
            self.setWindowTitle(_('Create virtual library'))
        self.setWindowIcon(QIcon(I('lt.png')))

        gl = QGridLayout()
        self.setLayout(gl)
        self.la1 = la1 = QLabel(_('Virtual library &name:'))
        gl.addWidget(la1, 0, 0)
        self.vl_name = QComboBox()
        self.vl_name.setEditable(True)
        self.vl_name.lineEdit().setMaxLength(MAX_VIRTUAL_LIBRARY_NAME_LENGTH)
        la1.setBuddy(self.vl_name)
        gl.addWidget(self.vl_name, 0, 1)
        self.editing = editing

        self.saved_searches_label = QLabel('')
        self.saved_searches_label.setTextInteractionFlags(
            Qt.TextSelectableByMouse)
        gl.addWidget(self.saved_searches_label, 2, 0, 1, 2)

        self.la2 = la2 = QLabel(_('&Search expression:'))
        gl.addWidget(la2, 1, 0)
        self.vl_text = QLineEdit()
        self.vl_text.textChanged.connect(self.search_text_changed)
        la2.setBuddy(self.vl_text)
        gl.addWidget(self.vl_text, 1, 1)
        self.vl_text.setText(_build_full_search_string(self.gui))

        self.sl = sl = QLabel(
            '<p>' + _('Create a virtual library based on: ') +
            ('<a href="author.{0}">{0}</a>, '
             '<a href="tag.{1}">{1}</a>, '
             '<a href="publisher.{2}">{2}</a>, '
             '<a href="series.{3}">{3}</a>, '
             '<a href="search.{4}">{4}</a>.').format(_('Authors'), _(
                 'Tags'), _('Publishers'), _('Series'), _('Saved Searches')))
        sl.setWordWrap(True)
        sl.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
        sl.linkActivated.connect(self.link_activated)
        gl.addWidget(sl, 3, 0, 1, 2)
        gl.setRowStretch(3, 10)

        self.hl = hl = QLabel(
            _('''
            <h2>Virtual Libraries</h2>

            <p>Using <i>virtual libraries</i> you can restrict calibre to only show
            you books that match a search. When a virtual library is in effect, calibre
            behaves as though the library contains only the matched books. The Tag Browser
            display only the tags/authors/series/etc. that belong to the matched books and any searches
            you do will only search within the books in the virtual library. This
            is a good way to partition your large library into smaller and easier to work with subsets.</p>

            <p>For example you can use a Virtual Library to only show you books with the Tag <i>"Unread"</i>
            or only books by <i>"My Favorite Author"</i> or only books in a particular series.</p>

            <p>More information and examples are available in the
            <a href="http://manual.calibre-ebook.com/virtual_libraries.html">User Manual</a>.</p>
            '''))
        hl.setWordWrap(True)
        hl.setOpenExternalLinks(True)
        hl.setFrameStyle(hl.StyledPanel)
        gl.addWidget(hl, 0, 3, 4, 1)

        bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        gl.addWidget(bb, 4, 0, 1, 0)

        if editing:
            db = self.gui.current_db
            virt_libs = db.prefs.get('virtual_libraries', {})
            for dex, vl in enumerate(sorted(virt_libs.keys(), key=sort_key)):
                self.vl_name.addItem(vl, virt_libs.get(vl, ''))
                if vl == editing:
                    self.vl_name.setCurrentIndex(dex)
                    self.original_index = dex
            self.original_search = virt_libs.get(editing, '')
            self.vl_text.setText(self.original_search)
            self.new_name = editing
            self.vl_name.currentIndexChanged[int].connect(
                self.name_index_changed)
            self.vl_name.lineEdit().textEdited.connect(self.name_text_edited)

        self.resize(self.sizeHint() + QSize(150, 25))

    def search_text_changed(self, txt):
        searches = [_('Saved searches recognized in the expression:')]
        txt = unicode(txt)
        while txt:
            p = txt.partition('search:')
            if p[1]:  # found 'search:'
                possible_search = p[2]
                if possible_search:  # something follows the 'search:'
                    if possible_search[0] == '"':  # strip any quotes
                        possible_search = possible_search[1:].partition('"')
                    else:  # find end of the search name. Is EOL, space, rparen
                        sp = possible_search.find(' ')
                        pp = possible_search.find(')')
                        if pp < 0 or (sp > 0 and sp <= pp):
                            # space in string before rparen, or neither found
                            possible_search = possible_search.partition(' ')
                        else:
                            # rparen in string before space
                            possible_search = possible_search.partition(')')
                    txt = possible_search[2]  # grab remainder of the string
                    search_name = possible_search[0]
                    if search_name.startswith('='):
                        search_name = search_name[1:]
                    if search_name in saved_searches().names():
                        searches.append(search_name + '=' +
                                        saved_searches().lookup(search_name))
                else:
                    txt = ''
            else:
                txt = ''
        if len(searches) > 1:
            self.saved_searches_label.setText('\n'.join(searches))
        else:
            self.saved_searches_label.setText('')

    def name_text_edited(self, new_name):
        self.new_name = unicode(new_name)

    def name_index_changed(self, dex):
        if self.editing and (self.vl_text.text() != self.original_search
                             or self.new_name != self.editing):
            if not question_dialog(
                    self.gui,
                    _('Search text changed'),
                    _('The virtual library name or the search text has changed. '
                      'Do you want to discard these changes?'),
                    default_yes=False):
                self.vl_name.blockSignals(True)
                self.vl_name.setCurrentIndex(self.original_index)
                self.vl_name.lineEdit().setText(self.new_name)
                self.vl_name.blockSignals(False)
                return
        self.new_name = self.editing = self.vl_name.currentText()
        self.original_index = dex
        self.original_search = unicode(self.vl_name.itemData(dex).toString())
        self.vl_text.setText(self.original_search)

    def link_activated(self, url):
        db = self.gui.current_db
        f, txt = unicode(url).partition('.')[0::2]
        if f == 'search':
            names = saved_searches().names()
        else:
            names = getattr(db, 'all_%s_names' % f)()
        d = SelectNames(names, txt, parent=self)
        if d.exec_() == d.Accepted:
            prefix = f + 's' if f in {'tag', 'author'} else f
            if f == 'search':
                search = [
                    '(%s)' % (saved_searches().lookup(x)) for x in d.names
                ]
            else:
                search = [
                    '%s:"=%s"' % (prefix, x.replace('"', '\\"'))
                    for x in d.names
                ]
            if search:
                if not self.editing:
                    self.vl_name.lineEdit().setText(d.names.next())
                    self.vl_name.lineEdit().setCursorPosition(0)
                self.vl_text.setText(d.match_type.join(search))
                self.vl_text.setCursorPosition(0)

    def accept(self):
        n = unicode(self.vl_name.currentText()).strip()
        if not n:
            error_dialog(
                self.gui,
                _('No name'),
                _('You must provide a name for the new virtual library'),
                show=True)
            return

        if n.startswith('*'):
            error_dialog(self.gui,
                         _('Invalid name'),
                         _('A virtual library name cannot begin with "*"'),
                         show=True)
            return

        if n in self.existing_names and n != self.editing:
            if not question_dialog(
                    self.gui,
                    _('Name already in use'),
                    _('That name is already in use. Do you want to replace it '
                      'with the new search?'),
                    default_yes=False):
                return

        v = unicode(self.vl_text.text()).strip()
        if not v:
            error_dialog(
                self.gui,
                _('No search string'),
                _('You must provide a search to define the new virtual library'
                  ),
                show=True)
            return

        try:
            db = self.gui.library_view.model().db
            recs = db.data.search_getting_ids('', v, use_virtual_library=False)
        except ParseException as e:
            error_dialog(self.gui,
                         _('Invalid search'),
                         _('The search in the search box is not valid'),
                         det_msg=e.msg,
                         show=True)
            return

        if not recs and not question_dialog(
                self.gui,
                _('Search found no books'),
                _('The search found no books, so the virtual library '
                  'will be empty. Do you really want to use that search?'),
                default_yes=False):
            return

        self.library_name = n
        self.library_search = v
        QDialog.accept(self)
class CreateVirtualLibrary(QDialog):  # {{{

    def __init__(self, gui, existing_names, editing=None):
        QDialog.__init__(self, gui)

        self.gui = gui
        self.existing_names = existing_names

        if editing:
            self.setWindowTitle(_('Edit virtual library'))
        else:
            self.setWindowTitle(_('Create virtual library'))
        self.setWindowIcon(QIcon(I('lt.png')))

        gl = QGridLayout()
        self.setLayout(gl)
        self.la1 = la1 = QLabel(_('Virtual library &name:'))
        gl.addWidget(la1, 0, 0)
        self.vl_name = QComboBox()
        self.vl_name.setEditable(True)
        self.vl_name.lineEdit().setMaxLength(MAX_VIRTUAL_LIBRARY_NAME_LENGTH)
        la1.setBuddy(self.vl_name)
        gl.addWidget(self.vl_name, 0, 1)
        self.editing = editing

        self.saved_searches_label = QLabel('')
        self.saved_searches_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        gl.addWidget(self.saved_searches_label, 2, 0, 1, 2)

        self.la2 = la2 = QLabel(_('&Search expression:'))
        gl.addWidget(la2, 1, 0)
        self.vl_text = QLineEdit()
        self.vl_text.textChanged.connect(self.search_text_changed)
        la2.setBuddy(self.vl_text)
        gl.addWidget(self.vl_text, 1, 1)
        self.vl_text.setText(_build_full_search_string(self.gui))

        self.sl = sl = QLabel('<p>'+_('Create a virtual library based on: ')+
            ('<a href="author.{0}">{0}</a>, '
            '<a href="tag.{1}">{1}</a>, '
            '<a href="publisher.{2}">{2}</a>, '
            '<a href="series.{3}">{3}</a>, '
            '<a href="search.{4}">{4}</a>.').format(_('Authors'), _('Tags'),
                                            _('Publishers'), _('Series'), _('Saved Searches')))
        sl.setWordWrap(True)
        sl.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
        sl.linkActivated.connect(self.link_activated)
        gl.addWidget(sl, 3, 0, 1, 2)
        gl.setRowStretch(3,10)

        self.hl = hl = QLabel(_('''
            <h2>Virtual Libraries</h2>

            <p>Using <i>virtual libraries</i> you can restrict calibre to only show
            you books that match a search. When a virtual library is in effect, calibre
            behaves as though the library contains only the matched books. The Tag Browser
            display only the tags/authors/series/etc. that belong to the matched books and any searches
            you do will only search within the books in the virtual library. This
            is a good way to partition your large library into smaller and easier to work with subsets.</p>

            <p>For example you can use a Virtual Library to only show you books with the Tag <i>"Unread"</i>
            or only books by <i>"My Favorite Author"</i> or only books in a particular series.</p>

            <p>More information and examples are available in the
            <a href="http://manual.calibre-ebook.com/virtual_libraries.html">User Manual</a>.</p>
            '''))
        hl.setWordWrap(True)
        hl.setOpenExternalLinks(True)
        hl.setFrameStyle(hl.StyledPanel)
        gl.addWidget(hl, 0, 3, 4, 1)

        bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        gl.addWidget(bb, 4, 0, 1, 0)

        if editing:
            db = self.gui.current_db
            virt_libs = db.prefs.get('virtual_libraries', {})
            for dex,vl in enumerate(sorted(virt_libs.keys(), key=sort_key)):
                self.vl_name.addItem(vl, virt_libs.get(vl, ''))
                if vl == editing:
                    self.vl_name.setCurrentIndex(dex)
                    self.original_index = dex
            self.original_search = virt_libs.get(editing, '')
            self.vl_text.setText(self.original_search)
            self.new_name = editing
            self.vl_name.currentIndexChanged[int].connect(self.name_index_changed)
            self.vl_name.lineEdit().textEdited.connect(self.name_text_edited)

        self.resize(self.sizeHint()+QSize(150, 25))

    def search_text_changed(self, txt):
        db = self.gui.current_db
        searches = [_('Saved searches recognized in the expression:')]
        txt = unicode(txt)
        while txt:
            p = txt.partition('search:')
            if p[1]:  # found 'search:'
                possible_search = p[2]
                if possible_search:  # something follows the 'search:'
                    if possible_search[0] == '"':  # strip any quotes
                        possible_search = possible_search[1:].partition('"')
                    else:  # find end of the search name. Is EOL, space, rparen
                        sp = possible_search.find(' ')
                        pp = possible_search.find(')')
                        if pp < 0 or (sp > 0 and sp <= pp):
                            # space in string before rparen, or neither found
                            possible_search = possible_search.partition(' ')
                        else:
                            # rparen in string before space
                            possible_search = possible_search.partition(')')
                    txt = possible_search[2]  # grab remainder of the string
                    search_name = possible_search[0]
                    if search_name.startswith('='):
                        search_name = search_name[1:]
                    if search_name in db.saved_search_names():
                        searches.append(search_name + '=' +
                                        db.saved_search_lookup(search_name))
                else:
                    txt = ''
            else:
                txt = ''
        if len(searches) > 1:
            self.saved_searches_label.setText('\n'.join(searches))
        else:
            self.saved_searches_label.setText('')

    def name_text_edited(self, new_name):
        self.new_name = unicode(new_name)

    def name_index_changed(self, dex):
        if self.editing and (self.vl_text.text() != self.original_search or
                             self.new_name != self.editing):
            if not question_dialog(self.gui, _('Search text changed'),
                         _('The virtual library name or the search text has changed. '
                           'Do you want to discard these changes?'),
                         default_yes=False):
                self.vl_name.blockSignals(True)
                self.vl_name.setCurrentIndex(self.original_index)
                self.vl_name.lineEdit().setText(self.new_name)
                self.vl_name.blockSignals(False)
                return
        self.new_name = self.editing = self.vl_name.currentText()
        self.original_index = dex
        self.original_search = unicode(self.vl_name.itemData(dex).toString())
        self.vl_text.setText(self.original_search)

    def link_activated(self, url):
        db = self.gui.current_db
        f, txt = unicode(url).partition('.')[0::2]
        if f == 'search':
            names = db.saved_search_names()
        else:
            names = getattr(db, 'all_%s_names'%f)()
        d = SelectNames(names, txt, parent=self)
        if d.exec_() == d.Accepted:
            prefix = f+'s' if f in {'tag', 'author'} else f
            if f == 'search':
                search = ['(%s)'%(db.saved_search_lookup(x)) for x in d.names]
            else:
                search = ['%s:"=%s"'%(prefix, x.replace('"', '\\"')) for x in d.names]
            if search:
                if not self.editing:
                    self.vl_name.lineEdit().setText(d.names.next())
                    self.vl_name.lineEdit().setCursorPosition(0)
                self.vl_text.setText(d.match_type.join(search))
                self.vl_text.setCursorPosition(0)

    def accept(self):
        n = unicode(self.vl_name.currentText()).strip()
        if not n:
            error_dialog(self.gui, _('No name'),
                         _('You must provide a name for the new virtual library'),
                         show=True)
            return

        if n.startswith('*'):
            error_dialog(self.gui, _('Invalid name'),
                         _('A virtual library name cannot begin with "*"'),
                         show=True)
            return

        if n in self.existing_names and n != self.editing:
            if not question_dialog(self.gui, _('Name already in use'),
                         _('That name is already in use. Do you want to replace it '
                           'with the new search?'),
                            default_yes=False):
                return

        v = unicode(self.vl_text.text()).strip()
        if not v:
            error_dialog(self.gui, _('No search string'),
                         _('You must provide a search to define the new virtual library'),
                         show=True)
            return

        try:
            db = self.gui.library_view.model().db
            recs = db.data.search_getting_ids('', v, use_virtual_library=False)
        except ParseException as e:
            error_dialog(self.gui, _('Invalid search'),
                         _('The search in the search box is not valid'),
                         det_msg=e.msg, show=True)
            return

        if not recs and not question_dialog(
                self.gui, _('Search found no books'),
                _('The search found no books, so the virtual library '
                'will be empty. Do you really want to use that search?'),
                default_yes=False):
                return

        self.library_name = n
        self.library_search = v
        QDialog.accept(self)
Пример #3
0
 def _updateModel(self, what=SkyModel.UpdateAll, origin=None):
     if origin is self or not what & (SkyModel.UpdateTags
                                      | SkyModel.UpdateGroupStyle):
         return
     model = self.model
     self._setting_model = True
     # to ignore cellChanged() signals (in valueChanged())
     # _item_cb is a dict (with row,col keys) containing the widgets (CheckBoxes ComboBoxes) per each cell
     self._item_cb = {}
     # lists of "list" and "plot" checkboxes per each grouping (excepting the default grouping); each entry is an (row,col,item) tuple.
     # used as argument to self._showControls()
     self._list_controls = []
     self._plot_controls = []
     # list of selection callbacks (to which signals are connected)
     self._callbacks = []
     # set requisite number of rows,and start filling
     self.table.setRowCount(len(model.groupings))
     for irow, group in enumerate(model.groupings):
         self.table.setItem(irow, 0, QTableWidgetItem(group.name))
         if group is model.selgroup:
             self._irow_selgroup = irow
         # total # source in group: skip for "current"
         if group is not model.curgroup:
             self.table.setItem(irow, 1, QTableWidgetItem(str(group.total)))
         # selection controls: skip for current and selection
         if group not in (model.curgroup, model.selgroup):
             btns = QWidget()
             lo = QHBoxLayout(btns)
             lo.setContentsMargins(0, 0, 0, 0)
             lo.setSpacing(0)
             # make selector buttons (depending on which group we're in)
             if group is model.defgroup:
                 Buttons = (("+", lambda src, grp=group: True,
                             "select all sources"),
                            ("-", lambda src, grp=group: False,
                             "unselect all sources"))
             else:
                 Buttons = (
                     ("=", lambda src, grp=group: grp.func(src),
                      "select only this grouping"),
                     ("+",
                      lambda src, grp=group: src.selected or grp.func(src),
                      "add grouping to selection"),
                     ("-", lambda src, grp=group: src.selected and not grp.
                      func(src), "remove grouping from selection"),
                     ("&&",
                      lambda src, grp=group: src.selected and grp.func(src),
                      "intersect selection with grouping"))
             lo.addStretch(1)
             for label, predicate, tooltip in Buttons:
                 btn = QToolButton(btns)
                 btn.setText(label)
                 btn.setMinimumWidth(24)
                 btn.setMaximumWidth(24)
                 btn.setToolTip(tooltip)
                 lo.addWidget(btn)
                 # add callback
                 QObject.connect(
                     btn, SIGNAL("clicked()"),
                     self._currier.curry(self.selectSources, predicate))
             lo.addStretch(1)
             self.table.setCellWidget(irow, 2, btns)
         # "list" checkbox (not for current and selected groupings: these are always listed)
         if group not in (model.curgroup, model.selgroup):
             item = self._makeCheckItem("", group, "show_list")
             self.table.setItem(irow, self.ColList, item)
             item.setToolTip(
                 """<P>If checked, sources in this grouping will be listed in the source table. If un-checked, sources will be
         excluded from the table. If partially checked, then the default list/no list setting of "all sources" will be in effect.
         </P>""")
         # "plot" checkbox (not for the current grouping, since that's always plotted)
         if group is not model.curgroup:
             item = self._makeCheckItem("", group, "show_plot")
             self.table.setItem(irow, self.ColPlot, item)
             item.setToolTip(
                 """<P>If checked, sources in this grouping will be included in the plot. If un-checked, sources will be
         excluded from the plot. If partially checked, then the default plot/no plot setting of "all sources" will be in effect.
         </P>""")
         # custom style control
         # for default, current and selected, this is just a text label
         if group is model.defgroup:
             item = QTableWidgetItem("default:")
             item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
             item.setToolTip(
                 """<P>This is the default plot style used for all sources for which a custom grouping style is not selected.</P>"""
             )
             self.table.setItem(irow, self.ColApply, item)
         elif group is model.curgroup:
             item = QTableWidgetItem("")
             item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
             item.setToolTip(
                 """<P>This is the plot style used for the highlighted source, if any.</P>"""
             )
             self.table.setItem(irow, self.ColApply, item)
         elif group is model.selgroup:
             item = QTableWidgetItem("")
             item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
             item.setToolTip(
                 """<P>This is the plot style used for the currently selected sources.</P>"""
             )
             self.table.setItem(irow, self.ColApply, item)
         # for the rest, a combobox with custom priorities
         else:
             cb = QComboBox()
             cb.addItems(["default"] +
                         ["custom %d" % p for p in range(1, 10)])
             index = max(0, min(group.style.apply, 9))
             #        dprint(0,group.name,"apply",index)
             cb.setCurrentIndex(index)
             QObject.connect(
                 cb, SIGNAL("activated(int)"),
                 self._currier.xcurry(self._valueChanged,
                                      (irow, self.ColApply)))
             self.table.setCellWidget(irow, self.ColApply, cb)
             cb.setToolTip(
                 """<P>This controls whether sources within this group are plotted with a customized
         plot style. Customized styles have numeric priority; if a source belongs to multiple groups, then
         the style with the lowest priority takes precedence.<P>""")
         # attribute comboboxes
         for icol, attr in self.AttrByCol.items():
             # get list of options for this style attribute. If dealing with first grouping (i==0), which is
             # the "all sources" grouping, then remove the "default" option (which is always first in the list)
             options = PlotStyles.StyleAttributeOptions[attr]
             if irow == 0:
                 options = options[1:]
             # make combobox
             cb = QComboBox()
             cb.addItems(list(map(str, options)))
             # the "label" option is also editable
             if attr == "label":
                 cb.setEditable(True)
             try:
                 index = options.index(getattr(group.style, attr))
                 cb.setCurrentIndex(index)
             except ValueError:
                 cb.setEditText(str(getattr(group.style, attr)))
             slot = self._currier.xcurry(self._valueChanged, (irow, icol))
             QObject.connect(cb, SIGNAL("activated(int)"), slot)
             QObject.connect(cb, SIGNAL("editTextChanged(const QString &)"),
                             slot)
             cb.setEnabled(group is model.defgroup or group.style.apply)
             self.table.setCellWidget(irow, icol, cb)
             label = attr
             if irow:
                 cb.setToolTip(
                     """<P>This is the %s used to plot sources in this group, when a "custom" style for the group
       is enabled via the style control.<P>""" % label)
             else:
                 cb.setToolTip(
                     "<P>This is the default %s used for all sources for which a custom style is not specified below.<P>"
                     % label)
     self.table.resizeColumnsToContents()
     # re-enable processing of cellChanged() signals
     self._setting_model = False
Пример #4
0
 def _updateModel(self, what=SkyModel.UpdateAll, origin=None):
     if origin is self or not what & (SkyModel.UpdateTags | SkyModel.UpdateGroupStyle):
         return
     model = self.model
     self._setting_model = True;  # to ignore cellChanged() signals (in valueChanged())
     # _item_cb is a dict (with row,col keys) containing the widgets (CheckBoxes ComboBoxes) per each cell
     self._item_cb = {}
     # lists of "list" and "plot" checkboxes per each grouping (excepting the default grouping); each entry is an (row,col,item) tuple.
     # used as argument to self._showControls()
     self._list_controls = []
     self._plot_controls = []
     # list of selection callbacks (to which signals are connected)
     self._callbacks = []
     # set requisite number of rows,and start filling
     self.table.setRowCount(len(model.groupings))
     for irow, group in enumerate(model.groupings):
         self.table.setItem(irow, 0, QTableWidgetItem(group.name))
         if group is model.selgroup:
             self._irow_selgroup = irow
         # total # source in group: skip for "current"
         if group is not model.curgroup:
             self.table.setItem(irow, 1, QTableWidgetItem(str(group.total)))
         # selection controls: skip for current and selection
         if group not in (model.curgroup, model.selgroup):
             btns = QWidget()
             lo = QHBoxLayout(btns)
             lo.setContentsMargins(0, 0, 0, 0)
             lo.setSpacing(0)
             # make selector buttons (depending on which group we're in)
             if group is model.defgroup:
                 Buttons = (
                     ("+", lambda src, grp=group: True, "select all sources"),
                     ("-", lambda src, grp=group: False, "unselect all sources"))
             else:
                 Buttons = (
                     ("=", lambda src, grp=group: grp.func(src), "select only this grouping"),
                     ("+", lambda src, grp=group: src.selected or grp.func(src), "add grouping to selection"),
                     ("-", lambda src, grp=group: src.selected and not grp.func(src),
                      "remove grouping from selection"),
                     ("&&", lambda src, grp=group: src.selected and grp.func(src),
                      "intersect selection with grouping"))
             lo.addStretch(1)
             for label, predicate, tooltip in Buttons:
                 btn = QToolButton(btns)
                 btn.setText(label)
                 btn.setMinimumWidth(24)
                 btn.setMaximumWidth(24)
                 btn.setToolTip(tooltip)
                 lo.addWidget(btn)
                 # add callback
                 QObject.connect(btn, SIGNAL("clicked()"), self._currier.curry(self.selectSources, predicate))
             lo.addStretch(1)
             self.table.setCellWidget(irow, 2, btns)
         # "list" checkbox (not for current and selected groupings: these are always listed)
         if group not in (model.curgroup, model.selgroup):
             item = self._makeCheckItem("", group, "show_list")
             self.table.setItem(irow, self.ColList, item)
             item.setToolTip("""<P>If checked, sources in this grouping will be listed in the source table. If un-checked, sources will be
         excluded from the table. If partially checked, then the default list/no list setting of "all sources" will be in effect.
         </P>""")
         # "plot" checkbox (not for the current grouping, since that's always plotted)
         if group is not model.curgroup:
             item = self._makeCheckItem("", group, "show_plot")
             self.table.setItem(irow, self.ColPlot, item)
             item.setToolTip("""<P>If checked, sources in this grouping will be included in the plot. If un-checked, sources will be
         excluded from the plot. If partially checked, then the default plot/no plot setting of "all sources" will be in effect.
         </P>""")
         # custom style control
         # for default, current and selected, this is just a text label
         if group is model.defgroup:
             item = QTableWidgetItem("default:")
             item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
             item.setToolTip(
                 """<P>This is the default plot style used for all sources for which a custom grouping style is not selected.</P>""")
             self.table.setItem(irow, self.ColApply, item)
         elif group is model.curgroup:
             item = QTableWidgetItem("")
             item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
             item.setToolTip("""<P>This is the plot style used for the highlighted source, if any.</P>""")
             self.table.setItem(irow, self.ColApply, item)
         elif group is model.selgroup:
             item = QTableWidgetItem("")
             item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
             item.setToolTip("""<P>This is the plot style used for the currently selected sources.</P>""")
             self.table.setItem(irow, self.ColApply, item)
         # for the rest, a combobox with custom priorities
         else:
             cb = QComboBox()
             cb.addItems(["default"] + ["custom %d" % p for p in range(1, 10)])
             index = max(0, min(group.style.apply, 9))
             #        dprint(0,group.name,"apply",index)
             cb.setCurrentIndex(index)
             QObject.connect(cb, SIGNAL("activated(int)"),
                             self._currier.xcurry(self._valueChanged, (irow, self.ColApply)))
             self.table.setCellWidget(irow, self.ColApply, cb)
             cb.setToolTip("""<P>This controls whether sources within this group are plotted with a customized
         plot style. Customized styles have numeric priority; if a source belongs to multiple groups, then
         the style with the lowest priority takes precedence.<P>""")
         # attribute comboboxes
         for icol, attr in self.AttrByCol.items():
             # get list of options for this style attribute. If dealing with first grouping (i==0), which is
             # the "all sources" grouping, then remove the "default" option (which is always first in the list)
             options = PlotStyles.StyleAttributeOptions[attr]
             if irow == 0:
                 options = options[1:]
             # make combobox
             cb = QComboBox()
             cb.addItems(list(map(str, options)))
             # the "label" option is also editable
             if attr == "label":
                 cb.setEditable(True)
             try:
                 index = options.index(getattr(group.style, attr))
                 cb.setCurrentIndex(index)
             except ValueError:
                 cb.setEditText(str(getattr(group.style, attr)))
             slot = self._currier.xcurry(self._valueChanged, (irow, icol))
             QObject.connect(cb, SIGNAL("activated(int)"), slot)
             QObject.connect(cb, SIGNAL("editTextChanged(const QString &)"), slot)
             cb.setEnabled(group is model.defgroup or group.style.apply)
             self.table.setCellWidget(irow, icol, cb)
             label = attr
             if irow:
                 cb.setToolTip("""<P>This is the %s used to plot sources in this group, when a "custom" style for the group
       is enabled via the style control.<P>""" % label)
             else:
                 cb.setToolTip(
                     "<P>This is the default %s used for all sources for which a custom style is not specified below.<P>" % label)
     self.table.resizeColumnsToContents()
     # re-enable processing of cellChanged() signals
     self._setting_model = False
Пример #5
0
class AddTagDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(modal)
        self.setWindowTitle("Add Tag")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # tag selector
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setSpacing(5)
        self.wtagsel = QComboBox(self)
        self.wtagsel.setEditable(True)
        wtagsel_lbl = QLabel("&Tag:", self)
        wtagsel_lbl.setBuddy(self.wtagsel)
        lo1.addWidget(wtagsel_lbl, 0)
        lo1.addWidget(self.wtagsel, 1)
        QObject.connect(self.wtagsel, SIGNAL("activated(int)"),
                        self._check_tag)
        QObject.connect(self.wtagsel,
                        SIGNAL("editTextChanged(const QString &)"),
                        self._check_tag_text)
        # value editor
        self.valedit = ValueTypeEditor(self)
        lo.addWidget(self.valedit)
        # buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setMargin(5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)

    def setTags(self, tagnames):
        self.wtagsel.clear()
        self.wtagsel.addItems(list(tagnames))
        self.wtagsel.addItem("")
        self.wtagsel.setCurrentIndex(len(tagnames))

    def setValue(self, value):
        self.valedit.setValue(value)

    def _check_tag(self, tag):
        self.wokbtn.setEnabled(True)

    def _check_tag_text(self, text):
        self.wokbtn.setEnabled(bool(str(text) != ""))

    def accept(self):
        """When dialog is accepted with a default (bool) tag type,
        check if the user hasn't entered a name=value entry in the tag name field.
        This is a common mistake, and should be treated as a shortcut for setting string tags."""
        if isinstance(self.valedit.getValue(), bool):
            tagval = str(self.wtagsel.currentText()).split("=", 1)
            if len(tagval) > 1:
                #        print tagval
                if QMessageBox.warning(
                        self, "Set a string tag instead?",
                        """<P>You have included an "=" sign in the tag name. 
            Perhaps you actually mean to set tag "%s" to the string value "%s"?</P>"""
                        % tuple(tagval), QMessageBox.Yes | QMessageBox.No,
                        QMessageBox.Yes) == QMessageBox.No:
                    return
                self.wtagsel.setEditText(tagval[0])
                self.valedit.setValue(tagval[1])
        return QDialog.accept(self)

    def getTag(self):
        return str(self.wtagsel.currentText()), self.valedit.getValue()
Пример #6
0
class AddTagDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(modal)
        self.setWindowTitle("Add Tag")
        lo = QVBoxLayout(self)
        lo.setMargin(10)
        lo.setSpacing(5)
        # tag selector
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setSpacing(5)
        self.wtagsel = QComboBox(self)
        self.wtagsel.setEditable(True)
        wtagsel_lbl = QLabel("&Tag:", self)
        wtagsel_lbl.setBuddy(self.wtagsel)
        lo1.addWidget(wtagsel_lbl, 0)
        lo1.addWidget(self.wtagsel, 1)
        QObject.connect(self.wtagsel, SIGNAL("activated(int)"), self._check_tag)
        QObject.connect(self.wtagsel, SIGNAL("editTextChanged(const QString &)"), self._check_tag_text)
        # value editor
        self.valedit = ValueTypeEditor(self)
        lo.addWidget(self.valedit)
        # buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setMargin(5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)

    def setTags(self, tagnames):
        self.wtagsel.clear()
        self.wtagsel.addItems(list(tagnames))
        self.wtagsel.addItem("")
        self.wtagsel.setCurrentIndex(len(tagnames))

    def setValue(self, value):
        self.valedit.setValue(value)

    def _check_tag(self, tag):
        self.wokbtn.setEnabled(True)

    def _check_tag_text(self, text):
        self.wokbtn.setEnabled(bool(str(text) != ""))

    def accept(self):
        """When dialog is accepted with a default (bool) tag type,
        check if the user hasn't entered a name=value entry in the tag name field.
        This is a common mistake, and should be treated as a shortcut for setting string tags."""
        if isinstance(self.valedit.getValue(), bool):
            tagval = str(self.wtagsel.currentText()).split("=", 1)
            if len(tagval) > 1:
                #        print tagval
                if QMessageBox.warning(self,
                                       "Set a string tag instead?", """<P>You have included an "=" sign in the tag name. 
            Perhaps you actually mean to set tag "%s" to the string value "%s"?</P>""" % tuple(tagval),
                                       QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) == QMessageBox.No:
                    return
                self.wtagsel.setEditText(tagval[0])
                self.valedit.setValue(tagval[1])
        return QDialog.accept(self)

    def getTag(self):
        return str(self.wtagsel.currentText()), self.valedit.getValue()
Пример #7
0
class TwitterGui(QWidget):    
    URL_REGEX = re.compile(r'''((?:mailto:|ftp://|http://|https://)[^ <>'"{}|\\^`[\]]*)''')
    
    def __init__(self, parent, logger, db_conn, update_func, safe_conn):
        super(TwitterGui, self).__init__(parent)
        self._db_conn = db_conn
        self.logger = logger
        self._reply_to_id = 0
        self._update_func = update_func
        
        self._list = None
        
        if get_settings().get_proxy():
            u = urlparse.urlsplit(get_settings().get_proxy())
            proxy = QNetworkProxy()
            
            proxy.setType(QNetworkProxy.HttpProxy)
            proxy.setHostName(u.hostname);
            proxy.setPort(u.port)
            QNetworkProxy.setApplicationProxy(proxy);
        
        self.msgview = QWebView(self)
        self.msgview.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.msgview.linkClicked.connect(self.link_clicked)
        
        self.userCombo = QComboBox(self)
        self.userCombo.setEditable(True)
        self.userCombo.activated.connect(self.toggle_user_in_list)
        
        self.showButton = QPushButton(chr(94), self)  
        self.showButton.setMaximumHeight(13)
        self.showButton.clicked.connect(self.show_hide_animation)
        
        self.post_field = QTextEdit(self)
        self.post_field.setMaximumHeight(50)
        self.post_field.textChanged.connect(self.text_changed)
        self.send_button = QPushButton("Post", self)
        self.send_button.clicked.connect(self.post_status_clicked)
        self.refresh_button = QPushButton("Refresh", self)
        self.refresh_button.clicked.connect(self._update_func)
        self.attach_button = QPushButton("Attach", self)
        self.attach_button.clicked.connect(lambda _ : self.set_status("Attach something"))
        self.lists_box = QComboBox(self)
        self.lists_box.currentIndexChanged.connect(self.list_changed)
        self.lists_box.setEditable(False)
        self.lists_box.addItems([u"Home"] + self._db_conn.get_lists())
        self.statusLabel = QLabel("Status", self)
        self.charCounter = QLabel("0", self)
        
        self.gridw = QWidget(self)
        self.gridw.setContentsMargins(0, 0, 0, 0)
        gridlay = QGridLayout(self.gridw)
        gridlay.setContentsMargins(0, 0, 0, 0)
        gridlay.addWidget(self.post_field, 0, 0, 2, 1)
        gridlay.addWidget(self.attach_button, 0, 1, 1, 1)
        gridlay.addWidget(self.send_button, 1, 1, 1, 1)
        gridlay.addWidget(self.lists_box, 0, 2, 1, 1)
        gridlay.addWidget(self.refresh_button, 1, 2, 1, 1)
        gridlay.addWidget(self.statusLabel, 2, 0, 1, 1)
        gridlay.addWidget(self.charCounter, 2, 1, 1, 2)
        
        hlay = QVBoxLayout(self)
        hlay.addWidget(self.msgview)
        hlay.addWidget(self.userCombo)
        hlay.addWidget(self.showButton)
        hlay.addWidget(self.gridw)
        
        safe_conn.connect_home_timeline_updated(self.update_view)
        safe_conn.connect_twitter_loop_started(self.start_refresh_animation)
        safe_conn.connect_twitter_loop_stopped(self.stop_refresh_animation)
        safe_conn.connect_update_posted(self.enable_posting)
        safe_conn.connect_range_limit_exceeded(lambda _ : self.set_status("Range limit exceeded"))
        safe_conn.connect_not_authenticated(lambda _ : self.set_status("Authentication failed"))
        
        self.gridw.hide()
        self.update_view()
        self.set_status("Twitter plugin initialized")
        
    def enable_posting(self, q_id, m_id):
        if m_id>1:
            self.post_field.setText("")
            self.set_status("Tweet posted")
        else:
            self.set_status("Failed to post tweet, Error: " + str(abs(m_id)))
        self.post_field.setEnabled(True)
        
    def link_clicked(self, url):
        if not url.host():
            if url.hasQueryItem("reply-to") and url.hasQueryItem("screen-name"):
                self._reply_to_id = long(convert_string(url.queryItemValue("reply-to")))
                self.post_field.setPlainText("@"+convert_string(url.queryItemValue("screen-name"))+" ")
                self.set_status("Reply to @"+convert_string(url.queryItemValue("screen-name")))
            else:
                self.logger.error("Unknown command from link: "+str(url.toString()))
        else:
            webbrowser.open(str(url.toString()))
            
    def list_changed(self, list_idx):
        if list_idx:
            self._list = convert_string(self.lists_box.currentText())
            self.userCombo.clear()
            self.userCombo.addItems(self._db_conn.get_known_users())            
            self.userCombo.completer().setCompletionMode(QCompleter.PopupCompletion)
            self.userCombo.show()
            self.set_status(self._list)
        else:
            self.userCombo.hide()
            self._list = None
        self.update_view()
        
    def post_status_clicked(self):
        msg = unicode(self.post_field.toPlainText().toUtf8(), encoding="UTF-8")
        if msg:
            self._db_conn.insert_post_queue(msg, self._reply_to_id)
            self._reply_to_id = 0
            self._update_func()
            self.post_field.setDisabled(True)
    
    def start_refresh_animation(self):
        self.refresh_button.setDisabled(True)
    
    def stop_refresh_animation(self):
        self.refresh_button.setEnabled(True)
        
    def show_hide_animation(self):
        if self.gridw.isHidden():
            self.gridw.show()            
            self.showButton.setText("v")
        else:
            self.gridw.hide()
            self.showButton.setText(chr(94))
            
    def text_changed(self):
        count = len(self.post_field.toPlainText())
        if count==0:
            self._reply_to_id = 0
        if self._reply_to_id:
            self.charCounter.setText(str(count) + " - reply to ")
        else:
            self.charCounter.setText(str(count))
            
    def toggle_user_in_list(self, _):
        user = convert_string(self.userCombo.currentText())
        if user in self._db_conn.get_users_from_list(self._list):
            self._db_conn.delete_user_from_list(user, self._list)
            self.set_status("Removed user %s from list %s"%(user, self._list))
        else:
            self._db_conn.add_user_to_list(user, self._list)
            self.set_status("Added user %s to list %s"%(user, self._list))
        self.update_view()
        
        
    @pyqtSlot(object)
    def update_view(self, _ = None):
        template_file_path = os.path.join(get_settings().get_main_config_dir(),"tweet.thtml")
        
        tweets = self._db_conn.get_last_tweets(user_list = self._list)
        if len(tweets)==0:
            return 0
        
        templ_text = '<div>\
                    <a href="http://twitter.com/$NAME$/status/$ID$">\
                            <img src="$IMAGE$" style="float: left; margin-right: 2px" alt="$NAME$" title="$NAME$"/>\
                    </a>\
                    <p>$TEXT$</p>\
                    <span><a href="http://twitter.com/$RT_USER$">$RT_USER$</a></span>\
                    <span style="float: right">$CREATE_TIME$ <a href="?retweet=$ID$">retweet</a> <a href="?reply-to=$ID$&screen-name=$NAME$">reply</a></span>\
                    </div>\
                    <hr style="clear: both" />\
                    '
        if os.path.exists(template_file_path):
            t_file = open(template_file_path, "r")
            templ_text = t_file.read()
            t_file.close()
        
        txt = ""
        for t in tweets:
            """m_id, screen_name, user_image, create_time, message_text, retweeted_by"""
            text = self.URL_REGEX.sub(r'<a href="\1">\1</a>', t[4])
            t_txt = templ_text.replace("$IMAGE$", t[2]).replace("$NAME$", t[1])
            t_txt = t_txt.replace("$ID$", str(t[0])).replace("$TEXT$", text)
            t_txt = t_txt.replace("$CREATE_TIME$", self.humanReadableTime(t[3]))
            t_txt = t_txt.replace("$RT_USER$", t[5] if t[5] else "")
            txt += t_txt
        txt += "<p style=\"float:right\">Updated: %s</p>"%time.strftime("%H:%M")

        self.msgview.setHtml(txt)
        
            
    """helper:"""
    def set_status(self, status_text):
        self.statusLabel.setText(status_text)
        self.showButton.setToolTip(status_text)
    
    def humanReadableTime(self, post_time):
        fudge = 1.25
        delta = long(time.time()) - long(post_time)
    
        if delta < (1 * fudge):
            return 'about a second ago' 
        elif delta < (60 * (1 / fudge)):
            return 'about %d seconds ago' % (delta)
        elif delta < (60 * fudge):
            return 'about a minute ago'
        elif delta < (60 * 60 * (1 / fudge)):
            return 'about %d minutes ago' % (delta / 60)
        elif delta < (60 * 60 * fudge) or delta / (60 * 60) == 1:
            return 'about an hour ago'
        elif delta < (60 * 60 * 24 * (1 / fudge)):
            return 'about %d hours ago' % (delta / (60 * 60))
        elif delta < (60 * 60 * 24 * fudge) or delta / (60 * 60 * 24) == 1:
            return 'about a day ago'
        else:
            return 'about %d days ago' % (delta / (60 * 60 * 24))