Beispiel #1
0
 def __init__(self, parent=None):
     QTableView.__init__(self, parent)
     self.rt_delegate = RichTextDelegate(self)
     self.setSelectionMode(self.SingleSelection)
     self.setAlternatingRowColors(True)
     self.setSelectionBehavior(self.SelectRows)
     self.setIconSize(QSize(24, 24))
     self.clicked.connect(self.show_details)
     self.doubleClicked.connect(self.select_index)
     self.setSortingEnabled(True)
Beispiel #2
0
 def __init__(self, parent=None):
     QTableView.__init__(self, parent)
     self.rt_delegate = RichTextDelegate(self)
     self.setSelectionMode(self.SingleSelection)
     self.setAlternatingRowColors(True)
     self.setSelectionBehavior(self.SelectRows)
     self.setIconSize(QSize(24, 24))
     self.clicked.connect(self.show_details)
     self.doubleClicked.connect(self.select_index)
     self.setSortingEnabled(True)
Beispiel #3
0
    def _initialize_controls(self):
        self.setWindowTitle(_('User plugins'))
        self.setWindowIcon(QIcon(I('plugins/plugin_updater.png')))
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png',
                _('User Plugins'))
        layout.addLayout(title_layout)

        header_layout = QHBoxLayout()
        layout.addLayout(header_layout)
        self.filter_combo = PluginFilterComboBox(self)
        self.filter_combo.setMinimumContentsLength(20)
        self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed)
        header_layout.addWidget(QLabel(_('Filter list of plugins')+':', self))
        header_layout.addWidget(self.filter_combo)
        header_layout.addStretch(10)

        self.plugin_view = QTableView(self)
        self.plugin_view.horizontalHeader().setStretchLastSection(True)
        self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.plugin_view.setAlternatingRowColors(True)
        self.plugin_view.setSortingEnabled(True)
        self.plugin_view.setIconSize(QSize(28, 28))
        layout.addWidget(self.plugin_view)

        details_layout = QHBoxLayout()
        layout.addLayout(details_layout)
        forum_label = QLabel('<a href="http://www.foo.com/">Plugin Forum Thread</a>', self)
        forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
        forum_label.linkActivated.connect(self._forum_label_activated)
        details_layout.addWidget(QLabel(_('Description')+':', self), 0, Qt.AlignLeft)
        details_layout.addWidget(forum_label, 1, Qt.AlignRight)

        self.description = QLabel(self)
        self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.description.setMinimumHeight(40)
        self.description.setWordWrap(True)
        layout.addWidget(self.description)

        self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
        self.button_box.rejected.connect(self.reject)
        self.finished.connect(self._finished)
        self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.AcceptRole)
        self.install_button.setToolTip(_('Install the selected plugin'))
        self.install_button.clicked.connect(self._install_clicked)
        self.install_button.setEnabled(False)
        self.configure_button = self.button_box.addButton(' '+_('&Customize plugin ')+' ', QDialogButtonBox.ResetRole)
        self.configure_button.setToolTip(_('Customize the options for this plugin'))
        self.configure_button.clicked.connect(self._configure_clicked)
        self.configure_button.setEnabled(False)
        layout.addWidget(self.button_box)
Beispiel #4
0
 def keyPressEvent(self, ev):
     ret = QTableView.keyPressEvent(self, ev)
     if ev.key() in (Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Up, Qt.Key_Down):
         idx = self.currentIndex()
         if idx.isValid():
             self.scrollTo(idx)
     return ret
Beispiel #5
0
 def moveCursor(self, action, modifiers):
     orig = self.currentIndex()
     index = QTableView.moveCursor(self, action, modifiers)
     if action == QTableView.MovePageDown:
         moved = index.row() - orig.row()
         try:
             rows = self.row_at_bottom() - self.row_at_top()
         except TypeError:
             rows = moved
         if moved > rows:
             index = self.model().index(orig.row() + rows, index.column())
     elif action == QTableView.MovePageUp:
         moved = orig.row() - index.row()
         try:
             rows = self.row_at_bottom() - self.row_at_top()
         except TypeError:
             rows = moved
         if moved > rows:
             index = self.model().index(orig.row() - rows, index.column())
     elif action == QTableView.MoveHome and modifiers & Qt.ControlModifier:
         return self.model().index(0, orig.column())
     elif action == QTableView.MoveEnd and modifiers & Qt.ControlModifier:
         return self.model().index(self.model().rowCount(QModelIndex()) - 1,
                                   orig.column())
     return index
Beispiel #6
0
 def mousePressEvent(self, event):
     ep = event.pos()
     if (
         self.indexAt(ep) in self.selectionModel().selectedIndexes()
         and event.button() == Qt.LeftButton
         and not self.event_has_mods()
     ):
         self.drag_start_pos = ep
     return QTableView.mousePressEvent(self, event)
    def _initialize_controls(self):
        self.setWindowTitle(_("User plugins"))
        self.setWindowIcon(QIcon(I("plugins/plugin_updater.png")))
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        title_layout = ImageTitleLayout(self, "plugins/plugin_updater.png", _("User Plugins"))
        layout.addLayout(title_layout)

        header_layout = QHBoxLayout()
        layout.addLayout(header_layout)
        self.filter_combo = PluginFilterComboBox(self)
        self.filter_combo.setMinimumContentsLength(20)
        self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed)
        header_layout.addWidget(QLabel(_("Filter list of plugins") + ":", self))
        header_layout.addWidget(self.filter_combo)
        header_layout.addStretch(10)

        self.plugin_view = QTableView(self)
        self.plugin_view.horizontalHeader().setStretchLastSection(True)
        self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.plugin_view.setAlternatingRowColors(True)
        self.plugin_view.setSortingEnabled(True)
        self.plugin_view.setIconSize(QSize(28, 28))
        layout.addWidget(self.plugin_view)

        details_layout = QHBoxLayout()
        layout.addLayout(details_layout)
        forum_label = QLabel('<a href="http://www.foo.com/">Plugin Forum Thread</a>', self)
        forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
        forum_label.linkActivated.connect(self._forum_label_activated)
        details_layout.addWidget(QLabel(_("Description") + ":", self), 0, Qt.AlignLeft)
        details_layout.addWidget(forum_label, 1, Qt.AlignRight)

        self.description = QLabel(self)
        self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.description.setMinimumHeight(40)
        self.description.setWordWrap(True)
        layout.addWidget(self.description)

        self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
        self.button_box.rejected.connect(self.reject)
        self.finished.connect(self._finished)
        self.install_button = self.button_box.addButton(_("&Install"), QDialogButtonBox.AcceptRole)
        self.install_button.setToolTip(_("Install the selected plugin"))
        self.install_button.clicked.connect(self._install_clicked)
        self.install_button.setEnabled(False)
        self.configure_button = self.button_box.addButton(
            " " + _("&Customize plugin ") + " ", QDialogButtonBox.ResetRole
        )
        self.configure_button.setToolTip(_("Customize the options for this plugin"))
        self.configure_button.clicked.connect(self._configure_clicked)
        self.configure_button.setEnabled(False)
        layout.addWidget(self.button_box)
Beispiel #8
0
 def keyPressEvent(self, ev):
     if ev == QKeySequence.Copy:
         self.copy_to_clipboard()
         ev.accept()
         return
     ret = QTableView.keyPressEvent(self, ev)
     if ev.key() in (Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Up, Qt.Key_Down):
         idx = self.currentIndex()
         if idx.isValid():
             self.scrollTo(idx)
     return ret
    def setup_ui(self):
        self.setAttribute(Qt.WA_DeleteOnClose, False)
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)

        self.bb.clear()
        self.bb.addButton(self.bb.Close)
        self.splitter = s = QSplitter(self)
        l.addWidget(s), l.addWidget(self.bb)

        self.fonts_view = fv = QTableView(self)
        self.model = m = AllFonts(fv)
        fv.horizontalHeader().setStretchLastSection(True)
        fv.setModel(m)
        fv.setSortingEnabled(True)
        fv.setShowGrid(False)
        fv.setAlternatingRowColors(True)
        fv.setSelectionMode(fv.ExtendedSelection)
        fv.setSelectionBehavior(fv.SelectRows)
        fv.horizontalHeader().setSortIndicator(1, Qt.AscendingOrder)
        self.container = c = QWidget()
        l = c.l = QVBoxLayout(c)
        c.setLayout(l)
        s.addWidget(fv), s.addWidget(c)

        self.cb = b = QPushButton(_('&Change selected fonts'))
        b.setIcon(QIcon(I('auto_author_sort.png')))
        b.clicked.connect(self.change_fonts)
        l.addWidget(b)
        self.rb = b = QPushButton(_('&Remove selected fonts'))
        b.clicked.connect(self.remove_fonts)
        b.setIcon(QIcon(I('trash.png')))
        l.addWidget(b)
        self.eb = b = QPushButton(_('&Embed all fonts'))
        b.setIcon(QIcon(I('embed-fonts.png')))
        b.clicked.connect(self.embed_fonts)
        l.addWidget(b)
        self.sb = b = QPushButton(_('&Subset all fonts'))
        b.setIcon(QIcon(I('subset-fonts.png')))
        b.clicked.connect(self.subset_fonts)
        l.addWidget(b)
        self.refresh_button = b = self.bb.addButton(_('&Refresh'), self.bb.ActionRole)
        b.setToolTip(_('Rescan the book for fonts in case you have made changes'))
        b.setIcon(QIcon(I('view-refresh.png')))
        b.clicked.connect(self.refresh)

        self.la = la = QLabel('<p>' + _(
        ''' All the fonts declared in this book are shown to the left, along with whether they are embedded or not.
            You can remove or replace any selected font and also embed any declared fonts that are not already embedded.'''))
        la.setWordWrap(True)
        l.addWidget(la)

        l.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
Beispiel #10
0
    def mouseMoveEvent(self, event):
        if not self.drag_allowed:
            return
        if self.drag_start_pos is None:
            return QTableView.mouseMoveEvent(self, event)

        if self.event_has_mods():
            self.drag_start_pos = None
            return

        if not (event.buttons() & Qt.LeftButton) or \
                (event.pos() - self.drag_start_pos).manhattanLength() \
                      < QApplication.startDragDistance():
            return

        index = self.indexAt(event.pos())
        if not index.isValid():
            return
        drag = self.drag_data()
        drag.exec_(Qt.CopyAction)
        self.drag_start_pos = None
Beispiel #11
0
    def mouseMoveEvent(self, event):
        if not self.drag_allowed:
            return
        if self.drag_start_pos is None:
            return QTableView.mouseMoveEvent(self, event)

        if self.event_has_mods():
            self.drag_start_pos = None
            return

        if not (event.buttons() & Qt.LeftButton) or \
                (event.pos() - self.drag_start_pos).manhattanLength() \
                      < QApplication.startDragDistance():
            return

        index = self.indexAt(event.pos())
        if not index.isValid():
            return
        drag = self.drag_data()
        drag.exec_(Qt.CopyAction)
        self.drag_start_pos = None
Beispiel #12
0
 def moveCursor(self, action, modifiers):
     orig = self.currentIndex()
     index = QTableView.moveCursor(self, action, modifiers)
     if action == QTableView.MovePageDown:
         moved = index.row() - orig.row()
         try:
             rows = self.row_at_bottom() - self.row_at_top()
         except TypeError:
             rows = moved
         if moved > rows:
             index = self.model().index(orig.row() + rows, index.column())
     elif action == QTableView.MovePageUp:
         moved = orig.row() - index.row()
         try:
             rows = self.row_at_bottom() - self.row_at_top()
         except TypeError:
             rows = moved
         if moved > rows:
             index = self.model().index(orig.row() - rows, index.column())
     elif action == QTableView.MoveHome and modifiers & Qt.ControlModifier:
         return self.model().index(0, orig.column())
     elif action == QTableView.MoveEnd and modifiers & Qt.ControlModifier:
         return self.model().index(self.model().rowCount(QModelIndex()) - 1, orig.column())
     return index
Beispiel #13
0
 def scrollContentsBy(self, dx, dy):
     # Needed as Qt bug causes headerview to not always update when scrolling
     QTableView.scrollContentsBy(self, dx, dy)
     if dy != 0:
         self.column_header.update()
Beispiel #14
0
 def mousePressEvent(self, event):
     ep = event.pos()
     if self.indexAt(ep) in self.selectionModel().selectedIndexes() and \
             event.button() == Qt.LeftButton and not self.event_has_mods():
         self.drag_start_pos = ep
     return QTableView.mousePressEvent(self, event)
Beispiel #15
0
 def __init__(self, parent=None):
     QTableView.__init__(self, parent)
     self.setSortingEnabled(True), self.setShowGrid(False), self.setAlternatingRowColors(True)
     self.setSelectionBehavior(self.SelectRows)
     self.setTabKeyNavigation(False)
     self.verticalHeader().close()
Beispiel #16
0
class MainWidget(QWidget):
    def __init__(self):
        QWidget.__init__(self)

        # define periodic table widget for element selection
        self.periodicTableWidget = widgets.PeriodicTableDialog()

        # initial molecule Zmatrix (can be empty)
        # self.inp = []
        self.inp = [['H'],
        ['O', 1, 0.9],
        ['O', 2, 1.4, 1, 105.],
        ['H', 3, 0.9, 2, 105., 1, 120.]]

        self.atomList = []
        self.highList = []
        self.labelList = []
        self.fast = False

        # define & initialize ZMatModel that will contain Zmatrix data
        self.ZMatModel = QStandardItemModel(len(self.inp), 7, self)
        self.ZMatTable = QTableView(self)
        self.ZMatTable.setModel(self.ZMatModel)
        self.ZMatTable.setFixedWidth(325)
        #self.ZMatTable.installEventFilter(self)
        #self.ZMatModel.installEventFilter(self)
        self.ZMatModel.setHorizontalHeaderLabels(['atom','','bond','','angle','','dihedral'])
        for j, width in enumerate([40, 22, 65, 22, 65, 22, 65]):
            self.ZMatTable.setColumnWidth(j, width)
        # populate the ZMatModel
        self.populateZMatModel()

        #define Menu bar menus and their actions
        self.menuBar = QMenuBar(self)
        fileMenu = self.menuBar.addMenu('&File')
        editMenu = self.menuBar.addMenu('&Edit')
        viewMenu = self.menuBar.addMenu('&View')
        measureMenu = self.menuBar.addMenu('&Measure')
        helpMenu = self.menuBar.addMenu('&Help')

        readZmatAction = QAction('&Read &ZMat', self)
        readZmatAction.setShortcut('Ctrl+O')
        readZmatAction.setStatusTip('Read Zmat from file')
        readZmatAction.triggered.connect(self.readZmat)
        fileMenu.addAction(readZmatAction)

        readXYZAction = QAction('&Read &XYZ', self)
        readXYZAction.setShortcut('Ctrl+Shift+O')
        readXYZAction.setStatusTip('Read XYZ from file')
        readXYZAction.triggered.connect(self.readXYZ)
        fileMenu.addAction(readXYZAction)

        readGaussianAction = QAction('&Read &Gaussian log', self)
        readGaussianAction.setShortcut('Ctrl+G')
        readGaussianAction.setStatusTip('Read Gaussian log file')
        readGaussianAction.triggered.connect(self.readGaussian)
        fileMenu.addAction(readGaussianAction)

        writeZmatAction = QAction('&Write &ZMat', self)
        writeZmatAction.setShortcut('Ctrl+S')
        writeZmatAction.setStatusTip('Write Zmat to file')
        writeZmatAction.triggered.connect(self.writeZmat)
        fileMenu.addAction(writeZmatAction)

        writeXYZAction = QAction('&Write &XYZ', self)
        writeXYZAction.setShortcut('Ctrl+Shift+S')
        writeXYZAction.setStatusTip('Write XYZ from file')
        writeXYZAction.triggered.connect(self.writeXYZ)
        fileMenu.addAction(writeXYZAction)

        exitAction = QAction('&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(qApp.quit)
        fileMenu.addAction(exitAction)

        addRowAction = QAction('&Add &row', self)
        addRowAction.setShortcut('Ctrl+R')
        addRowAction.setStatusTip('Add row to ZMatrix')
        addRowAction.triggered.connect(self.addRow)
        editMenu.addAction(addRowAction)

        deleteRowAction = QAction('&Delete &row', self)
        deleteRowAction.setShortcut('Ctrl+Shift+R')
        deleteRowAction.setStatusTip('Delete row from ZMatrix')
        deleteRowAction.triggered.connect(self.deleteRow)
        editMenu.addAction(deleteRowAction)

        addAtomAction = QAction('&Add &atom', self)
        addAtomAction.setShortcut('Ctrl+A')
        addAtomAction.setStatusTip('Add atom to ZMatrix')
        addAtomAction.triggered.connect(self.buildB)
        editMenu.addAction(addAtomAction)

        drawModeMenu = QMenu('Draw mode', self)
        viewMenu.addMenu(drawModeMenu)
        fastDrawAction = QAction('&Fast draw', self)
        fastDrawAction.triggered.connect(self.fastDraw)
        normalDrawAction = QAction('&Normal draw', self)
        normalDrawAction.triggered.connect(self.normalDraw)
        drawModeMenu.addAction(normalDrawAction)
        drawModeMenu.addAction(fastDrawAction)

        clearHighlightsAction = QAction('&Clear selection', self)
        clearHighlightsAction.setShortcut('Ctrl+C')
        clearHighlightsAction.setStatusTip('Clear highlighted atoms')
        clearHighlightsAction.triggered.connect(self.clearHighlights)
        viewMenu.addAction(clearHighlightsAction)

        clearLabelsAction = QAction('&Clear labels', self)
        clearLabelsAction.setShortcut('Ctrl+Alt+C')
        clearLabelsAction.setStatusTip('Clear labels')
        clearLabelsAction.triggered.connect(self.clearLabels)
        viewMenu.addAction(clearLabelsAction)

        clearUpdateViewAction = QAction('&Clear selection and labels', self)
        clearUpdateViewAction.setShortcut('Ctrl+Shift+C')
        clearUpdateViewAction.setStatusTip('Clear highlighted atoms and labels')
        clearUpdateViewAction.triggered.connect(self.clearUpdateView)
        viewMenu.addAction(clearUpdateViewAction)

        self.showGaussAction = QAction('Show &Gaussian geometry optimization', self)
        self.showGaussAction.setShortcut('Ctrl+G')
        self.showGaussAction.setStatusTip('Show Gaussian geometry optimization plots for energy, force and displacement.')
        self.showGaussAction.setEnabled(False)
        self.showGaussAction.triggered.connect(self.showGauss)
        viewMenu.addAction(self.showGaussAction)
        self.showFreqAction = QAction('Show &IR frequency plot', self)
        self.showFreqAction.setShortcut('Ctrl+I')
        self.showFreqAction.setStatusTip('Show Gaussian calculated IR frequency plot.')
        self.showFreqAction.setEnabled(False)
        self.showFreqAction.triggered.connect(self.showFreq)
        viewMenu.addAction(self.showFreqAction)

        measureDistanceAction = QAction('&Measure &distance', self)
        measureDistanceAction.setShortcut('Ctrl+D')
        measureDistanceAction.setStatusTip('Measure distance between two atoms')
        measureDistanceAction.triggered.connect(self.measureDistanceB)
        measureMenu.addAction(measureDistanceAction)

        measureAngleAction = QAction('&Measure &angle', self)
        measureAngleAction.setShortcut('Ctrl+Shift+D')
        measureAngleAction.setStatusTip('Measure angle between three atoms')
        measureAngleAction.triggered.connect(self.measureAngleB)
        measureMenu.addAction(measureAngleAction)

        aboutAction = QAction('&About', self)
        aboutAction.setStatusTip('About this program...')
        aboutAction.triggered.connect(self.about)
        helpMenu.addAction(aboutAction)

        aboutQtAction = QAction('&About Qt', self)
        aboutQtAction.setStatusTip('About Qt...')
        aboutQtAction.triggered.connect(self.aboutQt)
        helpMenu.addAction(aboutQtAction)

        # define GL widget that displays the 3D molecule model
        self.window = widgets.MyGLView()
        self.window.installEventFilter(self)
        self.window.setMinimumSize(500, 500)
        #self.window.setBackgroundColor((50, 0, 10))
        self.updateView()

        self.gaussianPlot = GraphicsLayoutWidget()
        self.gaussianPlot.resize(750, 250)
        self.gaussianPlot.setWindowTitle('Gaussian geometry optimization')
        #self.gaussianPlot.setAspectLocked(True)
        #self.gaussianPlot.addLayout(rowspan=3, colspan=1)

        self.FreqModel = QStandardItemModel(1, 3, self)
        self.freqTable = QTableView(self)
        self.freqTable.setModel(self.FreqModel)
        self.freqTable.setMinimumWidth(240)
        self.freqTable.installEventFilter(self)
        self.FreqModel.installEventFilter(self)
        self.FreqModel.setHorizontalHeaderLabels(['Frequency','IR Intensity','Raman Intensity'])
        for j, width in enumerate([80, 80, 80]):
            self.freqTable.setColumnWidth(j, width)

        self.freqWidget = QWidget()
        self.freqWidget.setWindowTitle('IR frequency plot & table')
        self.freqWidget.resize(800, 400)
        self.freqWidget.layout = QHBoxLayout(self.freqWidget)
        self.freqWidget.layout.setSpacing(1)
        self.freqWidget.layout.setContentsMargins(1, 1, 1, 1)
        self.freqPlot = GraphicsLayoutWidget()
        self.freqWidget.layout.addWidget(self.freqPlot)
        self.freqWidget.layout.addWidget(self.freqTable)
        self.freqTable.clicked.connect(self.freqCellClicked)

        # define other application parts
        self.statusBar = QStatusBar(self)
        self.fileDialog = QFileDialog(self)

        # define application layout
        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(1)
        self.layout.setContentsMargins(1, 1, 1, 1)
        self.layout1 = QHBoxLayout()
        self.layout1.setSpacing(1)
        self.layout1.addWidget(self.ZMatTable)
        self.layout1.addWidget(self.window)
        self.layout.addWidget(self.menuBar)
        self.layout.addLayout(self.layout1)
        self.layout.addWidget(self.statusBar)

        self.adjustSize()
        self.setWindowTitle('Moldy')
        iconPath = 'icon.png'
        icon = QIcon(iconPath)
        icon.addFile(iconPath, QSize(16, 16))
        icon.addFile(iconPath, QSize(24, 24))
        icon.addFile(iconPath, QSize(32, 32))
        icon.addFile(iconPath, QSize(48, 48))
        icon.addFile(iconPath, QSize(256, 256))
        self.setWindowIcon(icon)

        # start monitoring changes in the ZMatModel
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)

    # run and show the application
    def run(self):
        self.show()
        self.ZMatTable.clicked.connect(self.ZMatCellClicked)
        qt_app.instance().aboutToQuit.connect(self.deleteGLwidget)
        qt_app.exec_()

    # fill the ZMatModel with initial data from 'self.inp'
    def populateZMatModel(self):
        self.ZMatModel.removeRows(0, self.ZMatModel.rowCount())
        for i, row in enumerate(self.inp):
            for j, cell in enumerate(row):
                item = QStandardItem(str(cell))
                self.ZMatModel.setItem(i, j, item)
        # some cells should not be editable, they are disabled
        for i in range(min(len(self.inp), 3)):
            for j in range(2*i+1, 7):
                self.ZMatModel.setItem(i, j, QStandardItem())
                self.ZMatModel.item(i, j).setBackground(QColor(150,150,150))
                self.ZMatModel.item(i, j).setFlags(Qt.ItemIsEnabled)
    
    def populateFreqModel(self):
        self.FreqModel.removeRows(0, self.FreqModel.rowCount())
        for i, row in enumerate(zip(self.vibfreqs, self.vibirs, self.vibramans)):
            for j, cell in enumerate(row):
                item = QStandardItem(str(cell))
                self.FreqModel.setItem(i, j, item)

    # add a row to the bottom of the ZMatModel
    def addRow(self):
        # temporarily stop updating the GL window
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        row = self.ZMatModel.rowCount()
        self.ZMatModel.insertRow(row)
        # some cells should not be editable
        if row < 3:
            for j in range(2*row+1, 7):
                self.ZMatModel.setItem(row, j, QStandardItem())
                self.ZMatModel.item(row, j).setBackground(QColor(150,150,150))
                self.ZMatModel.item(row, j).setFlags(Qt.ItemIsEnabled)
        # restart GL window updating
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Added 1 row.', 3000)

    # delete the last row of the ZMatModel
    def deleteRow(self):
        xyz = [list(vi) for vi in list(v)]
        atoms = [str(elements[e]) for e in elems]
        oldLen = self.ZMatModel.rowCount()
        idxs = sorted(set(idx.row() for idx in self.ZMatTable.selectedIndexes()), reverse=True)
        newLen = oldLen - len(idxs)
        if newLen == oldLen:
            self.ZMatModel.removeRow(self.ZMatModel.rowCount()-1)
        else:
            self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
            for idx in idxs:
                self.ZMatModel.removeRow(idx)
                if idx < 3:
                    for i in range(idx, min(3, newLen)):
                        for j in range(2*i+1, 7):
                            self.ZMatModel.setItem(i, j, QStandardItem())
                            self.ZMatModel.item(i, j).setBackground(QColor(150,150,150))
                            self.ZMatModel.item(i, j).setFlags(Qt.ItemIsEnabled)
                if len(xyz) > idx:
                    xyz.pop(idx)
                    atoms.pop(idx)
            self.inp = xyz2zmat(xyz, atoms)
            self.populateZMatModel()
            for i in reversed(self.highList):
                self.window.removeItem(i[1])
            self.highList = []
            self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()
        self.statusBar.clearMessage()
        if idxs:
            self.statusBar.showMessage('Deleted row(s): '+str([i+1 for i in idxs]), 3000)
        else:
            self.statusBar.showMessage('Deleted last row.', 3000)

    # show the periodic table widget
    def periodicTable(self):
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Select element from periodic table.')
        self.periodicTableWidget.exec_()
        selection = self.periodicTableWidget.selection()
        return selection

    # import molecule with zmatrix coordinates
    def readZmat(self):
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        filename = self.fileDialog.getOpenFileName(self, 'Open file', expanduser('~'), '*.zmat;;*.*')
        self.inp = []
        self.populateZMatModel()
        if filename:
            with open(filename, 'r') as f:
                next(f)
                next(f)
                for row in f:
                    self.inp.append(row.split())
                f.close()
            self.populateZMatModel()
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Read molecule from '+filename+'.', 5000)
        self.showGaussAction.setEnabled(False)
        self.showFreqAction.setEnabled(False)

    # import molecule with xyz coordinates
    def readXYZ(self):
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        filename = self.fileDialog.getOpenFileName(self, 'Open file', expanduser('~'), '*.xyz;;*.*')
        xyz = []
        elems = []
        self.inp = []
        self.populateZMatModel()
        if filename:
            with open(filename, 'r') as f:
                next(f)
                next(f)
                for row in f:
                    rs = row.split()
                    if len(rs) == 4:
                        elems.append(rs[0])
                        xyz.append([float(f) for f in rs[1:]])
                f.close()
            self.inp = xyz2zmat(xyz, elems)
            self.populateZMatModel()
            #print(elems)
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Read molecule from '+filename+'.', 5000)
        self.showGaussAction.setEnabled(False)
        self.showFreqAction.setEnabled(False)

    # import Gaussian log file
    def readGaussian(self):
        global vsShifted
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        filename = self.fileDialog.getOpenFileName(self, 'Open file', expanduser('~'), '*.log;;*.*')
        if filename:
            self.gaussianPlot.clear()
            self.inp = []
            self.populateZMatModel()
            file = ccopen(filename)
            data = file.parse().getattributes()
            self.natom = data['natom']
            self.atomnos = data['atomnos'].tolist()
            self.atomsymbols = [ str(elements[e]) for e in self.atomnos ]
            self.atomcoords = data['atomcoords'].tolist()
            self.scfenergies = data['scfenergies'].tolist()
            self.geovalues = data['geovalues'].T.tolist()
            self.geotargets = data['geotargets'].tolist()
            if 'vibfreqs' in data.keys():
                self.vibfreqs = data['vibfreqs']
                #print(self.vibfreqs)
                self.vibirs = data['vibirs']
                #print(self.vibirs)
                #print(data.keys())
                if 'vibramans' in data.keys():
                    self.vibramans = data['vibramans']
                else:
                    self.vibramans = [''] * len(self.vibirs)
                self.vibdisps = data['vibdisps']
                #print(self.vibdisps)
            self.inp = xyz2zmat(self.atomcoords[0], self.atomsymbols)
            self.populateZMatModel()

            titles = ['SCF Energies', 'RMS & Max Forces', 'RMS & Max Displacements']
            for i in range(3):
                self.gaussianPlot.addPlot(row=1, col=i+1)
                plot = self.gaussianPlot.getItem(1, i+1)
                plot.setTitle(title=titles[i])
                if i == 0:
                    c = ['c']
                    x = [0]
                    y = [self.scfenergies]
                else:
                    c = ['r', 'y']
                    x = [0, 0]
                    y = [self.geovalues[2*i-2], self.geovalues[2*i-1]]
                    targety = [self.geotargets[2*i-2], self.geotargets[2*i-1]]
                plot.clear()
                plot.maxData = plot.plot(y[0], symbol='o', symbolPen=c[0], symbolBrush=c[0], pen=c[0], symbolSize=5, pxMode=True, antialias=True, autoDownsample=False)
                plot.highlight=plot.plot(x, [ yy[0] for yy in y ], symbol='o', symbolPen='w', symbolBrush=None, pen=None, symbolSize=15, pxMode=True, antialias=True, autoDownsample=False)
                plot.maxData.sigPointsClicked.connect(self.gausclicked)
                if i > 0:
                    for j in range(2):
                        plot.addLine(y=np.log10(targety[j]), pen=mkPen((255, 255*j, 0, int(255/2)), width=1))
                    plot.RMSData=plot.plot(y[1], symbol='o', symbolPen=c[1], symbolBrush=c[1], pen=c[1], symbolSize=5, pxMode=True, antialias=True, autoDownsample=False)
                    plot.RMSData.sigPointsClicked.connect(self.gausclicked)
                    plot.setLogMode(y=True)
            self.showGauss()
            self.updateView()
            self.statusBar.clearMessage()
            self.statusBar.showMessage('Read molecule from '+filename+'.', 5000)
            self.ZMatModel.dataChanged.connect(self.clearUpdateView)
            if self.natom:
                self.showGaussAction.setEnabled(True)
            if 'vibfreqs' in data.keys():
                self.showFreqAction.setEnabled(True)

                # populate the FreqModel
                self.populateFreqModel()

                self.freqPlot.clear()
                irPlot = self.freqPlot.addPlot(row=1, col=1)
                irPlot.clear()
                minFreq = np.min(self.vibfreqs)
                maxFreq = np.max(self.vibfreqs)
                maxInt = np.max(self.vibirs)
                x = np.sort(np.concatenate([np.linspace(minFreq-100, maxFreq+100, num=1000), self.vibfreqs]))
                y = x*0
                for f,i in zip(self.vibfreqs, self.vibirs):
                    y += lorentzv(x, f, 2*np.pi, i)
                #xy = np.array([np.concatenate([x, np.array(self.vibfreqs)]), np.concatenate([y, np.array(self.vibirs)])]).T
                #xysort = xy[xy[:,0].argsort()]
                irPlot.maxData = irPlot.plot(x, y, antialias=True)
                markers = ErrorBarItem(x=self.vibfreqs, y=self.vibirs, top=maxInt/30, bottom=None, pen='r')
                irPlot.addItem(markers)
                self.showFreq()
                #self.vibdisps = np.append(self.vibdisps, [np.mean(self.vibdisps, axis=0)], axis=0)
                maxt = 100
                vsShifted = np.array([ [ vs + self.vibdisps[i]*np.sin(t*2*np.pi/maxt)/3 for t in range(maxt) ] for i in range(len(self.vibfreqs)) ])
            else:
                self.showFreqAction.setEnabled(False)
                self.freqWidget.hide()

    def showGauss(self):
        self.gaussianPlot.show()

    def showFreq(self):
        self.freqWidget.show()

    # export Zmatrix to csv
    def writeZmat(self):
        zm = model2list(self.ZMatModel)
        filename = self.fileDialog.getSaveFileName(self, 'Save file', expanduser('~')+'/'+getFormula(list(list(zip(*zm))[0]))+'.zmat', '*.zmat;;*.*')
        try:
            filename
        except NameError:
            pass
        else:
            if filename:
                writeOutput(zm, filename)
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Wrote molecule to '+filename+'.', 5000)

    # export XYZ coordinates to csv
    def writeXYZ(self):
        xyz = []
        zm = model2list(self.ZMatModel)
        for i in range(len(v)):
            xyz.append(np.round(v[i], 7).tolist())
            xyz[i][:0] = zm[i][0]
        if len(v) > 0:
            formula = getFormula(list(list(zip(*xyz))[0]))
        else:
            formula = 'moldy_output'
        filename = self.fileDialog.getSaveFileName(self, 'Save file', expanduser('~')+'/'+formula+'.xyz', '*.xyz;;*.*')
        try:
            filename
        except NameError:
            pass
        else:
            if filename:
                writeOutput(xyz, filename)
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Wrote molecule to '+filename+'.', 5000)

    # redraw the 3D molecule in GL widget
    def updateView(self):
        global r
        global c
        global v
        global vs
        global elems
        global nelems
        data = model2list(self.ZMatModel)
        try:
            # create a list with element coordinates
            v = zmat2xyz(data)
        except (AssertionError, IndexError, ZMError):
            pass
        else:
            # clear the screen before redraw
            for item in reversed(self.window.items):
                self.window.removeItem(item)
            # create a second coordinate list 'vs' that is centered in the GL view
            self.atomList = []
            if len(v) > 0:
                shift = np.mean(v, axis=0)
                vs = np.add(v, -shift)
                elems = [ 1 + next((i for i, sublist in enumerate(colors) if row[0] in sublist), -1) for row in data ]
                nelems = len(elems)
                # define molecule radii and colors
                r = []
                c = []
                for i in elems:
                    r.append(elements[i].covalent_radius)
                    c.append(colors[i-1][-1])
                # draw atoms
                for i in range(nelems):
                    addAtom(self.window, i, r, vs, c, fast=self.fast)
                    self.atomList.append([i, self.window.items[-1]])
                #print(self.atomList)
                # draw bonds where appropriate
                combs = list(itertools.combinations(range(nelems), 2))
                bonds = []
                for i in combs:
                    bonds.append(addBond(self.window, i[0], i[1], r, vs, c, fast=self.fast))
                if self.fast:
                    bondedAtoms = set(filter((None).__ne__, flatten(bonds)))
                    for i in set(range(nelems)) - bondedAtoms:
                        addUnbonded(self.window, i, vs, c)
                        self.atomList[i][1]=self.window.items[-1]
                    #print(self.atomList)

                for i in self.highList:
                    self.window.addItem(i[1])
                for i in self.labelList:
                    self.window.addItem(i)
        if len(v) > 1:
            maxDim = float('-inf')
            for dim in v.T:
                span = max(dim)-min(dim)
                if span > maxDim:
                    maxDim = span
        else: maxDim = 2
        self.window.setCameraPosition(distance=maxDim*1.5+1)

    global index
    index = 0
    def updateFreq(self):
        global vsShifted, index, r, c
        index += 1
        index = index % len(vsShifted[0])
        #print(index)
        #print(vsShifted[index])
        for item in reversed(self.window.items):
            self.window.removeItem(item)
        for i in range(nelems):
            addAtom(self.window, i, r, vsShifted[self.freqIndex, index], c, fast=self.fast)
            self.atomList.append([i, self.window.items[-1]])
        combs = itertools.combinations(range(nelems), 2)
        bonds = []
        for i in combs:
            bonds.append(addBond(self.window, i[0], i[1], r, vsShifted[self.freqIndex, index], c, fast=self.fast))
        if self.fast:
            bondedAtoms = set(filter((None).__ne__, flatten(bonds)))
            for i in set(range(nelems)) - bondedAtoms:
                addUnbonded(self.window, i, vsShifted[self.freqIndex, index], c)
                self.atomList[i][1]=self.window.items[-1]

    # detect mouse clicks in GL window and process them
    def eventFilter(self, obj, event):
        if obj == self.window:
            if event.type() == event.MouseButtonPress:
                itms = obj.itemsAt((event.pos().x()-2, event.pos().y()-2, 4, 4))
                if len(itms):
                    self.highlight(obj, [itms[0]])
                elif len(self.atomList) == 0:
                    self.build()
        # also do the default click action
        return super(MainWidget, self).eventFilter(obj, event)

    def ZMatCellClicked(self):
        idxs = sorted(set(idx.row() for idx in self.ZMatTable.selectedIndexes()), reverse=True)
        itms = []
        if self.highList:
            highIdx = list(np.array(self.highList).T[0])
        for idx in idxs:
            if self.highList and idx in highIdx:
                itms.append(self.highList[highIdx.index(idx)][1])
            elif len(self.atomList) > idx:
                itms.append(self.atomList[idx][1])
        self.highlight(self.window, itms)

    def freqCellClicked(self):
        global vsShifted
        self.timer = QTimer()
        self.timer.setInterval(30)
        self.timer.timeout.connect(self.updateFreq)
        idxs = [ idx.row() for idx in self.freqTable.selectedIndexes() ]
        if len(idxs) == 1:
            self.freqIndex = idxs[0]
            self.timer.stop()
            self.timer.timeout.connect(self.updateFreq)
            try:
                self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
            except TypeError:
                pass
            self.timer.start()
        if len(idxs) != 1:
            self.timer.stop()
            self.freqTable.clearSelection()
            self.timer.timeout.disconnect(self.updateFreq)
            self.ZMatModel.dataChanged.connect(self.clearUpdateView)
            self.clearUpdateView()

    def gausclicked(self, item, point):
        itemdata = item.scatter.data
        points = [ row[7] for row in itemdata ]
        idx = points.index(point[0])
        for i in range(3):
            if i == 0:
                x = [idx]
                y = [self.scfenergies[idx]]
            else:
                x = [idx, idx]
                y = [self.geovalues[2*i-2][idx], self.geovalues[2*i-1][idx]]
            plot = self.gaussianPlot.getItem(1, i+1)
            plot.removeItem(plot.highlight)
            plot.highlight=plot.plot(x, y, symbol='o', symbolPen='w', symbolBrush=None, pen=None, symbolSize=15, pxMode=True, antialias=True, autoDownsample=False)
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        self.inp = []
        self.populateZMatModel()
        self.inp = xyz2zmat(self.atomcoords[min(idx, len(self.atomcoords)-1)], self.atomsymbols)
        self.populateZMatModel()
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()

    def highlight(self, obj, itms):
        for itm in itms:
            idx = next((i for i, sublist in enumerate(self.atomList) if itm in sublist), -1)
            #print(idx)
            if idx != -1:
                addAtom(obj, idx, r, vs, c, opt='highlight', fast=self.fast)
                self.highList.append([idx, obj.items[-1]])
                self.ZMatTable.selectRow(idx)
            idx = next((i for i, sublist in enumerate(self.highList) if itm in sublist), -1)
            if idx != -1:
                obj.removeItem(self.highList[idx][1])
                self.highList.pop(idx)
                self.ZMatTable.clearSelection()
        self.statusBar.clearMessage()
        if len(self.highList) > 0:
            idxs = np.asarray(self.highList).T[0]
            selected = []
            for i in idxs:
                selected.append(str(i+1)+str(elements[elems[i]]))
            self.statusBar.showMessage('Selected atoms: '+str(selected), 5000)

    def buildB(self):
        try:
            nelems
        except NameError:
            self.build()
        else:
            if len(self.highList) <= min(nelems, 3):
                diff = min(nelems, 3) - len(self.highList)
                if diff != 0:
                    self.statusBar.clearMessage()
                    self.statusBar.showMessage('Please select '+str(diff)+' more atom(s).')
                else:
                    self.build()
            else:
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Too many atoms selected.')

    def build(self):
        selection = self.periodicTable()
        row = self.ZMatModel.rowCount()
        self.addRow()
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        newSymbol = selection[1]
        newData = [newSymbol]
        if len(self.highList) >= 1:
            newBond = round(2.1*gmean([ elements[e].covalent_radius for e in [selection[0], elems[self.highList[0][0]]] ]), 4)
            newData.append(self.highList[0][0]+1)
            newData.append(newBond)
            if len(self.highList) >= 2:
                newAngle = 109.4712
                newData.append(self.highList[1][0]+1)
                newData.append(newAngle)
                if len(self.highList) == 3:
                    newDihedral = 120.
                    newData.append(self.highList[2][0]+1)
                    newData.append(newDihedral)
        for j, cell in enumerate(newData):
            item = QStandardItem(str(cell))
            self.ZMatModel.setItem(row, j, item)
        self.highList = []
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()

    def measureDistanceB(self):
        sel = len(self.highList)
        if sel <= 2:
            if sel < 2:
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Please select '+str(2-sel)+' more atom(s).')
            else:
                self.measureDistance()
        else:
            self.statusBar.clearMessage()
            self.statusBar.showMessage('Too many atoms selected.')

    def measureDistance(self):
        pts = []
        for pt in self.highList:
            pts.append(vs[pt[0]])
        pts = np.array(pts)
        self.clearHighlights()
        line = gl.GLLinePlotItem(pos=pts, color=(0., 1., 0., 1.), width=3)
        self.window.addItem(line)
        self.labelList.append(line)
        q = pts[1]-pts[0]
        dist = round(np.sqrt(np.dot(q, q)), 4)
        self.window.labelPos.append(np.mean(pts[0:2], axis=0))
        self.window.labelText.append(str(dist))
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Measured distance: '+str(dist)+' A.', 3000)

    def measureAngleB(self):
        sel = len(self.highList)
        if sel <= 3:
            if sel < 3:
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Please select '+str(3-sel)+' more atom(s).')
            else:
                self.measureAngle()
        else:
            self.statusBar.clearMessage()
            self.statusBar.showMessage('Too many atoms selected.')

    def measureAngle(self):
        pts = []
        for pt in self.highList:
            pts.append(vs[pt[0]])
        pts = np.array(pts)
        q = pts[1]-pts[0]
        r = pts[2]-pts[0]
        q_u = q / np.sqrt(np.dot(q, q))
        r_u = r / np.sqrt(np.dot(r, r))
        angle = round(degrees(acos(np.dot(q_u, r_u))), 1)
        srange = np.array([slerp(q, r, t) for t in np.arange(0.0, 13/12, 1/12)])
        self.clearHighlights()
        for i in range(12):
            mesh = gl.MeshData(np.array([[0,0,0],srange[i],srange[i+1]]))
            tri = gl.GLMeshItem(meshdata=mesh, smooth=False, computeNormals=False, color=(0.3, 1., 0.3, 0.5), glOptions=('translucent'))
            tri.translate(pts[0][0], pts[0][1], pts[0][2])
            self.window.addItem(tri)
            self.labelList.append(tri)
        self.window.labelPos.append(slerp(q, r, 0.5)+pts[0])
        self.window.labelText.append(str(angle))
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Measured angle: '+str(angle)+'°', 3000)

    def clearLabels(self):
        self.window.labelPos = []
        self.window.labelText = []
        self.labelList = []
        self.updateView()

    def clearHighlights(self):
        for item in reversed(self.highList):
                self.window.removeItem(item[1])
        self.highList = []
        self.updateView()

    def clearUpdateView(self):
        self.window.labelPos = []
        self.window.labelText = []
        self.labelList = []
        for item in reversed(self.highList):
                self.window.removeItem(item[1])
        self.highList = []
        self.updateView()
        #print(self.highList)

    def fastDraw(self):
        if not self.fast:
            self.fast = True
            self.updateView()

    def normalDraw(self):
        if self.fast:
            self.fast = False
            self.updateView()

    def about(self):
        QMessageBox.about(self, 'About moldy', 'moldy beta 15. 9. 2015')

    def aboutQt(self):
        QMessageBox.aboutQt(self, 'About Qt')

    def deleteGLwidget(self):
        self.window.setParent(None)
        del self.window
Beispiel #17
0
 def resizeColumnsToContents(self):
     QTableView.resizeColumnsToContents(self)
     self.columns_resized = True
Beispiel #18
0
    def __init__(self, parent, modelcls=BooksModel, use_edit_metadata_dialog=True):
        QTableView.__init__(self, parent)
        self.gui = parent
        self.setProperty('highlight_current_item', 150)
        self.row_sizing_done = False
        self.alternate_views = AlternateViews(self)

        if not tweaks['horizontal_scrolling_per_column']:
            self.setHorizontalScrollMode(self.ScrollPerPixel)

        self.setEditTriggers(self.EditKeyPressed)
        if tweaks['doubleclick_on_library_view'] == 'edit_cell':
            self.setEditTriggers(self.DoubleClicked|self.editTriggers())
        elif tweaks['doubleclick_on_library_view'] == 'open_viewer':
            self.setEditTriggers(self.SelectedClicked|self.editTriggers())
            self.doubleClicked.connect(parent.iactions['View'].view_triggered)
        elif tweaks['doubleclick_on_library_view'] == 'edit_metadata':
            # Must not enable single-click to edit, or the field will remain
            # open in edit mode underneath the edit metadata dialog
            if use_edit_metadata_dialog:
                self.doubleClicked.connect(
                        partial(parent.iactions['Edit Metadata'].edit_metadata,
                                checked=False))
            else:
                self.setEditTriggers(self.DoubleClicked|self.editTriggers())

        setup_dnd_interface(self)
        self.setAlternatingRowColors(True)
        self.setSelectionBehavior(self.SelectRows)
        self.setShowGrid(False)
        self.setWordWrap(False)

        self.rating_delegate = RatingDelegate(self)
        self.timestamp_delegate = DateDelegate(self)
        self.pubdate_delegate = PubDateDelegate(self)
        self.last_modified_delegate = DateDelegate(self,
                tweak_name='gui_last_modified_display_format')
        self.languages_delegate = LanguagesDelegate(self)
        self.tags_delegate = CompleteDelegate(self, ',', 'all_tag_names')
        self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True)
        self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True)
        self.series_delegate = TextDelegate(self)
        self.publisher_delegate = TextDelegate(self)
        self.text_delegate = TextDelegate(self)
        self.cc_text_delegate = CcTextDelegate(self)
        self.cc_enum_delegate = CcEnumDelegate(self)
        self.cc_bool_delegate = CcBoolDelegate(self)
        self.cc_comments_delegate = CcCommentsDelegate(self)
        self.cc_template_delegate = CcTemplateDelegate(self)
        self.cc_number_delegate = CcNumberDelegate(self)
        self.display_parent = parent
        self._model = modelcls(self)
        self.setModel(self._model)
        self._model.count_changed_signal.connect(self.do_row_sizing,
                                                 type=Qt.QueuedConnection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setSortingEnabled(True)
        self.selectionModel().currentRowChanged.connect(self._model.current_changed)
        self.preserve_state = partial(PreserveViewState, self)

        # {{{ Column Header setup
        self.can_add_columns = True
        self.was_restored = False
        self.column_header = HeaderView(Qt.Horizontal, self)
        self.setHorizontalHeader(self.column_header)
        self.column_header.setMovable(True)
        self.column_header.setClickable(True)
        self.column_header.sectionMoved.connect(self.save_state)
        self.column_header.setContextMenuPolicy(Qt.CustomContextMenu)
        self.column_header.customContextMenuRequested.connect(self.show_column_header_context_menu)
        self.column_header.sectionResized.connect(self.column_resized, Qt.QueuedConnection)
        self.row_header = HeaderView(Qt.Vertical, self)
        self.setVerticalHeader(self.row_header)
        # }}}

        self._model.database_changed.connect(self.database_changed)
        hv = self.verticalHeader()
        hv.setClickable(True)
        hv.setCursor(Qt.PointingHandCursor)
        self.selected_ids = []
        self._model.about_to_be_sorted.connect(self.about_to_be_sorted)
        self._model.sorting_done.connect(self.sorting_done,
                type=Qt.QueuedConnection)
Beispiel #19
0
    def __init__(self, main):
        def updateDustLimit():
            try:
                self.dustTableModel.updateDustList(
                    self.getSelectedWlt(), str2coin(self.dustLimitText.text()))
                self.beGoneDustButton.setEnabled(
                    len(self.dustTableModel.dustTxOutlist) > 0)
                if self.dustTableModel.wlt:
                    self.lblHeader.setText(
                        tr("""<b>Dust Outputs for Wallet: %s</b>""" %
                           self.dustTableModel.wlt.labelName))
            except NegativeValueError:
                pass
            except TooMuchPrecisionError:
                pass
            except:
                LOGEXCEPT("Unexpected exception")
                pass

        def sendDust():
            try:
                utxiList = []
                for utxo in self.dustTableModel.dustTxOutlist:
                    # The PyCreateAndSignTx method require PyTx and PyBtcAddress objects
                    rawTx = TheBDM.getTxByHash(utxo.getTxHash()).serialize()
                    a160 = CheckHash160(utxo.getRecipientScrAddr())
                    for pyAddr in self.dustTableModel.wlt.addrMap.values():
                        if a160 == pyAddr.getAddr160():
                            pubKey = pyAddr.binPublicKey65.toBinStr()
                            txoIdx = utxo.getTxOutIndex()
                            utxiList.append(
                                UnsignedTxInput(rawTx, txoIdx, None, pubKey))
                            break
                # Make copies, destroy them in the finally clause
                privKeyMap = {}
                for addrObj in self.dustTableModel.wlt.addrMap.values():
                    scrAddr = SCRADDR_P2PKH_BYTE + addrObj.getAddr160()
                    if self.dustTableModel.wlt.useEncryption and self.dustTableModel.wlt.isLocked:
                        # Target wallet is encrypted...
                        unlockdlg = DlgUnlockWallet(self.dustTableModel.wlt,
                                                    self.main, self.main,
                                                    'Unlock Wallet to Import')
                        if not unlockdlg.exec_():
                            QMessageBox.critical(self, 'Wallet is Locked', \
                               'Cannot send dust without unlocking the wallet!', \
                               QMessageBox.Ok)
                            return
                    privKeyMap[scrAddr] = addrObj.binPrivKey32_Plain.copy()
                signedTx = PyCreateAndSignTx(
                    utxiList, [], privKeyMap,
                    SIGHASH_NONE | SIGHASH_ANYONECANPAY)

                print "-------------"
                print binary_to_hex(signedTx.serialize())

                # sock = socket.create_connection(('dust-b-gone.bitcoin.petertodd.org',80))
                # sock.send(signedTx.serialize())
                # sock.send(b'\n')
                # sock.close()

            except socket.error as err:
                QMessageBox.critical(
                    self.main, tr('Negative Value'),
                    tr("""
               Failed to connect to dust-b-gone server: %s""" % err.strerror),
                    QMessageBox.Ok)
            except NegativeValueError:
                QMessageBox.critical(
                    self.main, tr('Negative Value'),
                    tr("""
               You must enter a positive value of at least 0.0000 0001 
               and less than %s for the dust limit.""" % MAX_DUST_LIMIT_STR),
                    QMessageBox.Ok)
            except TooMuchPrecisionError:
                QMessageBox.critical(
                    self.main.main, tr('Too much precision'),
                    tr("""
               Bitcoins can only be specified down to 8 decimal places. 
               The smallest unit of a Bitcoin is 0.0000 0001 BTC. 
               Please enter a dust limit of at least 0.0000 0001 and less than %s."""
                       % MAX_DUST_LIMIT_STR), QMessageBox.Ok)
            finally:
                for scraddr in privKeyMap:
                    privKeyMap[scraddr].destroy()

        self.main = main

        self.lblHeader = QRichLabel(
            tr("""<b>Dust Outputs for Wallet: None Selected</b>"""),
            doWrap=False)
        self.beGoneDustButton = QPushButton("Remove Dust")
        self.beGoneDustButton.setEnabled(False)
        self.main.connect(self.beGoneDustButton, SIGNAL('clicked()'), sendDust)
        topRow = makeHorizFrame([self.lblHeader, 'stretch'])
        secondRow = makeHorizFrame([self.beGoneDustButton, 'stretch'])

        self.dustLimitLabel = QLabel("Max Dust Value (BTC): ")
        self.dustLimitText = QLineEdit()
        self.dustLimitText.setFont(GETFONT('Fixed'))
        self.dustLimitText.setMinimumWidth(
            tightSizeNChar(self.dustLimitText, 6)[0])
        self.dustLimitText.setMaximumWidth(
            tightSizeNChar(self.dustLimitText, 12)[0])
        self.dustLimitText.setAlignment(Qt.AlignRight)
        self.dustLimitText.setText(coin2str(DEFAULT_DUST_LIMIT))
        self.main.connect(self.dustLimitText, SIGNAL('textChanged(QString)'),
                          updateDustLimit)

        limitPanel = makeHorizFrame(
            [self.dustLimitLabel, self.dustLimitText, 'stretch'])

        self.dustTableModel = DustDisplayModel()
        self.dustTableView = QTableView()
        self.dustTableView.setModel(self.dustTableModel)
        self.dustTableView.setSelectionMode(QTableView.NoSelection)
        self.dustTableView.verticalHeader().setDefaultSectionSize(20)
        self.dustTableView.verticalHeader().hide()
        h = tightSizeNChar(self.dustTableView, 1)[1]
        self.dustTableView.setMinimumHeight(2 * (1.3 * h))
        self.dustTableView.setMaximumHeight(10 * (1.3 * h))
        initialColResize(self.dustTableView, [100, .7, .3])

        self.dustTableView.setContextMenuPolicy(Qt.CustomContextMenu)

        self.lblTxioInfo = QRichLabel('')
        self.lblTxioInfo.setMinimumWidth(
            tightSizeNChar(self.lblTxioInfo, 30)[0])

        self.main.connect(self.main.walletsView,
                          SIGNAL('clicked(QModelIndex)'), updateDustLimit)

        self.dustBGoneFrame = makeVertFrame(
            [topRow, secondRow, limitPanel, self.dustTableView, 'stretch'])

        # Now set the scrollarea widget to the layout
        self.tabToDisplay = QScrollArea()
        self.tabToDisplay.setWidgetResizable(True)
        self.tabToDisplay.setWidget(self.dustBGoneFrame)
Beispiel #20
0
class PluginUpdaterDialog(SizePersistedDialog):

    initial_extra_size = QSize(350, 100)
    forum_label_text = _('Plugin homepage')

    def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE):
        SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog')
        self.gui = gui
        self.forum_link = None
        self.zip_url = None
        self.model = None
        self.do_restart = False
        self._initialize_controls()
        self._create_context_menu()

        display_plugins = read_available_plugins()

        if display_plugins:
            self.model = DisplayPluginModel(display_plugins)
            self.proxy_model = DisplayPluginSortFilterModel(self)
            self.proxy_model.setSourceModel(self.model)
            self.plugin_view.setModel(self.proxy_model)
            self.plugin_view.resizeColumnsToContents()
            self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed)
            self.plugin_view.doubleClicked.connect(self.install_button.click)
            self.filter_combo.setCurrentIndex(initial_filter)
            self._select_and_focus_view()
        else:
            error_dialog(self.gui, _('Update Check Failed'),
                        _('Unable to reach the plugin index page.'),
                        det_msg=INDEX_URL, show=True)
            self.filter_combo.setEnabled(False)
        # Cause our dialog size to be restored from prefs or created on first usage
        self.resize_dialog()

    def _initialize_controls(self):
        self.setWindowTitle(_('User plugins'))
        self.setWindowIcon(QIcon(I('plugins/plugin_updater.png')))
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png',
                _('User Plugins'))
        layout.addLayout(title_layout)

        header_layout = QHBoxLayout()
        layout.addLayout(header_layout)
        self.filter_combo = PluginFilterComboBox(self)
        self.filter_combo.setMinimumContentsLength(20)
        self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed)
        header_layout.addWidget(QLabel(_('Filter list of plugins')+':', self))
        header_layout.addWidget(self.filter_combo)
        header_layout.addStretch(10)

        # filter plugins by name
        header_layout.addWidget(QLabel(_('Filter by name')+':', self))
        self.filter_by_name_lineedit = QLineEdit(self)
        self.filter_by_name_lineedit.setText("")
        self.filter_by_name_lineedit.textChanged.connect(self._filter_name_lineedit_changed)

        header_layout.addWidget(self.filter_by_name_lineedit)

        self.plugin_view = QTableView(self)
        self.plugin_view.horizontalHeader().setStretchLastSection(True)
        self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.plugin_view.setAlternatingRowColors(True)
        self.plugin_view.setSortingEnabled(True)
        self.plugin_view.setIconSize(QSize(28, 28))
        layout.addWidget(self.plugin_view)

        details_layout = QHBoxLayout()
        layout.addLayout(details_layout)
        forum_label = self.forum_label = QLabel('')
        forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
        forum_label.linkActivated.connect(self._forum_label_activated)
        details_layout.addWidget(QLabel(_('Description')+':', self), 0, Qt.AlignLeft)
        details_layout.addWidget(forum_label, 1, Qt.AlignRight)

        self.description = QLabel(self)
        self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.description.setMinimumHeight(40)
        self.description.setWordWrap(True)
        layout.addWidget(self.description)

        self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
        self.button_box.rejected.connect(self.reject)
        self.finished.connect(self._finished)
        self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.AcceptRole)
        self.install_button.setToolTip(_('Install the selected plugin'))
        self.install_button.clicked.connect(self._install_clicked)
        self.install_button.setEnabled(False)
        self.configure_button = self.button_box.addButton(' '+_('&Customize plugin ')+' ', QDialogButtonBox.ResetRole)
        self.configure_button.setToolTip(_('Customize the options for this plugin'))
        self.configure_button.clicked.connect(self._configure_clicked)
        self.configure_button.setEnabled(False)
        layout.addWidget(self.button_box)

    def update_forum_label(self):
        txt = ''
        if self.forum_link:
            txt = '<a href="%s">%s</a>' % (self.forum_link, self.forum_label_text)
        self.forum_label.setText(txt)

    def _create_context_menu(self):
        self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.install_action = QAction(QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self)
        self.install_action.setToolTip(_('Install the selected plugin'))
        self.install_action.triggered.connect(self._install_clicked)
        self.install_action.setEnabled(False)
        self.plugin_view.addAction(self.install_action)
        self.history_action = QAction(QIcon(I('chapters.png')), _('Version &History'), self)
        self.history_action.setToolTip(_('Show history of changes to this plugin'))
        self.history_action.triggered.connect(self._history_clicked)
        self.history_action.setEnabled(False)
        self.plugin_view.addAction(self.history_action)
        self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &Forum Thread'), self)
        self.forum_action.triggered.connect(self._forum_label_activated)
        self.forum_action.setEnabled(False)
        self.plugin_view.addAction(self.forum_action)

        sep1 = QAction(self)
        sep1.setSeparator(True)
        self.plugin_view.addAction(sep1)

        self.toggle_enabled_action = QAction(_('Enable/&Disable plugin'), self)
        self.toggle_enabled_action.setToolTip(_('Enable or disable this plugin'))
        self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked)
        self.toggle_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.toggle_enabled_action)
        self.uninstall_action = QAction(_('&Remove plugin'), self)
        self.uninstall_action.setToolTip(_('Uninstall the selected plugin'))
        self.uninstall_action.triggered.connect(self._uninstall_clicked)
        self.uninstall_action.setEnabled(False)
        self.plugin_view.addAction(self.uninstall_action)

        sep2 = QAction(self)
        sep2.setSeparator(True)
        self.plugin_view.addAction(sep2)

        self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self)
        self.donate_enabled_action.setToolTip(_('Donate to the developer of this plugin'))
        self.donate_enabled_action.triggered.connect(self._donate_clicked)
        self.donate_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.donate_enabled_action)

        sep3 = QAction(self)
        sep3.setSeparator(True)
        self.plugin_view.addAction(sep3)

        self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self)
        self.configure_action.setToolTip(_('Customize the options for this plugin'))
        self.configure_action.triggered.connect(self._configure_clicked)
        self.configure_action.setEnabled(False)
        self.plugin_view.addAction(self.configure_action)

    def _finished(self, *args):
        if self.model:
            update_plugins = filter(filter_upgradeable_plugins, self.model.display_plugins)
            self.gui.recalc_update_label(len(update_plugins))

    def _plugin_current_changed(self, current, previous):
        if current.isValid():
            actual_idx = self.proxy_model.mapToSource(current)
            display_plugin = self.model.display_plugins[actual_idx.row()]
            self.description.setText(display_plugin.description)
            self.forum_link = display_plugin.forum_link
            self.zip_url = display_plugin.zip_url
            self.forum_action.setEnabled(bool(self.forum_link))
            self.install_button.setEnabled(display_plugin.is_valid_to_install())
            self.install_action.setEnabled(self.install_button.isEnabled())
            self.uninstall_action.setEnabled(display_plugin.is_installed())
            self.history_action.setEnabled(display_plugin.has_changelog)
            self.configure_button.setEnabled(display_plugin.is_installed())
            self.configure_action.setEnabled(self.configure_button.isEnabled())
            self.toggle_enabled_action.setEnabled(display_plugin.is_installed())
            self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link))
        else:
            self.description.setText('')
            self.forum_link = None
            self.zip_url = None
            self.forum_action.setEnabled(False)
            self.install_button.setEnabled(False)
            self.install_action.setEnabled(False)
            self.uninstall_action.setEnabled(False)
            self.history_action.setEnabled(False)
            self.configure_button.setEnabled(False)
            self.configure_action.setEnabled(False)
            self.toggle_enabled_action.setEnabled(False)
            self.donate_enabled_action.setEnabled(False)
        self.update_forum_label()

    def _donate_clicked(self):
        plugin = self._selected_display_plugin()
        if plugin and plugin.donation_link:
            open_url(QUrl(plugin.donation_link))

    def _select_and_focus_view(self, change_selection=True):
        if change_selection and self.plugin_view.model().rowCount() > 0:
            self.plugin_view.selectRow(0)
        else:
            idx = self.plugin_view.selectionModel().currentIndex()
            self._plugin_current_changed(idx, 0)
        self.plugin_view.setFocus()

    def _filter_combo_changed(self, idx):
        self.filter_by_name_lineedit.setText("")  # clear the name filter text when a different group was selected
        self.proxy_model.set_filter_criteria(idx)
        if idx == FILTER_NOT_INSTALLED:
            self.plugin_view.sortByColumn(5, Qt.DescendingOrder)
        else:
            self.plugin_view.sortByColumn(0, Qt.AscendingOrder)
        self._select_and_focus_view()

    def _filter_name_lineedit_changed(self, text):
        self.proxy_model.set_filter_text(text)  # set the filter text for filterAcceptsRow

    def _forum_label_activated(self):
        if self.forum_link:
            open_url(QUrl(self.forum_link))

    def _selected_display_plugin(self):
        idx = self.plugin_view.selectionModel().currentIndex()
        actual_idx = self.proxy_model.mapToSource(idx)
        return self.model.display_plugins[actual_idx.row()]

    def _uninstall_plugin(self, name_to_remove):
        if DEBUG:
            prints('Removing plugin: ', name_to_remove)
        remove_plugin(name_to_remove)
        # Make sure that any other plugins that required this plugin
        # to be uninstalled first have the requirement removed
        for display_plugin in self.model.display_plugins:
            # Make sure we update the status and display of the
            # plugin we just uninstalled
            if name_to_remove in display_plugin.uninstall_plugins:
                if DEBUG:
                    prints('Removing uninstall dependency for: ', display_plugin.name)
                display_plugin.uninstall_plugins.remove(name_to_remove)
            if display_plugin.name == name_to_remove:
                if DEBUG:
                    prints('Resetting plugin to uninstalled status: ', display_plugin.name)
                display_plugin.installed_version = None
                display_plugin.plugin = None
                display_plugin.uninstall_plugins = []
                if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
                    self.model.refresh_plugin(display_plugin)

    def _uninstall_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(self, _('Are you sure?'), '<p>'+
                   _('Are you sure you want to uninstall the <b>%s</b> plugin?')%display_plugin.name,
                   show_copy_button=False):
            return
        self._uninstall_plugin(display_plugin.name)
        if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
            self.model.reset()
            self._select_and_focus_view()
        else:
            self._select_and_focus_view(change_selection=False)

    def _install_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(self, _('Install %s')%display_plugin.name, '<p>' +
                _('Installing plugins is a <b>security risk</b>. '
                'Plugins can contain a virus/malware. '
                    'Only install it if you got it from a trusted source.'
                    ' Are you sure you want to proceed?'),
                show_copy_button=False):
            return

        if display_plugin.uninstall_plugins:
            uninstall_names = list(display_plugin.uninstall_plugins)
            if DEBUG:
                prints('Uninstalling plugin: ', ', '.join(uninstall_names))
            for name_to_remove in uninstall_names:
                self._uninstall_plugin(name_to_remove)

        plugin_zip_url = display_plugin.zip_url
        if DEBUG:
            prints('Downloading plugin zip attachment: ', plugin_zip_url)
        self.gui.status_bar.showMessage(_('Downloading plugin zip attachment: %s') % plugin_zip_url)
        zip_path = self._download_zip(plugin_zip_url)

        if DEBUG:
            prints('Installing plugin: ', zip_path)
        self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path)

        do_restart = False
        try:
            try:
                plugin = add_plugin(zip_path)
            except NameConflict as e:
                return error_dialog(self.gui, _('Already exists'),
                        unicode(e), show=True)
            # Check for any toolbars to add to.
            widget = ConfigWidget(self.gui)
            widget.gui = self.gui
            widget.check_for_add_to_toolbars(plugin)
            self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name)
            d = info_dialog(self.gui, _('Success'),
                    _('Plugin <b>{0}</b> successfully installed under <b>'
                        ' {1} plugins</b>. You may have to restart calibre '
                        'for the plugin to take effect.').format(plugin.name, plugin.type),
                    show_copy_button=False)
            b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole)
            b.setIcon(QIcon(I('lt.png')))
            d.do_restart = False
            def rf():
                d.do_restart = True
            b.clicked.connect(rf)
            d.set_details('')
            d.exec_()
            b.clicked.disconnect()
            do_restart = d.do_restart

            display_plugin.plugin = plugin
            # We cannot read the 'actual' version information as the plugin will not be loaded yet
            display_plugin.installed_version = display_plugin.available_version
        except:
            if DEBUG:
                prints('ERROR occurred while installing plugin: %s'%display_plugin.name)
                traceback.print_exc()
            error_dialog(self.gui, _('Install Plugin Failed'),
                         _('A problem occurred while installing this plugin.'
                           ' This plugin will now be uninstalled.'
                           ' Please post the error message in details below into'
                           ' the forum thread for this plugin and restart Calibre.'),
                         det_msg=traceback.format_exc(), show=True)
            if DEBUG:
                prints('Due to error now uninstalling plugin: %s'%display_plugin.name)
            remove_plugin(display_plugin.name)
            display_plugin.plugin = None

        display_plugin.uninstall_plugins = []
        if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]:
            self.model.reset()
            self._select_and_focus_view()
        else:
            self.model.refresh_plugin(display_plugin)
            self._select_and_focus_view(change_selection=False)
        if do_restart:
            self.do_restart = True
            self.accept()

    def _history_clicked(self):
        display_plugin = self._selected_display_plugin()
        text = self._read_version_history_html(display_plugin.forum_link)
        if text:
            dlg = VersionHistoryDialog(self, display_plugin.name, text)
            dlg.exec_()
        else:
            return error_dialog(self, _('Version history missing'),
                _('Unable to find the version history for %s')%display_plugin.name,
                show=True)

    def _configure_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.is_customizable():
            return info_dialog(self, _('Plugin not customizable'),
                _('Plugin: %s does not need customization')%plugin.name, show=True)
        from calibre.customize import InterfaceActionBase
        if isinstance(plugin, InterfaceActionBase) and not getattr(plugin,
                'actual_iaction_plugin_loaded', False):
            return error_dialog(self, _('Must restart'),
                    _('You must restart calibre before you can'
                        ' configure the <b>%s</b> plugin')%plugin.name, show=True)
        plugin.do_user_config(self.parent())

    def _toggle_enabled_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.can_be_disabled:
            return error_dialog(self,_('Plugin cannot be disabled'),
                         _('The plugin: %s cannot be disabled')%plugin.name, show=True)
        if is_disabled(plugin):
            enable_plugin(plugin)
        else:
            disable_plugin(plugin)
        self.model.refresh_plugin(display_plugin)

    def _read_version_history_html(self, forum_link):
        br = browser()
        br.set_handle_gzip(True)
        try:
            raw = br.open_novisit(forum_link).read()
            if not raw:
                return None
        except:
            traceback.print_exc()
            return None
        raw = raw.decode('utf-8', errors='replace')
        root = html.fromstring(raw)
        spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]')
        for spoiler_node in spoiler_nodes:
            try:
                if spoiler_node.getprevious() is None:
                    # This is a spoiler node that has been indented using [INDENT]
                    # Need to go up to parent div, then previous node to get header
                    heading_node = spoiler_node.getparent().getprevious()
                else:
                    # This is a spoiler node after a BR tag from the heading
                    heading_node = spoiler_node.getprevious().getprevious()
                if heading_node is None:
                    continue
                if heading_node.text_content().lower().find('version history') != -1:
                    div_node = spoiler_node.xpath('div')[0]
                    text = html.tostring(div_node, method='html', encoding=unicode)
                    return re.sub('<div\s.*?>', '<div>', text)
            except:
                if DEBUG:
                    prints('======= MobileRead Parse Error =======')
                    traceback.print_exc()
                    prints(html.tostring(spoiler_node))
        return None

    def _download_zip(self, plugin_zip_url):
        from calibre.ptempfile import PersistentTemporaryFile
        br = browser(user_agent='%s %s' % (__appname__, __version__))
        raw = br.open_novisit(plugin_zip_url).read()
        with PersistentTemporaryFile('.zip') as pt:
            pt.write(raw)
        return pt.name
   def __init__(self, main):

      def updateDustLimit():
         try:
            self.dustTableModel.updateDustList(self.getSelectedWlt(),
                  str2coin(self.dustLimitText.text()))
            self.beGoneDustButton.setEnabled(len(self.dustTableModel.dustTxOutlist)>0)
            if self.dustTableModel.wlt:
               self.lblHeader.setText(tr("""<b>Dust Outputs for Wallet: %s</b>""" 
                     % self.dustTableModel.wlt.labelName))
         except NegativeValueError:
            pass
         except TooMuchPrecisionError:
            pass
         except:
            LOGEXCEPT("Unexpected exception")
            pass

      def sendDust():
         try:
            utxiList = []
            for utxo in self.dustTableModel.dustTxOutlist:
               # The PyCreateAndSignTx method require PyTx and PyBtcAddress objects
               rawTx = TheBDM.getTxByHash(utxo.getTxHash()).serialize()
               a160 = CheckHash160(utxo.getRecipientScrAddr())
               for pyAddr in self.dustTableModel.wlt.addrMap.values():
                  if a160 == pyAddr.getAddr160():
                     pubKey = pyAddr.binPublicKey65.toBinStr()
                     txoIdx = utxo.getTxOutIndex()
                     utxiList.append(UnsignedTxInput(rawTx, txoIdx, None, pubKey))
                     break
            # Make copies, destroy them in the finally clause
            privKeyMap = {}
            for addrObj in self.dustTableModel.wlt.addrMap.values():
               scrAddr = SCRADDR_P2PKH_BYTE + addrObj.getAddr160()
               if self.dustTableModel.wlt.useEncryption and self.dustTableModel.wlt.isLocked:
                  # Target wallet is encrypted...
                  unlockdlg = DlgUnlockWallet(self.dustTableModel.wlt,
                        self.main, self.main, 'Unlock Wallet to Import')
                  if not unlockdlg.exec_():
                     QMessageBox.critical(self, 'Wallet is Locked', \
                        'Cannot send dust without unlocking the wallet!', \
                        QMessageBox.Ok)
                     return
               privKeyMap[scrAddr] = addrObj.binPrivKey32_Plain.copy()
            signedTx = PyCreateAndSignTx(utxiList,
                  [],
                  privKeyMap, SIGHASH_NONE|SIGHASH_ANYONECANPAY )
            
            print "-------------"
            print binary_to_hex(signedTx.serialize())
            
            # sock = socket.create_connection(('dust-b-gone.bitcoin.petertodd.org',80))
            # sock.send(signedTx.serialize())
            # sock.send(b'\n')
            # sock.close()
                  

         except socket.error as err:
            QMessageBox.critical(self.main, tr('Negative Value'), tr("""
               Failed to connect to dust-b-gone server: %s""" % err.strerror), QMessageBox.Ok)            
         except NegativeValueError:
            QMessageBox.critical(self.main, tr('Negative Value'), tr("""
               You must enter a positive value of at least 0.0000 0001 
               and less than %s for the dust limit.""" % MAX_DUST_LIMIT_STR), QMessageBox.Ok)
         except TooMuchPrecisionError:
            QMessageBox.critical(self.main.main, tr('Too much precision'), tr("""
               Bitcoins can only be specified down to 8 decimal places. 
               The smallest unit of a Groestlcoin is 0.0000 0001 GRS.
               Please enter a dust limit of at least 0.0000 0001 and less than %s.""" % MAX_DUST_LIMIT_STR), QMessageBox.Ok)
         finally:
            for scraddr in privKeyMap:
               privKeyMap[scraddr].destroy()
         
         
         
          
      self.main = main
 
      self.lblHeader    = QRichLabel(tr("""<b>Dust Outputs for Wallet: None Selected</b>"""), doWrap=False)
      self.beGoneDustButton = QPushButton("Remove Dust")
      self.beGoneDustButton.setEnabled(False)
      self.main.connect(self.beGoneDustButton, SIGNAL('clicked()'), sendDust)
      topRow =  makeHorizFrame([self.lblHeader,'stretch'])
      secondRow =  makeHorizFrame([self.beGoneDustButton, 'stretch'])
      
      self.dustLimitLabel = QLabel("Max Dust Value (GRS): ")
      self.dustLimitText = QLineEdit()
      self.dustLimitText.setFont(GETFONT('Fixed'))
      self.dustLimitText.setMinimumWidth(tightSizeNChar(self.dustLimitText, 6)[0])
      self.dustLimitText.setMaximumWidth(tightSizeNChar(self.dustLimitText, 12)[0])
      self.dustLimitText.setAlignment(Qt.AlignRight)
      self.dustLimitText.setText(coin2str(DEFAULT_DUST_LIMIT))
      self.main.connect(self.dustLimitText, SIGNAL('textChanged(QString)'), updateDustLimit)
      
      
      limitPanel = makeHorizFrame([self.dustLimitLabel, self.dustLimitText, 'stretch'])
      
      
      self.dustTableModel = DustDisplayModel()
      self.dustTableView = QTableView()
      self.dustTableView.setModel(self.dustTableModel)
      self.dustTableView.setSelectionMode(QTableView.NoSelection)
      self.dustTableView.verticalHeader().setDefaultSectionSize(20)
      self.dustTableView.verticalHeader().hide()
      h = tightSizeNChar(self.dustTableView, 1)[1]
      self.dustTableView.setMinimumHeight(2 * (1.3 * h))
      self.dustTableView.setMaximumHeight(10 * (1.3 * h))
      initialColResize(self.dustTableView, [100, .7, .3])

      self.dustTableView.setContextMenuPolicy(Qt.CustomContextMenu)

      self.lblTxioInfo = QRichLabel('')
      self.lblTxioInfo.setMinimumWidth(tightSizeNChar(self.lblTxioInfo, 30)[0])
      
      self.main.connect(self.main.walletsView, SIGNAL('clicked(QModelIndex)'), 
                   updateDustLimit)

      self.dustBGoneFrame = makeVertFrame([topRow, secondRow, limitPanel, self.dustTableView, 'stretch'])

      # Now set the scrollarea widget to the layout
      self.tabToDisplay = QScrollArea()
      self.tabToDisplay.setWidgetResizable(True)
      self.tabToDisplay.setWidget(self.dustBGoneFrame)
Beispiel #22
0
class PluginUpdaterDialog(SizePersistedDialog):

    initial_extra_size = QSize(350, 100)

    def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE):
        SizePersistedDialog.__init__(self, gui, "Plugin Updater plugin:plugin updater dialog")
        self.gui = gui
        self.forum_link = None
        self.model = None
        self.do_restart = False
        self._initialize_controls()
        self._create_context_menu()

        display_plugins = read_available_plugins()

        if display_plugins:
            self.model = DisplayPluginModel(display_plugins)
            self.proxy_model = DisplayPluginSortFilterModel(self)
            self.proxy_model.setSourceModel(self.model)
            self.plugin_view.setModel(self.proxy_model)
            self.plugin_view.resizeColumnsToContents()
            self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed)
            self.plugin_view.doubleClicked.connect(self.install_button.click)
            self.filter_combo.setCurrentIndex(initial_filter)
            self._select_and_focus_view()
        else:
            error_dialog(
                self.gui,
                _("Update Check Failed"),
                _("Unable to reach the MobileRead plugins forum index page."),
                det_msg=MR_INDEX_URL,
                show=True,
            )
            self.filter_combo.setEnabled(False)
        # Cause our dialog size to be restored from prefs or created on first usage
        self.resize_dialog()

    def _initialize_controls(self):
        self.setWindowTitle(_("User plugins"))
        self.setWindowIcon(QIcon(I("plugins/plugin_updater.png")))
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        title_layout = ImageTitleLayout(self, "plugins/plugin_updater.png", _("User Plugins"))
        layout.addLayout(title_layout)

        header_layout = QHBoxLayout()
        layout.addLayout(header_layout)
        self.filter_combo = PluginFilterComboBox(self)
        self.filter_combo.setMinimumContentsLength(20)
        self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed)
        header_layout.addWidget(QLabel(_("Filter list of plugins") + ":", self))
        header_layout.addWidget(self.filter_combo)
        header_layout.addStretch(10)

        self.plugin_view = QTableView(self)
        self.plugin_view.horizontalHeader().setStretchLastSection(True)
        self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.plugin_view.setAlternatingRowColors(True)
        self.plugin_view.setSortingEnabled(True)
        self.plugin_view.setIconSize(QSize(28, 28))
        layout.addWidget(self.plugin_view)

        details_layout = QHBoxLayout()
        layout.addLayout(details_layout)
        forum_label = QLabel('<a href="http://www.foo.com/">Plugin Forum Thread</a>', self)
        forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
        forum_label.linkActivated.connect(self._forum_label_activated)
        details_layout.addWidget(QLabel(_("Description") + ":", self), 0, Qt.AlignLeft)
        details_layout.addWidget(forum_label, 1, Qt.AlignRight)

        self.description = QLabel(self)
        self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.description.setMinimumHeight(40)
        self.description.setWordWrap(True)
        layout.addWidget(self.description)

        self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
        self.button_box.rejected.connect(self.reject)
        self.finished.connect(self._finished)
        self.install_button = self.button_box.addButton(_("&Install"), QDialogButtonBox.AcceptRole)
        self.install_button.setToolTip(_("Install the selected plugin"))
        self.install_button.clicked.connect(self._install_clicked)
        self.install_button.setEnabled(False)
        self.configure_button = self.button_box.addButton(
            " " + _("&Customize plugin ") + " ", QDialogButtonBox.ResetRole
        )
        self.configure_button.setToolTip(_("Customize the options for this plugin"))
        self.configure_button.clicked.connect(self._configure_clicked)
        self.configure_button.setEnabled(False)
        layout.addWidget(self.button_box)

    def _create_context_menu(self):
        self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.install_action = QAction(QIcon(I("plugins/plugin_upgrade_ok.png")), _("&Install"), self)
        self.install_action.setToolTip(_("Install the selected plugin"))
        self.install_action.triggered.connect(self._install_clicked)
        self.install_action.setEnabled(False)
        self.plugin_view.addAction(self.install_action)
        self.history_action = QAction(QIcon(I("chapters.png")), _("Version &History"), self)
        self.history_action.setToolTip(_("Show history of changes to this plugin"))
        self.history_action.triggered.connect(self._history_clicked)
        self.history_action.setEnabled(False)
        self.plugin_view.addAction(self.history_action)
        self.forum_action = QAction(QIcon(I("plugins/mobileread.png")), _("Plugin &Forum Thread"), self)
        self.forum_action.triggered.connect(self._forum_label_activated)
        self.forum_action.setEnabled(False)
        self.plugin_view.addAction(self.forum_action)

        sep1 = QAction(self)
        sep1.setSeparator(True)
        self.plugin_view.addAction(sep1)

        self.toggle_enabled_action = QAction(_("Enable/&Disable plugin"), self)
        self.toggle_enabled_action.setToolTip(_("Enable or disable this plugin"))
        self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked)
        self.toggle_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.toggle_enabled_action)
        self.uninstall_action = QAction(_("&Remove plugin"), self)
        self.uninstall_action.setToolTip(_("Uninstall the selected plugin"))
        self.uninstall_action.triggered.connect(self._uninstall_clicked)
        self.uninstall_action.setEnabled(False)
        self.plugin_view.addAction(self.uninstall_action)

        sep2 = QAction(self)
        sep2.setSeparator(True)
        self.plugin_view.addAction(sep2)

        self.donate_enabled_action = QAction(QIcon(I("donate.png")), _("Donate to developer"), self)
        self.donate_enabled_action.setToolTip(_("Donate to the developer of this plugin"))
        self.donate_enabled_action.triggered.connect(self._donate_clicked)
        self.donate_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.donate_enabled_action)

        sep3 = QAction(self)
        sep3.setSeparator(True)
        self.plugin_view.addAction(sep3)

        self.configure_action = QAction(QIcon(I("config.png")), _("&Customize plugin"), self)
        self.configure_action.setToolTip(_("Customize the options for this plugin"))
        self.configure_action.triggered.connect(self._configure_clicked)
        self.configure_action.setEnabled(False)
        self.plugin_view.addAction(self.configure_action)

    def _finished(self, *args):
        if self.model:
            update_plugins = filter(filter_upgradeable_plugins, self.model.display_plugins)
            self.gui.recalc_update_label(len(update_plugins))

    def _plugin_current_changed(self, current, previous):
        if current.isValid():
            actual_idx = self.proxy_model.mapToSource(current)
            display_plugin = self.model.display_plugins[actual_idx.row()]
            self.description.setText(display_plugin.description)
            self.forum_link = display_plugin.forum_link
            self.forum_action.setEnabled(bool(self.forum_link))
            self.install_button.setEnabled(display_plugin.is_valid_to_install())
            self.install_action.setEnabled(self.install_button.isEnabled())
            self.uninstall_action.setEnabled(display_plugin.is_installed())
            self.history_action.setEnabled(display_plugin.has_changelog)
            self.configure_button.setEnabled(display_plugin.is_installed())
            self.configure_action.setEnabled(self.configure_button.isEnabled())
            self.toggle_enabled_action.setEnabled(display_plugin.is_installed())
            self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link))
        else:
            self.description.setText("")
            self.forum_link = None
            self.forum_action.setEnabled(False)
            self.install_button.setEnabled(False)
            self.install_action.setEnabled(False)
            self.uninstall_action.setEnabled(False)
            self.history_action.setEnabled(False)
            self.configure_button.setEnabled(False)
            self.configure_action.setEnabled(False)
            self.toggle_enabled_action.setEnabled(False)
            self.donate_enabled_action.setEnabled(False)

    def _donate_clicked(self):
        plugin = self._selected_display_plugin()
        if plugin and plugin.donation_link:
            open_url(QUrl(plugin.donation_link))

    def _select_and_focus_view(self, change_selection=True):
        if change_selection and self.plugin_view.model().rowCount() > 0:
            self.plugin_view.selectRow(0)
        else:
            idx = self.plugin_view.selectionModel().currentIndex()
            self._plugin_current_changed(idx, 0)
        self.plugin_view.setFocus()

    def _filter_combo_changed(self, idx):
        self.proxy_model.set_filter_criteria(idx)
        if idx == FILTER_NOT_INSTALLED:
            self.plugin_view.sortByColumn(5, Qt.DescendingOrder)
        else:
            self.plugin_view.sortByColumn(0, Qt.AscendingOrder)
        self._select_and_focus_view()

    def _forum_label_activated(self):
        if self.forum_link:
            open_url(QUrl(self.forum_link))

    def _selected_display_plugin(self):
        idx = self.plugin_view.selectionModel().currentIndex()
        actual_idx = self.proxy_model.mapToSource(idx)
        return self.model.display_plugins[actual_idx.row()]

    def _uninstall_plugin(self, name_to_remove):
        if DEBUG:
            prints("Removing plugin: ", name_to_remove)
        remove_plugin(name_to_remove)
        # Make sure that any other plugins that required this plugin
        # to be uninstalled first have the requirement removed
        for display_plugin in self.model.display_plugins:
            # Make sure we update the status and display of the
            # plugin we just uninstalled
            if name_to_remove in display_plugin.uninstall_plugins:
                if DEBUG:
                    prints("Removing uninstall dependency for: ", display_plugin.name)
                display_plugin.uninstall_plugins.remove(name_to_remove)
            if display_plugin.name == name_to_remove:
                if DEBUG:
                    prints("Resetting plugin to uninstalled status: ", display_plugin.name)
                display_plugin.installed_version = None
                display_plugin.plugin = None
                display_plugin.uninstall_plugins = []
                if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
                    self.model.refresh_plugin(display_plugin)

    def _uninstall_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(
            self,
            _("Are you sure?"),
            "<p>" + _("Are you sure you want to uninstall the <b>%s</b> plugin?") % display_plugin.name,
            show_copy_button=False,
        ):
            return
        self._uninstall_plugin(display_plugin.name)
        if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
            self.model.reset()
            self._select_and_focus_view()
        else:
            self._select_and_focus_view(change_selection=False)

    def _install_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(
            self,
            _("Install %s") % display_plugin.name,
            "<p>"
            + _(
                "Installing plugins is a <b>security risk</b>. "
                "Plugins can contain a virus/malware. "
                "Only install it if you got it from a trusted source."
                " Are you sure you want to proceed?"
            ),
            show_copy_button=False,
        ):
            return

        if display_plugin.uninstall_plugins:
            uninstall_names = list(display_plugin.uninstall_plugins)
            if DEBUG:
                prints("Uninstalling plugin: ", ", ".join(uninstall_names))
            for name_to_remove in uninstall_names:
                self._uninstall_plugin(name_to_remove)

        if DEBUG:
            prints("Locating zip file for %s: %s" % (display_plugin.name, display_plugin.forum_link))
        self.gui.status_bar.showMessage(
            _("Locating zip file for %(name)s: %(link)s")
            % dict(name=display_plugin.name, link=display_plugin.forum_link)
        )
        plugin_zip_url = self._read_zip_attachment_url(display_plugin.forum_link)
        if not plugin_zip_url:
            return error_dialog(
                self.gui,
                _("Install Plugin Failed"),
                _("Unable to locate a plugin zip file for <b>%s</b>") % display_plugin.name,
                det_msg=display_plugin.forum_link,
                show=True,
            )

        if DEBUG:
            prints("Downloading plugin zip attachment: ", plugin_zip_url)
        self.gui.status_bar.showMessage(_("Downloading plugin zip attachment: %s") % plugin_zip_url)
        zip_path = self._download_zip(plugin_zip_url)

        if DEBUG:
            prints("Installing plugin: ", zip_path)
        self.gui.status_bar.showMessage(_("Installing plugin: %s") % zip_path)

        do_restart = False
        try:
            try:
                plugin = add_plugin(zip_path)
            except NameConflict as e:
                return error_dialog(self.gui, _("Already exists"), unicode(e), show=True)
            # Check for any toolbars to add to.
            widget = ConfigWidget(self.gui)
            widget.gui = self.gui
            widget.check_for_add_to_toolbars(plugin)
            self.gui.status_bar.showMessage(_("Plugin installed: %s") % display_plugin.name)
            d = info_dialog(
                self.gui,
                _("Success"),
                _(
                    "Plugin <b>{0}</b> successfully installed under <b>"
                    " {1} plugins</b>. You may have to restart calibre "
                    "for the plugin to take effect."
                ).format(plugin.name, plugin.type),
                show_copy_button=False,
            )
            b = d.bb.addButton(_("Restart calibre now"), d.bb.AcceptRole)
            b.setIcon(QIcon(I("lt.png")))
            d.do_restart = False

            def rf():
                d.do_restart = True

            b.clicked.connect(rf)
            d.set_details("")
            d.exec_()
            b.clicked.disconnect()
            do_restart = d.do_restart

            display_plugin.plugin = plugin
            # We cannot read the 'actual' version information as the plugin will not be loaded yet
            display_plugin.installed_version = display_plugin.available_version
        except:
            if DEBUG:
                prints("ERROR occurred while installing plugin: %s" % display_plugin.name)
                traceback.print_exc()
            error_dialog(
                self.gui,
                _("Install Plugin Failed"),
                _(
                    "A problem occurred while installing this plugin."
                    " This plugin will now be uninstalled."
                    " Please post the error message in details below into"
                    " the forum thread for this plugin and restart Calibre."
                ),
                det_msg=traceback.format_exc(),
                show=True,
            )
            if DEBUG:
                prints("Due to error now uninstalling plugin: %s" % display_plugin.name)
                remove_plugin(display_plugin.name)
                display_plugin.plugin = None

        display_plugin.uninstall_plugins = []
        if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]:
            self.model.reset()
            self._select_and_focus_view()
        else:
            self.model.refresh_plugin(display_plugin)
            self._select_and_focus_view(change_selection=False)
        if do_restart:
            self.do_restart = True
            self.accept()

    def _history_clicked(self):
        display_plugin = self._selected_display_plugin()
        text = self._read_version_history_html(display_plugin.forum_link)
        if text:
            dlg = VersionHistoryDialog(self, display_plugin.name, text)
            dlg.exec_()
        else:
            return error_dialog(
                self,
                _("Version history missing"),
                _("Unable to find the version history for %s") % display_plugin.name,
                show=True,
            )

    def _configure_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.is_customizable():
            return info_dialog(
                self, _("Plugin not customizable"), _("Plugin: %s does not need customization") % plugin.name, show=True
            )
        from calibre.customize import InterfaceActionBase

        if isinstance(plugin, InterfaceActionBase) and not getattr(plugin, "actual_iaction_plugin_loaded", False):
            return error_dialog(
                self,
                _("Must restart"),
                _("You must restart calibre before you can" " configure the <b>%s</b> plugin") % plugin.name,
                show=True,
            )
        plugin.do_user_config(self.parent())

    def _toggle_enabled_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.can_be_disabled:
            return error_dialog(
                self, _("Plugin cannot be disabled"), _("The plugin: %s cannot be disabled") % plugin.name, show=True
            )
        if is_disabled(plugin):
            enable_plugin(plugin)
        else:
            disable_plugin(plugin)
        self.model.refresh_plugin(display_plugin)

    def _read_version_history_html(self, forum_link):
        br = browser()
        br.set_handle_gzip(True)
        try:
            raw = br.open_novisit(forum_link).read()
            if not raw:
                return None
        except:
            traceback.print_exc()
            return None
        raw = raw.decode("utf-8", errors="replace")
        root = html.fromstring(raw)
        spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]')
        for spoiler_node in spoiler_nodes:
            try:
                if spoiler_node.getprevious() is None:
                    # This is a spoiler node that has been indented using [INDENT]
                    # Need to go up to parent div, then previous node to get header
                    heading_node = spoiler_node.getparent().getprevious()
                else:
                    # This is a spoiler node after a BR tag from the heading
                    heading_node = spoiler_node.getprevious().getprevious()
                if heading_node is None:
                    continue
                if heading_node.text_content().lower().find("version history") != -1:
                    div_node = spoiler_node.xpath("div")[0]
                    text = html.tostring(div_node, method="html", encoding=unicode)
                    return re.sub("<div\s.*?>", "<div>", text)
            except:
                if DEBUG:
                    prints("======= MobileRead Parse Error =======")
                    traceback.print_exc()
                    prints(html.tostring(spoiler_node))
        return None

    def _read_zip_attachment_url(self, forum_link):
        br = browser()
        br.set_handle_gzip(True)
        try:
            raw = br.open_novisit(forum_link).read()
            if not raw:
                return None
        except:
            traceback.print_exc()
            return None
        raw = raw.decode("utf-8", errors="replace")
        root = html.fromstring(raw)
        attachment_nodes = root.xpath("//fieldset/table/tr/td/a")
        for attachment_node in attachment_nodes:
            try:
                filename = attachment_node.text_content().lower()
                if filename.find(".zip") != -1:
                    full_url = MR_URL + attachment_node.attrib["href"]
                    return full_url
            except:
                if DEBUG:
                    prints("======= MobileRead Parse Error =======")
                    traceback.print_exc()
                    prints(html.tostring(attachment_node))
        return None

    def _download_zip(self, plugin_zip_url):
        from calibre.ptempfile import PersistentTemporaryFile

        br = browser()
        br.set_handle_gzip(True)
        raw = br.open_novisit(plugin_zip_url).read()
        pt = PersistentTemporaryFile(".zip")
        pt.write(raw)
        pt.close()
        return pt.name
Beispiel #23
0
 def __init__(self, parent):    
     QTableView.__init__(self, parent)
     self.setAcceptDrops(True)
class PluginUpdaterDialog(SizePersistedDialog):

    initial_extra_size = QSize(350, 100)

    def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE):
        SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog')
        self.gui = gui
        self.forum_link = None
        self.model = None
        self._initialize_controls()
        self._create_context_menu()

        display_plugins = read_available_plugins()

        if display_plugins:
            self.model = DisplayPluginModel(display_plugins)
            self.proxy_model = DisplayPluginSortFilterModel(self)
            self.proxy_model.setSourceModel(self.model)
            self.plugin_view.setModel(self.proxy_model)
            self.plugin_view.resizeColumnsToContents()
            self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed)
            self.plugin_view.doubleClicked.connect(self.install_button.click)
            self.filter_combo.setCurrentIndex(initial_filter)
            self._select_and_focus_view()
        else:
            error_dialog(self.gui, _('Update Check Failed'),
                        _('Unable to reach the MobileRead plugins forum index page.'),
                        det_msg=MR_INDEX_URL, show=True)
            self.filter_combo.setEnabled(False)
        # Cause our dialog size to be restored from prefs or created on first usage
        self.resize_dialog()

    def _initialize_controls(self):
        self.setWindowTitle(_('User plugins'))
        self.setWindowIcon(QIcon(I('plugins/plugin_updater.png')))
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png',
                _('User Plugins'))
        layout.addLayout(title_layout)

        header_layout = QHBoxLayout()
        layout.addLayout(header_layout)
        self.filter_combo = PluginFilterComboBox(self)
        self.filter_combo.setMinimumContentsLength(20)
        self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed)
        header_layout.addWidget(QLabel(_('Filter list of plugins')+':', self))
        header_layout.addWidget(self.filter_combo)
        header_layout.addStretch(10)

        self.plugin_view = QTableView(self)
        self.plugin_view.horizontalHeader().setStretchLastSection(True)
        self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.plugin_view.setAlternatingRowColors(True)
        self.plugin_view.setSortingEnabled(True)
        self.plugin_view.setIconSize(QSize(28, 28))
        layout.addWidget(self.plugin_view)

        details_layout = QHBoxLayout()
        layout.addLayout(details_layout)
        forum_label = QLabel('<a href="http://www.foo.com/">Plugin Forum Thread</a>', self)
        forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
        forum_label.linkActivated.connect(self._forum_label_activated)
        details_layout.addWidget(QLabel(_('Description')+':', self), 0, Qt.AlignLeft)
        details_layout.addWidget(forum_label, 1, Qt.AlignRight)

        self.description = QLabel(self)
        self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.description.setMinimumHeight(40)
        layout.addWidget(self.description)

        self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
        self.button_box.rejected.connect(self.reject)
        self.finished.connect(self._finished)
        self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.AcceptRole)
        self.install_button.setToolTip(_('Install the selected plugin'))
        self.install_button.clicked.connect(self._install_clicked)
        self.install_button.setEnabled(False)
        self.configure_button = self.button_box.addButton(' '+_('&Customize plugin ')+' ', QDialogButtonBox.ResetRole)
        self.configure_button.setToolTip(_('Customize the options for this plugin'))
        self.configure_button.clicked.connect(self._configure_clicked)
        self.configure_button.setEnabled(False)
        layout.addWidget(self.button_box)

    def _create_context_menu(self):
        self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.install_action = QAction(QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self)
        self.install_action.setToolTip(_('Install the selected plugin'))
        self.install_action.triggered.connect(self._install_clicked)
        self.install_action.setEnabled(False)
        self.plugin_view.addAction(self.install_action)
        self.history_action = QAction(QIcon(I('chapters.png')), _('Version &History'), self)
        self.history_action.setToolTip(_('Show history of changes to this plugin'))
        self.history_action.triggered.connect(self._history_clicked)
        self.history_action.setEnabled(False)
        self.plugin_view.addAction(self.history_action)
        self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &Forum Thread'), self)
        self.forum_action.triggered.connect(self._forum_label_activated)
        self.forum_action.setEnabled(False)
        self.plugin_view.addAction(self.forum_action)

        sep1 = QAction(self)
        sep1.setSeparator(True)
        self.plugin_view.addAction(sep1)

        self.toggle_enabled_action = QAction(_('Enable/&Disable plugin'), self)
        self.toggle_enabled_action.setToolTip(_('Enable or disable this plugin'))
        self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked)
        self.toggle_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.toggle_enabled_action)
        self.uninstall_action = QAction(_('&Remove plugin'), self)
        self.uninstall_action.setToolTip(_('Uninstall the selected plugin'))
        self.uninstall_action.triggered.connect(self._uninstall_clicked)
        self.uninstall_action.setEnabled(False)
        self.plugin_view.addAction(self.uninstall_action)

        sep2 = QAction(self)
        sep2.setSeparator(True)
        self.plugin_view.addAction(sep2)

        self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self)
        self.donate_enabled_action.setToolTip(_('Donate to the developer of this plugin'))
        self.donate_enabled_action.triggered.connect(self._donate_clicked)
        self.donate_enabled_action.setEnabled(False)
        self.plugin_view.addAction(self.donate_enabled_action)

        sep3 = QAction(self)
        sep3.setSeparator(True)
        self.plugin_view.addAction(sep3)

        self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self)
        self.configure_action.setToolTip(_('Customize the options for this plugin'))
        self.configure_action.triggered.connect(self._configure_clicked)
        self.configure_action.setEnabled(False)
        self.plugin_view.addAction(self.configure_action)

    def _finished(self, *args):
        if self.model:
            update_plugins = filter(filter_upgradeable_plugins, self.model.display_plugins)
            self.gui.recalc_update_label(len(update_plugins))

    def _plugin_current_changed(self, current, previous):
        if current.isValid():
            actual_idx = self.proxy_model.mapToSource(current)
            display_plugin = self.model.display_plugins[actual_idx.row()]
            self.description.setText(display_plugin.description)
            self.forum_link = display_plugin.forum_link
            self.forum_action.setEnabled(bool(self.forum_link))
            self.install_button.setEnabled(display_plugin.is_valid_to_install())
            self.install_action.setEnabled(self.install_button.isEnabled())
            self.uninstall_action.setEnabled(display_plugin.is_installed())
            self.history_action.setEnabled(display_plugin.has_changelog)
            self.configure_button.setEnabled(display_plugin.is_installed())
            self.configure_action.setEnabled(self.configure_button.isEnabled())
            self.toggle_enabled_action.setEnabled(display_plugin.is_installed())
            self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link))
        else:
            self.description.setText('')
            self.forum_link = None
            self.forum_action.setEnabled(False)
            self.install_button.setEnabled(False)
            self.install_action.setEnabled(False)
            self.uninstall_action.setEnabled(False)
            self.history_action.setEnabled(False)
            self.configure_button.setEnabled(False)
            self.configure_action.setEnabled(False)
            self.toggle_enabled_action.setEnabled(False)
            self.donate_enabled_action.setEnabled(False)

    def _donate_clicked(self):
        plugin = self._selected_display_plugin()
        if plugin and plugin.donation_link:
            open_url(QUrl(plugin.donation_link))

    def _select_and_focus_view(self, change_selection=True):
        if change_selection and self.plugin_view.model().rowCount() > 0:
            self.plugin_view.selectRow(0)
        else:
            idx = self.plugin_view.selectionModel().currentIndex()
            self._plugin_current_changed(idx, 0)
        self.plugin_view.setFocus()

    def _filter_combo_changed(self, idx):
        self.proxy_model.set_filter_criteria(idx)
        if idx == FILTER_NOT_INSTALLED:
            self.plugin_view.sortByColumn(5, Qt.DescendingOrder)
        else:
            self.plugin_view.sortByColumn(0, Qt.AscendingOrder)
        self._select_and_focus_view()

    def _forum_label_activated(self):
        if self.forum_link:
            open_url(QUrl(self.forum_link))

    def _selected_display_plugin(self):
        idx = self.plugin_view.selectionModel().currentIndex()
        actual_idx = self.proxy_model.mapToSource(idx)
        return self.model.display_plugins[actual_idx.row()]

    def _uninstall_plugin(self, name_to_remove):
        if DEBUG:
            prints('Removing plugin: ', name_to_remove)
        remove_plugin(name_to_remove)
        # Make sure that any other plugins that required this plugin
        # to be uninstalled first have the requirement removed
        for display_plugin in self.model.display_plugins:
            # Make sure we update the status and display of the
            # plugin we just uninstalled
            if name_to_remove in display_plugin.uninstall_plugins:
                if DEBUG:
                    prints('Removing uninstall dependency for: ', display_plugin.name)
                display_plugin.uninstall_plugins.remove(name_to_remove)
            if display_plugin.name == name_to_remove:
                if DEBUG:
                    prints('Resetting plugin to uninstalled status: ', display_plugin.name)
                display_plugin.installed_version = None
                display_plugin.plugin = None
                display_plugin.uninstall_plugins = []
                if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
                    self.model.refresh_plugin(display_plugin)

    def _uninstall_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(self, _('Are you sure?'), '<p>'+
                   _('Are you sure you want to uninstall the <b>%s</b> plugin?')%display_plugin.name,
                   show_copy_button=False):
            return
        self._uninstall_plugin(display_plugin.name)
        if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
            self.model.reset()
            self._select_and_focus_view()
        else:
            self._select_and_focus_view(change_selection=False)

    def _install_clicked(self):
        display_plugin = self._selected_display_plugin()
        if not question_dialog(self, _('Install %s')%display_plugin.name, '<p>' + \
                _('Installing plugins is a <b>security risk</b>. '
                'Plugins can contain a virus/malware. '
                    'Only install it if you got it from a trusted source.'
                    ' Are you sure you want to proceed?'),
                show_copy_button=False):
            return

        if display_plugin.uninstall_plugins:
            uninstall_names = list(display_plugin.uninstall_plugins)
            if DEBUG:
                prints('Uninstalling plugin: ', ', '.join(uninstall_names))
            for name_to_remove in uninstall_names:
                self._uninstall_plugin(name_to_remove)

        if DEBUG:
            prints('Locating zip file for %s: %s'% (display_plugin.name, display_plugin.forum_link))
        self.gui.status_bar.showMessage(
                _('Locating zip file for %(name)s: %(link)s') % dict(
                    name=display_plugin.name, link=display_plugin.forum_link))
        plugin_zip_url = self._read_zip_attachment_url(display_plugin.forum_link)
        if not plugin_zip_url:
            return error_dialog(self.gui, _('Install Plugin Failed'),
                        _('Unable to locate a plugin zip file for <b>%s</b>') % display_plugin.name,
                        det_msg=display_plugin.forum_link, show=True)

        if DEBUG:
            prints('Downloading plugin zip attachment: ', plugin_zip_url)
        self.gui.status_bar.showMessage(_('Downloading plugin zip attachment: %s') % plugin_zip_url)
        zip_path = self._download_zip(plugin_zip_url)

        if DEBUG:
            prints('Installing plugin: ', zip_path)
        self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path)

        try:
            try:
                plugin = add_plugin(zip_path)
            except NameConflict as e:
                return error_dialog(self.gui, _('Already exists'),
                        unicode(e), show=True)
            # Check for any toolbars to add to.
            widget = ConfigWidget(self.gui)
            widget.gui = self.gui
            widget.check_for_add_to_toolbars(plugin)
            self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name)
            info_dialog(self.gui, _('Success'),
                    _('Plugin <b>{0}</b> successfully installed under <b>'
                        ' {1} plugins</b>. You may have to restart calibre '
                        'for the plugin to take effect.').format(plugin.name, plugin.type),
                    show=True, show_copy_button=False)

            display_plugin.plugin = plugin
            # We cannot read the 'actual' version information as the plugin will not be loaded yet
            display_plugin.installed_version = display_plugin.available_version
        except:
            if DEBUG:
                prints('ERROR occurred while installing plugin: %s'%display_plugin.name)
                traceback.print_exc()
            error_dialog(self.gui, _('Install Plugin Failed'),
                         _('A problem occurred while installing this plugin.'
                           ' This plugin will now be uninstalled.'
                           ' Please post the error message in details below into'
                           ' the forum thread for this plugin and restart Calibre.'),
                         det_msg=traceback.format_exc(), show=True)
            if DEBUG:
                prints('Due to error now uninstalling plugin: %s'%display_plugin.name)
                remove_plugin(display_plugin.name)
                display_plugin.plugin = None

        display_plugin.uninstall_plugins = []
        if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]:
            self.model.reset()
            self._select_and_focus_view()
        else:
            self.model.refresh_plugin(display_plugin)
            self._select_and_focus_view(change_selection=False)

    def _history_clicked(self):
        display_plugin = self._selected_display_plugin()
        text = self._read_version_history_html(display_plugin.forum_link)
        if text:
            dlg = VersionHistoryDialog(self, display_plugin.name, text)
            dlg.exec_()
        else:
            return error_dialog(self, _('Version history missing'),
                _('Unable to find the version history for %s')%display_plugin.name,
                show=True)

    def _configure_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.is_customizable():
            return info_dialog(self, _('Plugin not customizable'),
                _('Plugin: %s does not need customization')%plugin.name, show=True)
        from calibre.customize import InterfaceActionBase
        if isinstance(plugin, InterfaceActionBase) and not getattr(plugin,
                'actual_iaction_plugin_loaded', False):
            return error_dialog(self, _('Must restart'),
                    _('You must restart calibre before you can'
                        ' configure the <b>%s</b> plugin')%plugin.name, show=True)
        plugin.do_user_config(self.parent())

    def _toggle_enabled_clicked(self):
        display_plugin = self._selected_display_plugin()
        plugin = display_plugin.plugin
        if not plugin.can_be_disabled:
            return error_dialog(self,_('Plugin cannot be disabled'),
                         _('The plugin: %s cannot be disabled')%plugin.name, show=True)
        if is_disabled(plugin):
            enable_plugin(plugin)
        else:
            disable_plugin(plugin)
        self.model.refresh_plugin(display_plugin)

    def _read_version_history_html(self, forum_link):
        br = browser()
        br.set_handle_gzip(True)
        try:
            raw = br.open_novisit(forum_link).read()
            if not raw:
                return None
        except:
            traceback.print_exc()
            return None
        raw = raw.decode('utf-8', errors='replace')
        root = html.fromstring(raw)
        spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]')
        for spoiler_node in spoiler_nodes:
            try:
                if spoiler_node.getprevious() is None:
                    # This is a spoiler node that has been indented using [INDENT]
                    # Need to go up to parent div, then previous node to get header
                    heading_node = spoiler_node.getparent().getprevious()
                else:
                    # This is a spoiler node after a BR tag from the heading
                    heading_node = spoiler_node.getprevious().getprevious()
                if heading_node is None:
                    continue
                if heading_node.text_content().lower().find('version history') != -1:
                    div_node = spoiler_node.xpath('div')[0]
                    text = html.tostring(div_node, method='html', encoding=unicode)
                    return re.sub('<div\s.*?>', '<div>', text)
            except:
                if DEBUG:
                    prints('======= MobileRead Parse Error =======')
                    traceback.print_exc()
                    prints(html.tostring(spoiler_node))
        return None

    def _read_zip_attachment_url(self, forum_link):
        br = browser()
        br.set_handle_gzip(True)
        try:
            raw = br.open_novisit(forum_link).read()
            if not raw:
                return None
        except:
            traceback.print_exc()
            return None
        raw = raw.decode('utf-8', errors='replace')
        root = html.fromstring(raw)
        attachment_nodes = root.xpath('//fieldset/table/tr/td/a')
        for attachment_node in attachment_nodes:
            try:
                filename = attachment_node.text_content().lower()
                if filename.find('.zip') != -1:
                    full_url = MR_URL + attachment_node.attrib['href']
                    return full_url
            except:
                if DEBUG:
                    prints('======= MobileRead Parse Error =======')
                    traceback.print_exc()
                    prints(html.tostring(attachment_node))
        return None

    def _download_zip(self, plugin_zip_url):
        from calibre.ptempfile import PersistentTemporaryFile
        br = browser()
        br.set_handle_gzip(True)
        raw = br.open_novisit(plugin_zip_url).read()
        pt = PersistentTemporaryFile('.zip')
        pt.write(raw)
        pt.close()
        return pt.name
Beispiel #25
0
 def viewportEvent(self, event):
     if (event.type() == event.ToolTip and not gprefs['book_list_tooltips']):
         return False
     return QTableView.viewportEvent(self, event)
   def __init__(self, main):
      self.searchThread = None
      self.passPhraseFinder = None
      self.isSearchOver = False
      
      def updateResults(resultStr):
         self.resultStr += resultStr
         
      def updateDisplay():
         if len(self.resultStr) > 0:
            self.resultsDisplay.append(self.resultStr)
            self.resultStr = ''
            self.resultsDisplay.moveCursor(QtGui.QTextCursor.End)
            self.resultsDisplay.repaint()
         if self.isSearchOver:
            endSearch()
      
      # Call this from another thread to end the search
      def terminateSearch():
         self.isSearchOver = True
      
      # Call this from the main thread to end the search
      def endSearch():
         self.main.extraHeartbeatAlways.remove(updateDisplay)
         self.searchButton.setEnabled(True)
         self.stopButton.setEnabled(False)
         # If the thread is still searching tell the pass phrase finder to stop
         if self.passPhraseFinder and self.searchThread  and not self.searchThread.isFinished():
            self.passPhraseFinder.isStopped = True
            
      def searchForPassphrase():
         # Get the selected wallet from the main screen
         wlt = self.getSelectedWlt()
         if wlt and not wlt.watchingOnly and wlt.isLocked:
            self.resultStr = ''
            self.passPhraseFinder = PassPhraseFinder(wlt)
            self.resultsDisplay.setText(QString(''))
            self.main.extraHeartbeatAlways.append(updateDisplay)
            if len(self.segOrdStrSet) > 0:
               # From self.segOrdStrList, create a list of lists of indexes that describe the segment orderings to search
               # In other words convert all of the strings in orderings list to lists of integers
               segOrdIntListList = []
               for ordStr in self.segOrdStrSet:
                  # The indexes provided by the users are 1 based, and the list indexes ought to be 0 based
                  segOrdIntListList.append([int(indexStr)-1 for indexStr in ordStr.split(',')])
               self.searchThread = PyBackgroundThread(self.passPhraseFinder.searchForPassPhrase, 
                  [segDef.getSegList() for segDef in self.segDefList], 
                  segOrdIntListList, 
                  updateResults,
                  terminateSearch )
               # Reset the isSearchOver flag
               self.isSearchOver = False
               self.searchThread.start()
               
               # Disable search button adn enabled stop button
               self.stopButton.setEnabled(True)
               self.searchButton.setEnabled(False)
            else:
               QMessageBox.warning(self.main, tr('Invalid'), tr("""
                  There are no valid segment combinations to search.
                  Please add at least one segment and ordering to search."""), QMessageBox.Ok)
         else:
            QMessageBox.warning(self.main, tr('Invalid'), tr("""
               No valid wallet is selected. Please select a locked
               non-watching-only from Available Wallets."""), QMessageBox.Ok)

      def addKnownSegment():
         dlgEnterSegment = DlgEnterSegment(main, main)
         if dlgEnterSegment.exec_():
            segmentText = str(dlgEnterSegment.editSegment.text())
            if len(segmentText)>0:
               self.segDefList.append(KnownSeg(segmentText))
               self.segDefTableModel.updateSegList(self.segDefList)
      
      def addUnknownCaseSegment():
         dlgEnterSegment = DlgEnterSegment(main, main)
         if dlgEnterSegment.exec_():
            segmentText = str(dlgEnterSegment.editSegment.text())
            if len(segmentText)>0:
               self.segDefList.append(UnknownCaseSeg(segmentText))
               self.segDefTableModel.updateSegList(self.segDefList)
               
      def addUnknownOrderSegment():
         dlgEnterSegment = DlgEnterSegment(main, main, isUnknownOrder=True)
         if dlgEnterSegment.exec_():
            segmentText = str(dlgEnterSegment.editSegment.text())
            minLen = int(str(dlgEnterSegment.minSelector.currentText()))
            maxLen = int(str(dlgEnterSegment.maxSelector.currentText()))
            if len(segmentText)>0:
               self.segDefList.append(UnknownSeg(segmentText, minLen, maxLen))
               self.segDefTableModel.updateSegList(self.segDefList)

      def addOrdering():
         if len(self.segDefList) > 0:
            dlgSpecifyOrdering = DlgSpecifyOrdering(main, main, len(self.segDefList))     
            if dlgSpecifyOrdering.exec_():
               self.segOrdStrSet.add(str(dlgSpecifyOrdering.parseOrderingList()).strip('[]'))
               self.updateOrderingListBox()
         else:
            QMessageBox.warning(self.main, tr('Not Ready'), tr("""
               No segments have been entered. You must enter some segments before you can order them."""), QMessageBox.Ok)

      
      self.main = main
      self.segDefList = []
      self.segOrdStrSet = set()
      segmentHeader = QRichLabel(tr("""<b>Build segments for pass phrase search: </b>"""), doWrap=False)
      self.knownButton = QPushButton("Add Known Segment")
      self.unknownCaseButton = QPushButton("Add Unknown Case Segment")
      self.unknownOrderButton = QPushButton("Add Unknown Order Segment")
      self.main.connect(self.knownButton, SIGNAL('clicked()'), addKnownSegment)
      self.main.connect(self.unknownCaseButton, SIGNAL('clicked()'), addUnknownCaseSegment)
      self.main.connect(self.unknownOrderButton, SIGNAL('clicked()'), addUnknownOrderSegment)
      topRow =  makeHorizFrame([segmentHeader, self.knownButton, self.unknownCaseButton, self.unknownOrderButton, 'stretch'])
      
      self.segDefTableModel = SegDefDisplayModel()
      self.segDefTableView = QTableView()
      self.segDefTableView.setModel(self.segDefTableModel)
      self.segDefTableView.setSelectionBehavior(QTableView.SelectRows)
      self.segDefTableView.setSelectionMode(QTableView.SingleSelection)
      self.segDefTableView.verticalHeader().setDefaultSectionSize(20)
      self.segDefTableView.verticalHeader().hide()
      
      h = tightSizeNChar(self.segDefTableView, 1)[1]
      self.segDefTableView.setMinimumHeight(2 * (1.3 * h))
      self.segDefTableView.setMaximumHeight(10 * (1.3 * h))      
      initialColResize(self.segDefTableView, [.1, .2, .4, .1, .1, .1])

      self.segDefTableView.customContextMenuRequested.connect(self.showSegContextMenu)
      self.segDefTableView.setContextMenuPolicy(Qt.CustomContextMenu)
      
      segmentOrderingsHeader = QRichLabel(tr("""<b>Specify orderings for pass phrase search: </b>"""), doWrap=False)
      self.addOrderingButton = QPushButton("Add Ordering")
      
      
      self.main.connect(self.addOrderingButton, SIGNAL('clicked()'), addOrdering)
      orderingButtonPanel = makeHorizFrame([segmentOrderingsHeader, self.addOrderingButton, 'stretch'])

      self.segOrdListBox  = QListWidget()
      
      self.segOrdListBox.customContextMenuRequested.connect(self.showOrdContextMenu)
      self.segOrdListBox.setContextMenuPolicy(Qt.CustomContextMenu)
      
      
      self.searchButton = QPushButton("Search")
      self.main.connect(self.searchButton, SIGNAL('clicked()'), searchForPassphrase)
      self.stopButton = QPushButton("Stop Searching")
      self.stopButton.setEnabled(False)
      self.main.connect(self.stopButton, SIGNAL('clicked()'), endSearch)
      totalSearchLabel = QRichLabel(tr("""<b>Total Passphrase Tries To Search: </b>"""), doWrap=False)
      self.totalSearchTriesDisplay = QLineEdit()
      self.totalSearchTriesDisplay.setReadOnly(True)
      self.totalSearchTriesDisplay.setText(QString('0'))
      self.totalSearchTriesDisplay.setFont(GETFONT('Fixed'))
      self.totalSearchTriesDisplay.setMinimumWidth(tightSizeNChar(self.totalSearchTriesDisplay, 6)[0])
      self.totalSearchTriesDisplay.setMaximumWidth(tightSizeNChar(self.totalSearchTriesDisplay, 12)[0])
      searchButtonPanel = makeHorizFrame([self.searchButton, self.stopButton, 'stretch', totalSearchLabel,  self.totalSearchTriesDisplay])
      
      self.resultsDisplay = QTextEdit()
      self.resultsDisplay.setReadOnly(True)
      self.resultsDisplay.setFont(GETFONT('Fixed'))
      self.resultsDisplay.setMinimumHeight(100)
      self.searchPanel = makeVertFrame([topRow, self.segDefTableView, orderingButtonPanel,
             self.segOrdListBox, searchButtonPanel, self.resultsDisplay, 'stretch'])
      # Now set the scrollarea widget to the layout
      self.tabToDisplay = QScrollArea()
      self.tabToDisplay.setWidgetResizable(True)
      self.tabToDisplay.setWidget(self.searchPanel)
Beispiel #27
0
 def scrollContentsBy(self, dx, dy):
     # Needed as Qt bug causes headerview to not always update when scrolling
     QTableView.scrollContentsBy(self, dx, dy)
     if dy != 0:
         self.column_header.update()
Beispiel #28
0
 def itemTableViewSelectionChanged(self, selected, deselected):
     self.inspector.inspect(self.getCurrentSelected())
     QTableView.currentChanged(self.tableView, selected, deselected)
Beispiel #29
0
 def __init__(self, parent):
     QTableView.__init__(self, parent)
Beispiel #30
0
 def user_sort_requested(self, col, order=Qt.AscendingOrder):
     if col >= len(self.column_map) or col < 0:
         return QTableView.sortByColumn(self, col)
     field = self.column_map[col]
     self.intelligent_sort(field, order == Qt.AscendingOrder)
Beispiel #31
0
    def __init__(self):
        QWidget.__init__(self)

        # define periodic table widget for element selection
        self.periodicTableWidget = widgets.PeriodicTableDialog()

        # initial molecule Zmatrix (can be empty)
        # self.inp = []
        self.inp = [['H'],
        ['O', 1, 0.9],
        ['O', 2, 1.4, 1, 105.],
        ['H', 3, 0.9, 2, 105., 1, 120.]]

        self.atomList = []
        self.highList = []
        self.labelList = []
        self.fast = False

        # define & initialize ZMatModel that will contain Zmatrix data
        self.ZMatModel = QStandardItemModel(len(self.inp), 7, self)
        self.ZMatTable = QTableView(self)
        self.ZMatTable.setModel(self.ZMatModel)
        self.ZMatTable.setFixedWidth(325)
        #self.ZMatTable.installEventFilter(self)
        #self.ZMatModel.installEventFilter(self)
        self.ZMatModel.setHorizontalHeaderLabels(['atom','','bond','','angle','','dihedral'])
        for j, width in enumerate([40, 22, 65, 22, 65, 22, 65]):
            self.ZMatTable.setColumnWidth(j, width)
        # populate the ZMatModel
        self.populateZMatModel()

        #define Menu bar menus and their actions
        self.menuBar = QMenuBar(self)
        fileMenu = self.menuBar.addMenu('&File')
        editMenu = self.menuBar.addMenu('&Edit')
        viewMenu = self.menuBar.addMenu('&View')
        measureMenu = self.menuBar.addMenu('&Measure')
        helpMenu = self.menuBar.addMenu('&Help')

        readZmatAction = QAction('&Read &ZMat', self)
        readZmatAction.setShortcut('Ctrl+O')
        readZmatAction.setStatusTip('Read Zmat from file')
        readZmatAction.triggered.connect(self.readZmat)
        fileMenu.addAction(readZmatAction)

        readXYZAction = QAction('&Read &XYZ', self)
        readXYZAction.setShortcut('Ctrl+Shift+O')
        readXYZAction.setStatusTip('Read XYZ from file')
        readXYZAction.triggered.connect(self.readXYZ)
        fileMenu.addAction(readXYZAction)

        readGaussianAction = QAction('&Read &Gaussian log', self)
        readGaussianAction.setShortcut('Ctrl+G')
        readGaussianAction.setStatusTip('Read Gaussian log file')
        readGaussianAction.triggered.connect(self.readGaussian)
        fileMenu.addAction(readGaussianAction)

        writeZmatAction = QAction('&Write &ZMat', self)
        writeZmatAction.setShortcut('Ctrl+S')
        writeZmatAction.setStatusTip('Write Zmat to file')
        writeZmatAction.triggered.connect(self.writeZmat)
        fileMenu.addAction(writeZmatAction)

        writeXYZAction = QAction('&Write &XYZ', self)
        writeXYZAction.setShortcut('Ctrl+Shift+S')
        writeXYZAction.setStatusTip('Write XYZ from file')
        writeXYZAction.triggered.connect(self.writeXYZ)
        fileMenu.addAction(writeXYZAction)

        exitAction = QAction('&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(qApp.quit)
        fileMenu.addAction(exitAction)

        addRowAction = QAction('&Add &row', self)
        addRowAction.setShortcut('Ctrl+R')
        addRowAction.setStatusTip('Add row to ZMatrix')
        addRowAction.triggered.connect(self.addRow)
        editMenu.addAction(addRowAction)

        deleteRowAction = QAction('&Delete &row', self)
        deleteRowAction.setShortcut('Ctrl+Shift+R')
        deleteRowAction.setStatusTip('Delete row from ZMatrix')
        deleteRowAction.triggered.connect(self.deleteRow)
        editMenu.addAction(deleteRowAction)

        addAtomAction = QAction('&Add &atom', self)
        addAtomAction.setShortcut('Ctrl+A')
        addAtomAction.setStatusTip('Add atom to ZMatrix')
        addAtomAction.triggered.connect(self.buildB)
        editMenu.addAction(addAtomAction)

        drawModeMenu = QMenu('Draw mode', self)
        viewMenu.addMenu(drawModeMenu)
        fastDrawAction = QAction('&Fast draw', self)
        fastDrawAction.triggered.connect(self.fastDraw)
        normalDrawAction = QAction('&Normal draw', self)
        normalDrawAction.triggered.connect(self.normalDraw)
        drawModeMenu.addAction(normalDrawAction)
        drawModeMenu.addAction(fastDrawAction)

        clearHighlightsAction = QAction('&Clear selection', self)
        clearHighlightsAction.setShortcut('Ctrl+C')
        clearHighlightsAction.setStatusTip('Clear highlighted atoms')
        clearHighlightsAction.triggered.connect(self.clearHighlights)
        viewMenu.addAction(clearHighlightsAction)

        clearLabelsAction = QAction('&Clear labels', self)
        clearLabelsAction.setShortcut('Ctrl+Alt+C')
        clearLabelsAction.setStatusTip('Clear labels')
        clearLabelsAction.triggered.connect(self.clearLabels)
        viewMenu.addAction(clearLabelsAction)

        clearUpdateViewAction = QAction('&Clear selection and labels', self)
        clearUpdateViewAction.setShortcut('Ctrl+Shift+C')
        clearUpdateViewAction.setStatusTip('Clear highlighted atoms and labels')
        clearUpdateViewAction.triggered.connect(self.clearUpdateView)
        viewMenu.addAction(clearUpdateViewAction)

        self.showGaussAction = QAction('Show &Gaussian geometry optimization', self)
        self.showGaussAction.setShortcut('Ctrl+G')
        self.showGaussAction.setStatusTip('Show Gaussian geometry optimization plots for energy, force and displacement.')
        self.showGaussAction.setEnabled(False)
        self.showGaussAction.triggered.connect(self.showGauss)
        viewMenu.addAction(self.showGaussAction)
        self.showFreqAction = QAction('Show &IR frequency plot', self)
        self.showFreqAction.setShortcut('Ctrl+I')
        self.showFreqAction.setStatusTip('Show Gaussian calculated IR frequency plot.')
        self.showFreqAction.setEnabled(False)
        self.showFreqAction.triggered.connect(self.showFreq)
        viewMenu.addAction(self.showFreqAction)

        measureDistanceAction = QAction('&Measure &distance', self)
        measureDistanceAction.setShortcut('Ctrl+D')
        measureDistanceAction.setStatusTip('Measure distance between two atoms')
        measureDistanceAction.triggered.connect(self.measureDistanceB)
        measureMenu.addAction(measureDistanceAction)

        measureAngleAction = QAction('&Measure &angle', self)
        measureAngleAction.setShortcut('Ctrl+Shift+D')
        measureAngleAction.setStatusTip('Measure angle between three atoms')
        measureAngleAction.triggered.connect(self.measureAngleB)
        measureMenu.addAction(measureAngleAction)

        aboutAction = QAction('&About', self)
        aboutAction.setStatusTip('About this program...')
        aboutAction.triggered.connect(self.about)
        helpMenu.addAction(aboutAction)

        aboutQtAction = QAction('&About Qt', self)
        aboutQtAction.setStatusTip('About Qt...')
        aboutQtAction.triggered.connect(self.aboutQt)
        helpMenu.addAction(aboutQtAction)

        # define GL widget that displays the 3D molecule model
        self.window = widgets.MyGLView()
        self.window.installEventFilter(self)
        self.window.setMinimumSize(500, 500)
        #self.window.setBackgroundColor((50, 0, 10))
        self.updateView()

        self.gaussianPlot = GraphicsLayoutWidget()
        self.gaussianPlot.resize(750, 250)
        self.gaussianPlot.setWindowTitle('Gaussian geometry optimization')
        #self.gaussianPlot.setAspectLocked(True)
        #self.gaussianPlot.addLayout(rowspan=3, colspan=1)

        self.FreqModel = QStandardItemModel(1, 3, self)
        self.freqTable = QTableView(self)
        self.freqTable.setModel(self.FreqModel)
        self.freqTable.setMinimumWidth(240)
        self.freqTable.installEventFilter(self)
        self.FreqModel.installEventFilter(self)
        self.FreqModel.setHorizontalHeaderLabels(['Frequency','IR Intensity','Raman Intensity'])
        for j, width in enumerate([80, 80, 80]):
            self.freqTable.setColumnWidth(j, width)

        self.freqWidget = QWidget()
        self.freqWidget.setWindowTitle('IR frequency plot & table')
        self.freqWidget.resize(800, 400)
        self.freqWidget.layout = QHBoxLayout(self.freqWidget)
        self.freqWidget.layout.setSpacing(1)
        self.freqWidget.layout.setContentsMargins(1, 1, 1, 1)
        self.freqPlot = GraphicsLayoutWidget()
        self.freqWidget.layout.addWidget(self.freqPlot)
        self.freqWidget.layout.addWidget(self.freqTable)
        self.freqTable.clicked.connect(self.freqCellClicked)

        # define other application parts
        self.statusBar = QStatusBar(self)
        self.fileDialog = QFileDialog(self)

        # define application layout
        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(1)
        self.layout.setContentsMargins(1, 1, 1, 1)
        self.layout1 = QHBoxLayout()
        self.layout1.setSpacing(1)
        self.layout1.addWidget(self.ZMatTable)
        self.layout1.addWidget(self.window)
        self.layout.addWidget(self.menuBar)
        self.layout.addLayout(self.layout1)
        self.layout.addWidget(self.statusBar)

        self.adjustSize()
        self.setWindowTitle('Moldy')
        iconPath = 'icon.png'
        icon = QIcon(iconPath)
        icon.addFile(iconPath, QSize(16, 16))
        icon.addFile(iconPath, QSize(24, 24))
        icon.addFile(iconPath, QSize(32, 32))
        icon.addFile(iconPath, QSize(48, 48))
        icon.addFile(iconPath, QSize(256, 256))
        self.setWindowIcon(icon)

        # start monitoring changes in the ZMatModel
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
Beispiel #32
0
 def resizeColumnsToContents(self):
     QTableView.resizeColumnsToContents(self)
     self.columns_resized = True
Beispiel #33
0
 def currentChanged(self, current, previous):
     ret = QTableView.currentChanged(self, current, previous)
     self.show_details(current)
     return ret
Beispiel #34
0
 def viewportEvent(self, event):
     if (event.type() == event.ToolTip
             and not gprefs['book_list_tooltips']):
         return False
     return QTableView.viewportEvent(self, event)
Beispiel #35
0
 def currentChanged(self, current, previous):
     ret = QTableView.currentChanged(self, current, previous)
     self.show_details(current)
     return ret
Beispiel #36
0
    def __init__(self,
                 parent,
                 modelcls=BooksModel,
                 use_edit_metadata_dialog=True):
        QTableView.__init__(self, parent)
        self.setProperty('highlight_current_item', 150)
        self.row_sizing_done = False

        if not tweaks['horizontal_scrolling_per_column']:
            self.setHorizontalScrollMode(self.ScrollPerPixel)

        self.setEditTriggers(self.EditKeyPressed)
        if tweaks['doubleclick_on_library_view'] == 'edit_cell':
            self.setEditTriggers(self.DoubleClicked | self.editTriggers())
        elif tweaks['doubleclick_on_library_view'] == 'open_viewer':
            self.setEditTriggers(self.SelectedClicked | self.editTriggers())
            self.doubleClicked.connect(parent.iactions['View'].view_triggered)
        elif tweaks['doubleclick_on_library_view'] == 'edit_metadata':
            # Must not enable single-click to edit, or the field will remain
            # open in edit mode underneath the edit metadata dialog
            if use_edit_metadata_dialog:
                self.doubleClicked.connect(
                    partial(parent.iactions['Edit Metadata'].edit_metadata,
                            checked=False))
            else:
                self.setEditTriggers(self.DoubleClicked | self.editTriggers())

        self.drag_allowed = True
        self.setDragEnabled(True)
        self.setDragDropOverwriteMode(False)
        self.setDragDropMode(self.DragDrop)
        self.drag_start_pos = None
        self.setAlternatingRowColors(True)
        self.setSelectionBehavior(self.SelectRows)
        self.setShowGrid(False)
        self.setWordWrap(False)

        self.rating_delegate = RatingDelegate(self)
        self.timestamp_delegate = DateDelegate(self)
        self.pubdate_delegate = PubDateDelegate(self)
        self.last_modified_delegate = DateDelegate(
            self, tweak_name='gui_last_modified_display_format')
        self.languages_delegate = LanguagesDelegate(self)
        self.tags_delegate = CompleteDelegate(self, ',', 'all_tag_names')
        self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names',
                                                 True)
        self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom',
                                                  True)
        self.series_delegate = TextDelegate(self)
        self.publisher_delegate = TextDelegate(self)
        self.text_delegate = TextDelegate(self)
        self.cc_text_delegate = CcTextDelegate(self)
        self.cc_enum_delegate = CcEnumDelegate(self)
        self.cc_bool_delegate = CcBoolDelegate(self)
        self.cc_comments_delegate = CcCommentsDelegate(self)
        self.cc_template_delegate = CcTemplateDelegate(self)
        self.cc_number_delegate = CcNumberDelegate(self)
        self.display_parent = parent
        self._model = modelcls(self)
        self.setModel(self._model)
        self._model.count_changed_signal.connect(self.do_row_sizing,
                                                 type=Qt.QueuedConnection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setSortingEnabled(True)
        self.selectionModel().currentRowChanged.connect(
            self._model.current_changed)
        self.preserve_state = partial(PreserveViewState, self)

        # {{{ Column Header setup
        self.can_add_columns = True
        self.was_restored = False
        self.column_header = HeaderView(Qt.Horizontal, self)
        self.setHorizontalHeader(self.column_header)
        self.column_header.setMovable(True)
        self.column_header.setClickable(True)
        self.column_header.sectionMoved.connect(self.save_state)
        self.column_header.setContextMenuPolicy(Qt.CustomContextMenu)
        self.column_header.customContextMenuRequested.connect(
            self.show_column_header_context_menu)
        self.column_header.sectionResized.connect(self.column_resized,
                                                  Qt.QueuedConnection)
        self.row_header = HeaderView(Qt.Vertical, self)
        self.setVerticalHeader(self.row_header)
        # }}}

        self._model.database_changed.connect(self.database_changed)
        hv = self.verticalHeader()
        hv.setClickable(True)
        hv.setCursor(Qt.PointingHandCursor)
        self.selected_ids = []
        self._model.about_to_be_sorted.connect(self.about_to_be_sorted)
        self._model.sorting_done.connect(self.sorting_done,
                                         type=Qt.QueuedConnection)