示例#1
0
文件: nxtDB.py 项目: l8orre/XG8
class WalletDB_Runner(QtCore.QRunnable):
    """- This is what needs to be put into the QThreadpool """
    #nxtApi = nxtApi
    
    def __init__(self, emitter , sessMan,   DB,   consLogger = None , walletLogger = None, ): #emitter,
        super(QtCore.QRunnable, self).__init__()
        self.consLogger = consLogger
        self.walletLogger = walletLogger

        self.walletDBConn = DB[0]
        self.walletDBCur = DB[1]
        self.emitter = emitter

        self.walletDB_pollTime = 25000
        self.walletDBTimer = QTimer()
        QObject.connect(self.walletDBTimer, SIGNAL("timeout()"),  self.walletDBTimer_CB)


    def run(self,):
        self.blockDBTimer.start(self.blockDB_pollTime)

    def run(self,):
        self.walletDBTimer.start(self.walletDB_pollTime)
        
    def walletDBTimer_CB(self,):
        pass
        # this is  a heartbeat for now!
        self.consLogger.info('walletDB heartbeat')
示例#2
0
文件: jobs.py 项目: 089git/calibre
class DetailView(QDialog, Ui_Dialog):  # {{{

    def __init__(self, parent, job):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowTitle(job.description)
        self.job = job
        self.html_view = (hasattr(job, 'html_details') and not getattr(job,
            'ignore_html_details', False))
        if self.html_view:
            self.log.setVisible(False)
        else:
            self.tb.setVisible(False)
        self.next_pos = 0
        self.update()
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update)
        self.timer.start(1000)
        v = self.log.verticalScrollBar()
        v.setValue(v.maximum())

    def update(self):
        if self.html_view:
            html = self.job.html_details
            if len(html) > self.next_pos:
                self.next_pos = len(html)
                self.tb.setHtml(
                    '<pre style="font-family:monospace">%s</pre>'%html)
        else:
            f = self.job.log_file
            f.seek(self.next_pos)
            more = f.read()
            self.next_pos = f.tell()
            if more:
                self.log.appendPlainText(more.decode('utf-8', 'replace'))
示例#3
0
class ScreenCapture(QObject):

    newScreen = pyqtSignal("PyQt_PyObject")
    newTransformedScreen = pyqtSignal("PyQt_PyObject")

    def __init__(self, coords, fps=1):
        QObject.__init__(self)
        self.fps = fps
        self.coords = coords

        self.captureClock = QTimer()
        self.captureClock.timeout.connect(self.captureScreen)
        self.captureClock.start(1000 / self.fps)

        self.screenBuffer = Queue.deque(maxlen=self.fps * 2)
        self.transformedScreenBuffer = Queue.deque(maxlen=self.fps * 2)

    def captureScreen(self):
        screenshot = QPixmap.grabWindow(QApplication.desktop().winId(),
                                        self.coords.x(), self.coords.y(),
                                        self.coords.width(),
                                        self.coords.height())
        self.screenBuffer.append(screenshot.toImage().copy())
        self.transformedScreenBuffer.append(
            ModifiedImage(self.screenBuffer[-1]).get())

        self.newScreen.emit(self.screenBuffer)
        self.newTransformedScreen.emit(self.transformedScreenBuffer)
示例#4
0
文件: jobs.py 项目: sss/calibre
class DetailView(QDialog, Ui_Dialog):  # {{{
    def __init__(self, parent, job):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowTitle(job.description)
        self.job = job
        self.html_view = (hasattr(job, 'html_details')
                          and not getattr(job, 'ignore_html_details', False))
        if self.html_view:
            self.log.setVisible(False)
        else:
            self.tb.setVisible(False)
        self.next_pos = 0
        self.update()
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update)
        self.timer.start(1000)
        v = self.log.verticalScrollBar()
        v.setValue(v.maximum())

    def update(self):
        if self.html_view:
            html = self.job.html_details
            if len(html) > self.next_pos:
                self.next_pos = len(html)
                self.tb.setHtml('<pre style="font-family:monospace">%s</pre>' %
                                html)
        else:
            f = self.job.log_file
            f.seek(self.next_pos)
            more = f.read()
            self.next_pos = f.tell()
            if more:
                self.log.appendPlainText(more.decode('utf-8', 'replace'))
示例#5
0
    class SliderControl(QObject):
        """This class implements a slider control for a colormap"""

        def __init__(self, name, value, minval, maxval, step, format="%s: %.1f"):
            QObject.__init__(self)
            self.name, self.value, self.minval, self.maxval, self.step, self.format = \
                name, value, minval, maxval, step, format
            self._default = value
            self._wlabel = None

        def makeControlWidgets(self, parent, gridlayout, row, column):
            toprow = QWidget(parent)
            gridlayout.addWidget(toprow, row * 2, column)
            top_lo = QHBoxLayout(toprow)
            top_lo.setContentsMargins(0, 0, 0, 0)
            self._wlabel = QLabel(self.format % (self.name, self.value), toprow)
            top_lo.addWidget(self._wlabel)
            self._wreset = QToolButton(toprow)
            self._wreset.setText("reset")
            self._wreset.setToolButtonStyle(Qt.ToolButtonTextOnly)
            self._wreset.setAutoRaise(True)
            self._wreset.setEnabled(self.value != self._default)
            QObject.connect(self._wreset, SIGNAL("clicked()"), self._resetValue)
            top_lo.addWidget(self._wreset)
            self._wslider = QwtSlider(parent)
            # This works around a stupid bug in QwtSliders -- see comments on histogram zoom wheel above
            self._wslider_timer = QTimer(parent)
            self._wslider_timer.setSingleShot(True)
            self._wslider_timer.setInterval(500)
            QObject.connect(self._wslider_timer, SIGNAL("timeout()"), self.setValue)
            gridlayout.addWidget(self._wslider, row * 2 + 1, column)
            self._wslider.setRange(self.minval, self.maxval)
            self._wslider.setStep(self.step)
            self._wslider.setValue(self.value)
            self._wslider.setTracking(False)
            QObject.connect(self._wslider, SIGNAL("valueChanged(double)"), self.setValue)
            QObject.connect(self._wslider, SIGNAL("sliderMoved(double)"), self._previewValue)

        def _resetValue(self):
            self._wslider.setValue(self._default)
            self.setValue(self._default)

        def setValue(self, value=None, notify=True):
            # only update widgets if already created
            self.value = value
            if self._wlabel is not None:
                if value is None:
                    self.value = value = self._wslider.value()
                self._wreset.setEnabled(value != self._default)
                self._wlabel.setText(self.format % (self.name, self.value))
                # stop timer if being called to finalize the change in value
                if notify:
                    self._wslider_timer.stop()
                    self.emit(SIGNAL("valueChanged"), self.value)

        def _previewValue(self, value):
            self.setValue(notify=False)
            self._wslider_timer.start(500)
            self.emit(SIGNAL("valueMoved"), self.value)
示例#6
0
class MainWidget(QWidget):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.__timer = QTimer(self)
        self.background = QLabel(self)
        self.background.setPixmap(QPixmap(_PATH + os.sep + "assets" + os.sep + "background.png"))

    def startMain(self, main, delay=25):
        self.__timer.timeout.connect(main)
        self.__timer.start(delay)
示例#7
0
class MainWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.__timer = QTimer(self)
        self.background = QLabel(self)
        self.background.setPixmap(
            QPixmap(_PATH + os.sep + "assets" + os.sep + "background.png"))

    def startMain(self, main, delay=25):
        self.__timer.timeout.connect(main)
        self.__timer.start(delay)
示例#8
0
class GarbageCollector(QObject):

    '''
    Disable automatic garbage collection and instead collect manually
    every INTERVAL milliseconds.

    This is done to ensure that garbage collection only happens in the GUI
    thread, as otherwise Qt can crash.
    '''

    INTERVAL = 5000

    def __init__(self, parent, debug=False):
        QObject.__init__(self, parent)
        self.debug = debug

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.check)

        self.threshold = gc.get_threshold()
        gc.disable()
        self.timer.start(self.INTERVAL)
        #gc.set_debug(gc.DEBUG_SAVEALL)

    def check(self):
        #return self.debug_cycles()
        l0, l1, l2 = gc.get_count()
        if self.debug:
            print ('gc_check called:', l0, l1, l2)
        if l0 > self.threshold[0]:
            num = gc.collect(0)
            if self.debug:
                print ('collecting gen 0, found:', num, 'unreachable')
            if l1 > self.threshold[1]:
                num = gc.collect(1)
                if self.debug:
                    print ('collecting gen 1, found:', num, 'unreachable')
                if l2 > self.threshold[2]:
                    num = gc.collect(2)
                    if self.debug:
                        print ('collecting gen 2, found:', num, 'unreachable')

    def debug_cycles(self):
        gc.collect()
        for obj in gc.garbage:
            print (obj, repr(obj), type(obj))
示例#9
0
class GarbageCollector(QObject):

    '''
    Disable automatic garbage collection and instead collect manually
    every INTERVAL milliseconds.

    This is done to ensure that garbage collection only happens in the GUI
    thread, as otherwise Qt can crash.
    '''

    INTERVAL = 5000

    def __init__(self, parent, debug=False):
        QObject.__init__(self, parent)
        self.debug = debug

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.check)

        self.threshold = gc.get_threshold()
        gc.disable()
        self.timer.start(self.INTERVAL)
        #gc.set_debug(gc.DEBUG_SAVEALL)

    def check(self):
        #return self.debug_cycles()
        l0, l1, l2 = gc.get_count()
        if self.debug:
            print ('gc_check called:', l0, l1, l2)
        if l0 > self.threshold[0]:
            num = gc.collect(0)
            if self.debug:
                print ('collecting gen 0, found:', num, 'unreachable')
            if l1 > self.threshold[1]:
                num = gc.collect(1)
                if self.debug:
                    print ('collecting gen 1, found:', num, 'unreachable')
                if l2 > self.threshold[2]:
                    num = gc.collect(2)
                    if self.debug:
                        print ('collecting gen 2, found:', num, 'unreachable')

    def debug_cycles(self):
        gc.collect()
        for obj in gc.garbage:
            print (obj, repr(obj), type(obj))
示例#10
0
class MacListWidget(QtGui.QWidget, Ui_MacListWidget):

    activeStateChanged = pyqtSignal(int, Device)

    def __init__(self, device, parent=None):
        super(MacListWidget, self).__init__(parent)
        self.setupUi(self)

        self._cancelEmit = False

        self.device = device
        self.device.dataChanged.connect(self.refresh)
        self.refresh()
        self.lastUpdate = time.time()

        self.checkActive.stateChanged.connect(self.activeStateChangedEmit)

        self.timer = QTimer()
        self.timer.timeout.connect(self.updateAge)
        self.timer.start(1000)

    def refresh(self):
        self.labelMac.setText("{0}".format(self.device.mac))
        self.labelRSSI.setText("{0} dB".format(self.device.rssi))
        self.labelBatt.setText("{0:04X} {1:0.2f} V".format(
            self.device.batt, self.device.volts))
        self.labelCount.setText("{0}".format(self.device.count))
        self.labelAge.setText("--")
        self.lastUpdate = time.time()

    def updateAge(self):
        age = time.time() - self.lastUpdate
        self.labelAge.setText("{0:0.0f} s".format(age))

    def activeStateChangedEmit(self, newState):
        if self._cancelEmit == False:
            self.activeStateChanged.emit(newState, self.device)

    def isActive(self):
        return self.checkActive.isChecked()

    def setActiveState(self, newState, cancelEmit=False):
        self._cancelEmit = cancelEmit
        self.checkActive.setCheckState(newState)
        self._cancelEmit = False
class MacListWidget(QtGui.QWidget, Ui_MacListWidget):
    
    activeStateChanged = pyqtSignal(int, Device)
    
    def __init__(self, device, parent = None):
        super(MacListWidget, self).__init__(parent)
        self.setupUi(self)
        
        self._cancelEmit = False
        
        self.device = device
        self.device.dataChanged.connect(self.refresh)
        self.refresh()
        self.lastUpdate = time.time()
        
        self.checkActive.stateChanged.connect(self.activeStateChangedEmit)
        
        self.timer = QTimer();
        self.timer.timeout.connect(self.updateAge)
        self.timer.start(1000)
        
    def refresh(self):
        self.labelMac.setText("{0}".format(self.device.mac))
        self.labelRSSI.setText("{0} dB".format(self.device.rssi))
        self.labelBatt.setText("{0:04X} {1:0.2f} V".format(self.device.batt,self.device.volts))
        self.labelCount.setText("{0}".format(self.device.count))
        self.labelAge.setText("--")
        self.lastUpdate = time.time()
        
    def updateAge(self):
        age = time.time() - self.lastUpdate     
        self.labelAge.setText("{0:0.0f} s".format(age))
        
    def activeStateChangedEmit(self, newState):
        if self._cancelEmit == False:
            self.activeStateChanged.emit(newState, self.device)
        
    def isActive(self):
        return self.checkActive.isChecked()
    
    def setActiveState(self, newState, cancelEmit = False):
        self._cancelEmit = cancelEmit
        self.checkActive.setCheckState(newState)
        self._cancelEmit = False
示例#12
0
class MoveMonitor(QObject):
    def __init__(self, worker, rq, callback, parent):
        QObject.__init__(self, parent)
        self.worker = worker
        self.rq = rq
        self.callback = callback
        self.parent = parent

        self.worker.start()
        self.dialog = ProgressDialog(_('Moving library...'),
                                     '',
                                     max=self.worker.total,
                                     parent=parent)
        self.dialog.button_box.setDisabled(True)
        self.dialog.setModal(True)
        self.dialog.show()
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.check)
        self.timer.start(200)

    def check(self):
        if self.worker.is_alive():
            self.update()
        else:
            self.timer.stop()
            self.dialog.hide()
            if self.worker.failed:
                error_dialog(self.parent,
                             _('Failed to move library'),
                             _('Failed to move library'),
                             self.worker.details,
                             show=True)
                return self.callback(None)
            else:
                return self.callback(self.worker.to)

    def update(self):
        try:
            title = self.rq.get_nowait()[-1]
            self.dialog.value += 1
            self.dialog.set_msg(_('Copied') + ' ' + title)
        except Empty:
            pass
示例#13
0
class MoveMonitor(QObject):

    def __init__(self, worker, rq, callback, parent):
        QObject.__init__(self, parent)
        self.worker = worker
        self.rq = rq
        self.callback = callback
        self.parent = parent

        self.worker.start()
        self.dialog = ProgressDialog(_('Moving library...'), '',
                max=self.worker.total, parent=parent)
        self.dialog.button_box.setDisabled(True)
        self.dialog.setModal(True)
        self.dialog.show()
        self.timer = QTimer(self)
        self.connect(self.timer, SIGNAL('timeout()'), self.check)
        self.timer.start(200)

    def check(self):
        if self.worker.is_alive():
            self.update()
        else:
            self.timer.stop()
            self.dialog.hide()
            if self.worker.failed:
                error_dialog(self.parent, _('Failed to move library'),
                    _('Failed to move library'), self.worker.details, show=True)
                return self.callback(None)
            else:
                return self.callback(self.worker.to)

    def update(self):
        try:
            title = self.rq.get_nowait()[-1]
            self.dialog.value += 1
            self.dialog.set_msg(_('Copied') + ' '+title)
        except Empty:
            pass
示例#14
0
class AboutWindow(QDialog):
    def __init__(self, html, width=600, timeout=None):
        QDialog.__init__(self)
        self.setMinimumWidth(width)
        self.setObjectName("About NanoEngineer-1")
        TextEditLayout = QVBoxLayout(self)
        TextEditLayout.setSpacing(0)
        TextEditLayout.setMargin(0)
        self.text_edit = QTextEdit(self)
        self.text_edit.setHtml(html)
        self.text_edit.setReadOnly(True)
        self.text_edit.setWordWrapMode(QTextOption.WordWrap)
        TextEditLayout.addWidget(self.text_edit)
        self.quit_button = QPushButton("OK")
        TextEditLayout.addWidget(self.quit_button)
        self.connect(self.quit_button, SIGNAL("clicked()"), self.close)
        if timeout is not None:
            self.qt = QTimer()
            self.qt.setInterval(1000 * timeout)
            self.qt.start()
            self.connect(self.qt, SIGNAL("timeout()"), self.close)
        self.show()
        self.exec_()
示例#15
0
class SpriteAnimation(object):

    def __init__(self, image_path, sprite_width, sprite_height, label):
        pixmap = QPixmap(image_path)
        width, height = pixmap.width(), pixmap.height()
        self.pixmaps = []
        for x in range(0, width, sprite_width):
            for y in range(0, height, sprite_height):
                self.pixmaps.append(pixmap.copy(x, y, sprite_width, sprite_height))

        self._current_frame = 0
        self.label = label

    def play(self, interval = 100):
        self._timer = QTimer(interval=interval, timeout=self._animation_step)
        self._timer.start()

    def _animation_step(self):
        self.label.setPixmap(self.pixmaps[self._current_frame])
        self.label.update()
        self._current_frame += 1
        if self._current_frame >= len(self.pixmaps):
            self._current_frame = 0
示例#16
0
class AboutWindow(QDialog):
    def __init__(self, html, width=600, timeout=None):
        QDialog.__init__(self)
        self.setMinimumWidth(width)
        self.setObjectName("About NanoEngineer-1")
        TextEditLayout = QVBoxLayout(self)
        TextEditLayout.setSpacing(0)
        TextEditLayout.setMargin(0)
        self.text_edit = QTextEdit(self)
        self.text_edit.setHtml(html)
        self.text_edit.setReadOnly(True)
        self.text_edit.setWordWrapMode(QTextOption.WordWrap)
        TextEditLayout.addWidget(self.text_edit)
        self.quit_button = QPushButton("OK")
        TextEditLayout.addWidget(self.quit_button)
        self.connect(self.quit_button, SIGNAL("clicked()"), self.close)
        if timeout is not None:
            self.qt = QTimer()
            self.qt.setInterval(1000*timeout)
            self.qt.start()
            self.connect(self.qt, SIGNAL("timeout()"), self.close)
        self.show()
        self.exec_()
示例#17
0
    def __init__(self, parent, updateinterval):
        SurfacePlot.__init__(self, parent)
        # fonts
        family = QCoreApplication.instance().font().family()
        if 'Verdana' in QFontDatabase().families():
            family = 'Verdana'
        family = 'Courier'

        self.setTitleFont(family, 16, QFont.Bold)
            
        self.setRotation(30, 0, 15)
        self.setShift(0.1, 0, 0)
        self.setZoom(0.8)

        self.coordinates().setNumberFont(family, 8)
        
        axes = self.coordinates().axes # alias
        for axis in axes:
            axis.setMajors(7)
            axis.setMinors(4)

        axes[X1].setLabelString("x")
        axes[Y1].setLabelString("y")
        axes[Z1].setLabelString("z")
        axes[X2].setLabelString("x")
        axes[Y2].setLabelString("y")
        axes[Z2].setLabelString("z")
        axes[X3].setLabelString("x")
        axes[Y3].setLabelString("y")
        axes[Z3].setLabelString("z")
        axes[X4].setLabelString("x")
        axes[Y4].setLabelString("y")
        axes[Z4].setLabelString("z")

        timer = QTimer(self)
        self.connect(timer, SIGNAL('timeout()'), self.rotate)
        timer.start(updateinterval)
示例#18
0
    def setupWidget(self, context):
        super(MkzGui, self).__init__(context)
        # Give QObjects reasonable names
        self.setObjectName(self.gui_object_name)

        # Create QWidget
        self._widget = QWidget()

        # Get path to UI file which should be in the "resource" folder of this package
        ui_file = os.path.join(rospkg.RosPack().get_path(self.package_name),
                               'resource', self.ui_filename)
        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file, self._widget)
        # Give QObjects reasonable names
        self._widget.setObjectName(self.gui_object_name)
        # Add widget to the user interface
        context.add_widget(self._widget)

        # Set up a timer to update the GUI
        update_timer = QTimer(self._widget)
        update_timer.setInterval(self.update_period)
        update_timer.setSingleShot(False)
        update_timer.timeout.connect(lambda: self.updateGuiCallback())
        update_timer.start()
示例#19
0
 def setupTimer(self):
     timer = QTimer(self)
     timer.timeout.connect(self.count)
     timer.setInterval(2000)
     timer.start()
示例#20
0
class Scheduler(QObject):

    INTERVAL = 1 # minutes

    delete_old_news = pyqtSignal(object)
    start_recipe_fetch = pyqtSignal(object)

    def __init__(self, parent, db):
        QObject.__init__(self, parent)
        self.internet_connection_failed = False
        self._parent = parent
        self.no_internet_msg = _('Cannot download news as no internet connection '
                'is active')
        self.no_internet_dialog = d = error_dialog(self._parent,
                self.no_internet_msg, _('No internet connection'),
                show_copy_button=False)
        d.setModal(False)

        self.recipe_model = RecipeModel()
        self.db = db
        self.lock = QMutex(QMutex.Recursive)
        self.download_queue = set([])

        self.news_menu = QMenu()
        self.news_icon = QIcon(I('news.png'))
        self.scheduler_action = QAction(QIcon(I('scheduler.png')), _('Schedule news download'), self)
        self.news_menu.addAction(self.scheduler_action)
        self.scheduler_action.triggered[bool].connect(self.show_dialog)
        self.cac = QAction(QIcon(I('user_profile.png')), _('Add a custom news source'), self)
        self.cac.triggered[bool].connect(self.customize_feeds)
        self.news_menu.addAction(self.cac)
        self.news_menu.addSeparator()
        self.all_action = self.news_menu.addAction(
                _('Download all scheduled news sources'),
                self.download_all_scheduled)

        self.timer = QTimer(self)
        self.timer.start(int(self.INTERVAL * 60 * 1000))
        self.timer.timeout.connect(self.check)
        self.oldest = gconf['oldest_news']
        QTimer.singleShot(5 * 1000, self.oldest_check)

    def database_changed(self, db):
        self.db = db

    def oldest_check(self):
        if self.oldest > 0:
            delta = timedelta(days=self.oldest)
            try:
                ids = list(self.db.tags_older_than(_('News'),
                    delta, must_have_authors=['calibre']))
            except:
                # Happens if library is being switched
                ids = []
            if ids:
                if ids:
                    self.delete_old_news.emit(ids)
        QTimer.singleShot(60 * 60 * 1000, self.oldest_check)


    def show_dialog(self, *args):
        self.lock.lock()
        try:
            d = SchedulerDialog(self.recipe_model)
            d.download.connect(self.download_clicked)
            d.exec_()
            gconf['oldest_news'] = self.oldest = d.old_news.value()
            d.break_cycles()
        finally:
            self.lock.unlock()

    def customize_feeds(self, *args):
        from calibre.gui2.dialogs.user_profiles import UserProfiles
        d = UserProfiles(self._parent, self.recipe_model)
        d.exec_()
        d.break_cycles()

    def do_download(self, urn):
        self.lock.lock()
        try:
            account_info = self.recipe_model.get_account_info(urn)
            customize_info = self.recipe_model.get_customize_info(urn)
            recipe = self.recipe_model.recipe_from_urn(urn)
            un = pw = None
            if account_info is not None:
                un, pw = account_info
            add_title_tag, custom_tags, keep_issues = customize_info
            script = self.recipe_model.get_recipe(urn)
            pt = PersistentTemporaryFile('_builtin.recipe')
            pt.write(script)
            pt.close()
            arg = {
                    'username': un,
                    'password': pw,
                    'add_title_tag':add_title_tag,
                    'custom_tags':custom_tags,
                    'recipe':pt.name,
                    'title':recipe.get('title',''),
                    'urn':urn,
                    'keep_issues':keep_issues
                   }
            self.download_queue.add(urn)
            self.start_recipe_fetch.emit(arg)
        finally:
            self.lock.unlock()

    def recipe_downloaded(self, arg):
        self.lock.lock()
        try:
            self.recipe_model.update_last_downloaded(arg['urn'])
            self.download_queue.remove(arg['urn'])
        finally:
            self.lock.unlock()

    def recipe_download_failed(self, arg):
        self.lock.lock()
        try:
            self.recipe_model.update_last_downloaded(arg['urn'])
            self.download_queue.remove(arg['urn'])
        finally:
            self.lock.unlock()

    def download_clicked(self, urn):
        if urn is not None:
            return self.download(urn)
        for urn in self.recipe_model.scheduled_urns():
            if not self.download(urn):
                break

    def download_all_scheduled(self):
        self.download_clicked(None)

    def has_internet_connection(self):
        if not internet_connected():
            if not self.internet_connection_failed:
                self.internet_connection_failed = True
                if self._parent.is_minimized_to_tray:
                    self._parent.status_bar.show_message(self.no_internet_msg,
                            5000)
                elif not self.no_internet_dialog.isVisible():
                    self.no_internet_dialog.show()
            return False
        self.internet_connection_failed = False
        if self.no_internet_dialog.isVisible():
            self.no_internet_dialog.hide()
        return True

    def download(self, urn):
        self.lock.lock()
        if not self.has_internet_connection():
            return False
        doit = urn not in self.download_queue
        self.lock.unlock()
        if doit:
            self.do_download(urn)
        return True

    def check(self):
        recipes = self.recipe_model.get_to_be_downloaded_recipes()
        for urn in recipes:
            if not self.download(urn):
                # No internet connection, we will try again in a minute
                break
示例#21
0
class ImageControlDialog(QDialog):
    def __init__(self, parent, rc, imgman):
        """An ImageControlDialog is initialized with a parent widget, a RenderControl object,
        and an ImageManager object"""
        QDialog.__init__(self, parent)
        image = rc.image
        self.setWindowTitle("%s: Colour Controls" % image.name)
        self.setWindowIcon(pixmaps.colours.icon())
        self.setModal(False)
        self.image = image
        self._rc = rc
        self._imgman = imgman
        self._currier = PersistentCurrier()

        # init internal state
        self._prev_range = self._display_range = None, None
        self._hist = None
        self._geometry = None

        # create layouts
        lo0 = QVBoxLayout(self)
        #    lo0.setContentsMargins(0,0,0,0)

        # histogram plot
        whide = self.makeButton("Hide", self.hide, width=128)
        whide.setShortcut(Qt.Key_F9)
        lo0.addWidget(Separator(self, "Histogram and ITF", extra_widgets=[whide]))
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        self._histplot = QwtPlot(self)
        self._histplot.setAutoDelete(False)
        lo1.addWidget(self._histplot, 1)
        lo2 = QHBoxLayout()
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setSpacing(2)
        lo0.addLayout(lo2)
        lo0.addLayout(lo1)
        self._wautozoom = QCheckBox("autozoom", self)
        self._wautozoom.setChecked(True)
        self._wautozoom.setToolTip("""<P>If checked, then the histrogram plot will zoom in automatically when
      you narrow the current intensity range.</P>""")
        self._wlogy = QCheckBox("log Y", self)
        self._wlogy.setChecked(True)
        self._ylogscale = True
        self._wlogy.setToolTip(
            """<P>If checked, a log-scale Y axis is used for the histogram plot instead of a linear one.""")
        QObject.connect(self._wlogy, SIGNAL("toggled(bool)"), self._setHistLogScale)
        self._whistunzoom = self.makeButton("", self._unzoomHistogram, icon=pixmaps.full_range.icon())
        self._whistzoomout = self.makeButton("-", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(.1)))
        self._whistzoomin = self.makeButton("+", self._currier.curry(self._zoomHistogramByFactor, math.sqrt(10)))
        self._whistzoomin.setToolTip("""<P>Click to zoom into the histogram plot by one step. This does not
      change the current intensity range.</P>""")
        self._whistzoomout.setToolTip("""<P>Click to zoom out of the histogram plot by one step. This does not
      change the current intensity range.</P>""")
        self._whistunzoom.setToolTip("""<P>Click to reset the histogram plot back to its full extent.
      This does not change the current intensity range.</P>""")
        self._whistzoom = QwtWheel(self)
        self._whistzoom.setOrientation(Qt.Horizontal)
        self._whistzoom.setMaximumWidth(80)
        self._whistzoom.setRange(10, 0)
        self._whistzoom.setStep(0.1)
        self._whistzoom.setTickCnt(30)
        self._whistzoom.setTracking(False)
        QObject.connect(self._whistzoom, SIGNAL("valueChanged(double)"), self._zoomHistogramFinalize)
        QObject.connect(self._whistzoom, SIGNAL("sliderMoved(double)"), self._zoomHistogramPreview)
        self._whistzoom.setToolTip("""<P>Use this wheel control to zoom in/out of the histogram plot.
      This does not change the current intensity range.
      Note that the zoom wheel should also respond to your mouse wheel, if you have one.</P>""")
        # This works around a stupid bug in QwtSliders -- when using the mousewheel, only sliderMoved() signals are emitted,
        # with no final  valueChanged(). If we want to do a fast preview of something on sliderMoved(), and a "slow" final
        # step on valueChanged(), we're in trouble. So we start a timer on sliderMoved(), and if the timer expires without
        # anything else happening, do a valueChanged().
        # Here we use a timer to call zoomHistogramFinalize() w/o an argument.
        self._whistzoom_timer = QTimer(self)
        self._whistzoom_timer.setSingleShot(True)
        self._whistzoom_timer.setInterval(500)
        QObject.connect(self._whistzoom_timer, SIGNAL("timeout()"), self._zoomHistogramFinalize)
        # set same size for all buttons and controls
        width = 24
        for w in self._whistunzoom, self._whistzoomin, self._whistzoomout:
            w.setMinimumSize(width, width)
            w.setMaximumSize(width, width)
        self._whistzoom.setMinimumSize(80, width)
        self._wlab_histpos_text = "(hover here for help)"
        self._wlab_histpos = QLabel(self._wlab_histpos_text, self)
        self._wlab_histpos.setToolTip("""
      <P>The plot shows a histogram of either the full image or its selected subset
      (as per the "Data subset" section below).</P>
      <P>The current intensity range is indicated by the grey box
      in the plot.</P>
      <P>Use the left mouse button to change the low intensity limit, and the right
      button (on Macs, use Ctrl-click) to change the high limit.</P>
      <P>Use Shift with the left mouse button to zoom into an area of the histogram,
      or else use the "zoom wheel" control or the plus/minus toolbuttons above the histogram to zoom in or out.
      To zoom back out to the full extent of the histogram, click on the rightmost button above the histogram.</P>
      """)
        lo2.addWidget(self._wlab_histpos, 1)
        lo2.addWidget(self._wautozoom)
        lo2.addWidget(self._wlogy, 0)
        lo2.addWidget(self._whistzoomin, 0)
        lo2.addWidget(self._whistzoom, 0)
        lo2.addWidget(self._whistzoomout, 0)
        lo2.addWidget(self._whistunzoom, 0)
        self._zooming_histogram = False

        sliced_axes = rc.slicedAxes()
        dprint(1, "sliced axes are", sliced_axes)
        self._stokes_axis = None

        # subset indication
        lo0.addWidget(Separator(self, "Data subset"))
        # sliced axis selectors
        self._wslicers = []
        if sliced_axes:
            lo1 = QHBoxLayout()
            lo1.setContentsMargins(0, 0, 0, 0)
            lo1.setSpacing(2)
            lo0.addLayout(lo1)
            lo1.addWidget(QLabel("Current slice:  ", self))
            for i, (iextra, name, labels) in enumerate(sliced_axes):
                lo1.addWidget(QLabel("%s:" % name, self))
                if name == "STOKES":
                    self._stokes_axis = iextra
                # add controls
                wslicer = QComboBox(self)
                self._wslicers.append(wslicer)
                wslicer.addItems(labels)
                wslicer.setToolTip("""<P>Selects current slice along the %s axis.</P>""" % name)
                wslicer.setCurrentIndex(self._rc.currentSlice()[iextra])
                QObject.connect(wslicer, SIGNAL("activated(int)"), self._currier.curry(self._rc.changeSlice, iextra))
                lo2 = QVBoxLayout()
                lo1.addLayout(lo2)
                lo2.setContentsMargins(0, 0, 0, 0)
                lo2.setSpacing(0)
                wminus = QToolButton(self)
                wminus.setArrowType(Qt.UpArrow)
                QObject.connect(wminus, SIGNAL("clicked()"), self._currier.curry(self._rc.incrementSlice, iextra, 1))
                if i == 0:
                    wminus.setShortcut(Qt.SHIFT + Qt.Key_F7)
                elif i == 1:
                    wminus.setShortcut(Qt.SHIFT + Qt.Key_F8)
                wplus = QToolButton(self)
                wplus.setArrowType(Qt.DownArrow)
                QObject.connect(wplus, SIGNAL("clicked()"), self._currier.curry(self._rc.incrementSlice, iextra, -1))
                if i == 0:
                    wplus.setShortcut(Qt.Key_F7)
                elif i == 1:
                    wplus.setShortcut(Qt.Key_F8)
                wminus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
                wplus.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
                sz = QSize(12, 8)
                wminus.setMinimumSize(sz)
                wplus.setMinimumSize(sz)
                wminus.resize(sz)
                wplus.resize(sz)
                lo2.addWidget(wminus)
                lo2.addWidget(wplus)
                lo1.addWidget(wslicer)
                lo1.addSpacing(5)
            lo1.addStretch(1)
        # subset indicator
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo1.setSpacing(2)
        lo0.addLayout(lo1)
        self._wlab_subset = QLabel("Subset: xxx", self)
        self._wlab_subset.setToolTip("""<P>This indicates the current data subset to which the histogram
      and the stats given here apply. Use the "Reset to" control on the right to change the
      current subset and recompute the histogram and stats.</P>""")
        lo1.addWidget(self._wlab_subset, 1)

        self._wreset_full = self.makeButton("\u2192 full", self._rc.setFullSubset)
        lo1.addWidget(self._wreset_full)
        if sliced_axes:
            #      if self._stokes_axis is not None and len(sliced_axes)>1:
            #        self._wreset_stokes = self.makeButton(u"\u21920Stokes",self._rc.setFullSubset)
            self._wreset_slice = self.makeButton("\u2192 slice", self._rc.setSliceSubset)
            lo1.addWidget(self._wreset_slice)
        else:
            self._wreset_slice = None

        # min/max controls
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo0.addLayout(lo1, 0)
        self._wlab_stats = QLabel(self)
        lo1.addWidget(self._wlab_stats, 0)
        self._wmore_stats = self.makeButton("more...", self._showMeanStd)
        self._wlab_stats.setMinimumHeight(self._wmore_stats.height())
        lo1.addWidget(self._wmore_stats, 0)
        lo1.addStretch(1)

        # intensity controls
        lo0.addWidget(Separator(self, "Intensity mapping"))
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo1.setSpacing(2)
        lo0.addLayout(lo1, 0)
        self._range_validator = FloatValidator(self)
        self._wrange = QLineEdit(self), QLineEdit(self)
        self._wrange[0].setToolTip("""<P>This is the low end of the intensity range.</P>""")
        self._wrange[1].setToolTip("""<P>This is the high end of the intensity range.</P>""")
        for w in self._wrange:
            w.setValidator(self._range_validator)
            QObject.connect(w, SIGNAL("editingFinished()"), self._changeDisplayRange)
        lo1.addWidget(QLabel("low:", self), 0)
        lo1.addWidget(self._wrange[0], 1)
        self._wrangeleft0 = self.makeButton("\u21920", self._setZeroLeftLimit, width=32)
        self._wrangeleft0.setToolTip("""<P>Click this to set the low end of the intensity range to 0.</P>""")
        lo1.addWidget(self._wrangeleft0, 0)
        lo1.addSpacing(8)
        lo1.addWidget(QLabel("high:", self), 0)
        lo1.addWidget(self._wrange[1], 1)
        lo1.addSpacing(8)
        self._wrange_full = self.makeButton(None, self._setHistDisplayRange, icon=pixmaps.intensity_graph.icon())
        lo1.addWidget(self._wrange_full)
        self._wrange_full.setToolTip(
            """<P>Click this to reset the intensity range to the current extent of the histogram plot.</P>""")
        # add menu for display range
        range_menu = QMenu(self)
        wrange_menu = QToolButton(self)
        wrange_menu.setText("Reset to")
        wrange_menu.setToolTip("""<P>Use this to reset the intensity range to various pre-defined settings.</P>""")
        lo1.addWidget(wrange_menu)
        self._qa_range_full = range_menu.addAction(pixmaps.full_range.icon(), "Full subset",
                                                   self._rc.resetSubsetDisplayRange)
        self._qa_range_hist = range_menu.addAction(pixmaps.intensity_graph.icon(), "Current histogram limits",
                                                   self._setHistDisplayRange)
        for percent in (99.99, 99.9, 99.5, 99, 98, 95):
            range_menu.addAction("%g%%" % percent, self._currier.curry(self._changeDisplayRangeToPercent, percent))
        wrange_menu.setMenu(range_menu)
        wrange_menu.setPopupMode(QToolButton.InstantPopup)

        lo1 = QGridLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo0.addLayout(lo1, 0)
        self._wimap = QComboBox(self)
        lo1.addWidget(QLabel("Intensity policy:", self), 0, 0)
        lo1.addWidget(self._wimap, 1, 0)
        self._wimap.addItems(rc.getIntensityMapNames())
        QObject.connect(self._wimap, SIGNAL("currentIndexChanged(int)"), self._rc.setIntensityMapNumber)
        self._wimap.setToolTip("""<P>Use this to change the type of the intensity transfer function (ITF).</P>""")

        # log cycles control
        lo1.setColumnStretch(1, 1)
        self._wlogcycles_label = QLabel("Log cycles: ", self)
        lo1.addWidget(self._wlogcycles_label, 0, 1)
        #    self._wlogcycles = QwtWheel(self)
        #    self._wlogcycles.setTotalAngle(360)
        self._wlogcycles = QwtSlider(self)
        self._wlogcycles.setToolTip(
            """<P>Use this to change the log-base for the logarithmic intensity transfer function (ITF).</P>""")
        # This works around a stupid bug in QwtSliders -- see comments on histogram zoom wheel above
        self._wlogcycles_timer = QTimer(self)
        self._wlogcycles_timer.setSingleShot(True)
        self._wlogcycles_timer.setInterval(500)
        QObject.connect(self._wlogcycles_timer, SIGNAL("timeout()"), self._setIntensityLogCycles)
        lo1.addWidget(self._wlogcycles, 1, 1)
        self._wlogcycles.setRange(1., 10)
        self._wlogcycles.setStep(0.1)
        self._wlogcycles.setTracking(False)
        QObject.connect(self._wlogcycles, SIGNAL("valueChanged(double)"), self._setIntensityLogCycles)
        QObject.connect(self._wlogcycles, SIGNAL("sliderMoved(double)"), self._previewIntensityLogCycles)
        self._updating_imap = False

        # lock intensity map
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo0.addLayout(lo1, 0)
        #    lo1.addWidget(QLabel("Lock range accross",self))
        wlock = QCheckBox("Lock display range", self)
        wlock.setToolTip("""<P>If checked, then the intensity range will be locked. The ranges of all locked images
      change simultaneously.</P>""")
        lo1.addWidget(wlock)
        wlockall = QToolButton(self)
        wlockall.setIcon(pixmaps.locked.icon())
        wlockall.setText("Lock all to this")
        wlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        wlockall.setAutoRaise(True)
        wlockall.setToolTip("""<P>Click this to lock together the intensity ranges of all images.</P>""")
        lo1.addWidget(wlockall)
        wunlockall = QToolButton(self)
        wunlockall.setIcon(pixmaps.unlocked.icon())
        wunlockall.setText("Unlock all")
        wunlockall.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        wunlockall.setAutoRaise(True)
        wunlockall.setToolTip("""<P>Click this to unlock the intensity ranges of all images.</P>""")
        lo1.addWidget(wunlockall)
        wlock.setChecked(self._rc.isDisplayRangeLocked())
        QObject.connect(wlock, SIGNAL("clicked(bool)"), self._rc.lockDisplayRange)
        QObject.connect(wlockall, SIGNAL("clicked()"),
                        self._currier.curry(self._imgman.lockAllDisplayRanges, self._rc))
        QObject.connect(wunlockall, SIGNAL("clicked()"), self._imgman.unlockAllDisplayRanges)
        QObject.connect(self._rc, SIGNAL("displayRangeLocked"), wlock.setChecked)

        #    self._wlock_imap_axis = [ QCheckBox(name,self) for iaxis,name,labels in sliced_axes ]
        #    for iw,w in enumerate(self._wlock_imap_axis):
        #      QObject.connect(w,SIGNAL("toggled(bool)"),self._currier.curry(self._rc.lockDisplayRangeForAxis,iw))
        #      lo1.addWidget(w,0)
        lo1.addStretch(1)

        # lo0.addWidget(Separator(self,"Colourmap"))
        # color bar
        self._colorbar = QwtPlot(self)
        lo0.addWidget(self._colorbar)
        self._colorbar.setAutoDelete(False)
        self._colorbar.setMinimumHeight(32)
        self._colorbar.enableAxis(QwtPlot.yLeft, False)
        self._colorbar.enableAxis(QwtPlot.xBottom, False)
        # color plot
        self._colorplot = QwtPlot(self)
        lo0.addWidget(self._colorplot)
        self._colorplot.setAutoDelete(False)
        self._colorplot.setMinimumHeight(64)
        self._colorplot.enableAxis(QwtPlot.yLeft, False)
        self._colorplot.enableAxis(QwtPlot.xBottom, False)
        # self._colorplot.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Preferred)
        self._colorbar.hide()
        self._colorplot.hide()
        # color controls
        lo1 = QHBoxLayout()
        lo1.setContentsMargins(0, 0, 0, 0)
        lo0.addLayout(lo1, 1)
        lo1.addWidget(QLabel("Colourmap:", self))
        # colormap list
        ### NB: use setIconSize() and icons in QComboBox!!!
        self._wcolmaps = QComboBox(self)
        self._wcolmaps.setIconSize(QSize(128, 16))
        self._wcolmaps.setToolTip("""<P>Use this to select a different colourmap.</P>""")
        for cmap in self._rc.getColormapList():
            self._wcolmaps.addItem(QIcon(cmap.makeQPixmap(128, 16)), cmap.name)
        lo1.addWidget(self._wcolmaps)
        QObject.connect(self._wcolmaps, SIGNAL("activated(int)"), self._rc.setColorMapNumber)
        # add widgetstack for colormap controls
        self._wcolmap_control_stack = QStackedWidget(self)
        self._wcolmap_control_blank = QWidget(self._wcolmap_control_stack)
        self._wcolmap_control_stack.addWidget(self._wcolmap_control_blank)
        lo0.addWidget(self._wcolmap_control_stack)
        self._colmap_controls = []
        # add controls to stack
        for index, cmap in enumerate(self._rc.getColormapList()):
            if isinstance(cmap, Colormaps.ColormapWithControls):
                controls = cmap.makeControlWidgets(self._wcolmap_control_stack)
                self._wcolmap_control_stack.addWidget(controls)
                QObject.connect(cmap, SIGNAL("colormapChanged"),
                                self._currier.curry(self._previewColormapParameters, index, cmap))
                QObject.connect(cmap, SIGNAL("colormapPreviewed"),
                                self._currier.curry(self._previewColormapParameters, index, cmap))
                self._colmap_controls.append(controls)
            else:
                self._colmap_controls.append(self._wcolmap_control_blank)

        # connect updates from renderControl and image
        self.image.connect(SIGNAL("slice"), self._updateImageSlice)
        QObject.connect(self._rc, SIGNAL("intensityMapChanged"), self._updateIntensityMap)
        QObject.connect(self._rc, SIGNAL("colorMapChanged"), self._updateColorMap)
        QObject.connect(self._rc, SIGNAL("dataSubsetChanged"), self._updateDataSubset)
        QObject.connect(self._rc, SIGNAL("displayRangeChanged"), self._updateDisplayRange)

        # update widgets
        self._setupHistogramPlot()
        self._updateDataSubset(*self._rc.currentSubset())
        self._updateColorMap(image.colorMap())
        self._updateIntensityMap(rc.currentIntensityMap(), rc.currentIntensityMapNumber())
        self._updateDisplayRange(*self._rc.displayRange())

    def makeButton(self, label, callback=None, width=None, icon=None):
        btn = QToolButton(self)
        #    btn.setAutoRaise(True)
        label and btn.setText(label)
        icon and btn.setIcon(icon)
        #    btn = QPushButton(label,self)
        #   btn.setFlat(True)
        if width:
            btn.setMinimumWidth(width)
            btn.setMaximumWidth(width)
        if icon:
            btn.setIcon(icon)
        if callback:
            QObject.connect(btn, SIGNAL("clicked()"), callback)
        return btn

    #  def closeEvent (self,ev):
    #    ev.ignore()
    #    self.hide()

    def hide(self):
        self._geometry = self.geometry()
        QDialog.hide(self)

    def show(self):
        dprint(4, "show entrypoint")
        if self._geometry:
            dprint(4, "setting geometry")
            self.setGeometry(self._geometry)
        if self._hist is None:
            busy = BusyIndicator()
            dprint(4, "updating histogram")
            self._updateHistogram()
            dprint(4, "updating stats")
            self._updateStats(self._subset, self._subset_range)
            busy = None
        dprint(4, "calling QDialog.show")
        QDialog.show(self)

    # number of bins used to compute intensity transfer function
    NumItfBins = 1000
    # number of bins used for displaying histograms
    NumHistBins = 500
    # number of bins used for high-res histograms
    NumHistBinsHi = 10000
    # colorbar height, as fraction of plot area
    ColorBarHeight = 0.1

    class HistLimitPicker(QwtPlotPicker):
        """Auguments QwtPlotPicker with functions for selecting hist min/max values"""

        def __init__(self, plot, label, color="green", mode=QwtPicker.PointSelection,
                     rubber_band=QwtPicker.VLineRubberBand, tracker_mode=QwtPicker.ActiveOnly, track=None):
            QwtPlotPicker.__init__(self, QwtPlot.xBottom, QwtPlot.yRight, mode, rubber_band, tracker_mode,
                                   plot.canvas())
            self.plot = plot
            self.label = label
            self.track = track
            self.color = QColor(color)
            self.setRubberBandPen(QPen(self.color))

        def trackerText(self, pos):
            x, y = self.plot.invTransform(QwtPlot.xBottom, pos.x()), self.plot.invTransform(QwtPlot.yLeft, pos.y())
            if self.track:
                text = self.track(x, y)
                if text is not None:
                    return text
            if self.label:
                text = QwtText(self.label % dict(x=x, y=y))
                text.setColor(self.color)
                return text
            return QwtText()

        def widgetLeaveEvent(self, ev):
            if self.track:
                self.track(None, None)
            QwtPlotPicker.widgetLeaveEvent(self, ev)

    class ColorBarPlotItem(QwtPlotItem):
        def __init__(self, y0, y1, *args):
            QwtPlotItem.__init__(self, *args)
            self._y0 = y1
            self._dy = y1 - y0

        def setIntensityMap(self, imap):
            self.imap = imap

        def setColorMap(self, cmap):
            self.cmap = cmap

        def draw(self, painter, xmap, ymap, rect):
            """Implements QwtPlotItem.draw(), to render the colorbar on the given painter."""
            xp1, xp2, xdp, xs1, xs2, xds = xinfo = xmap.p1(), xmap.p2(), xmap.pDist(), xmap.s1(), xmap.s2(), xmap.sDist()
            yp1, yp2, ydp, ys1, ys2, yds = yinfo = ymap.p1(), ymap.p2(), ymap.pDist(), ymap.s1(), ymap.s2(), ymap.sDist()
            # xp: coordinates of pixels xp1...xp2 in data units
            xp = xs1 + (xds / xdp) * (0.5 + numpy.arange(int(xdp)))
            # convert y0 and y1 into pixel coordinates
            y0 = yp1 - (self._y0 - ys1) * (ydp / yds)
            dy = self._dy * (ydp / yds)
            # remap into an Nx1 image
            qimg = self.cmap.colorize(self.imap.remap(xp.reshape((len(xp), 1))))
            # plot image
            painter.drawImage(QRect(xp1, y0, xdp, dy), qimg)

    class HistogramLineMarker(object):
        """Helper class implementing a line marker for a histogram plot"""

        def __init__(self, plot, color="black", linestyle=Qt.DotLine, align=Qt.AlignBottom | Qt.AlignRight, z=90,
                     label="", zlabel=None, linewidth=1, spacing=2,
                     yaxis=QwtPlot.yRight):
            self.line = TiggerPlotCurve()
            self.color = color = color if isinstance(color, QColor) else QColor(color)
            self.line.setPen(QPen(color, linewidth, linestyle))
            self.marker = TiggerPlotMarker()
            self.marker.setLabelAlignment(align)
            try:
                self.marker.setSpacing(spacing)
            except AttributeError:
                pass
            self.setText(label)
            self.line.setZ(z)
            self.marker.setZ(zlabel if zlabel is not None else z)
            # set axes -- using yRight, since that is the "markup" z-axis
            self.line.setAxis(QwtPlot.xBottom, yaxis)
            self.marker.setAxis(QwtPlot.xBottom, yaxis)
            # attach to plot
            self.line.attach(plot)
            self.marker.attach(plot)

        def show(self):
            self.line.show()
            self.marker.show()

        def hide(self):
            self.line.hide()
            self.marker.hide()

        def setText(self, text):
            label = QwtText(text)
            label.setColor(self.color)
            self.marker.setLabel(label)

    def _setupHistogramPlot(self):
        self._histplot.setCanvasBackground(QColor("lightgray"))
        self._histplot.setAxisFont(QwtPlot.yLeft, QApplication.font())
        self._histplot.setAxisFont(QwtPlot.xBottom, QApplication.font())
        # add histogram curves
        self._histcurve1 = TiggerPlotCurve()
        self._histcurve2 = TiggerPlotCurve()
        self._histcurve1.setStyle(QwtPlotCurve.Steps)
        self._histcurve2.setStyle(QwtPlotCurve.Steps)
        self._histcurve1.setPen(QPen(Qt.NoPen))
        self._histcurve1.setBrush(QBrush(QColor("slategrey")))
        pen = QPen(QColor("red"))
        pen.setWidth(1)
        self._histcurve2.setPen(pen)
        self._histcurve1.setZ(0)
        self._histcurve2.setZ(100)
        #    self._histcurve1.attach(self._histplot)
        self._histcurve2.attach(self._histplot)
        # add maxbin and half-max curves
        self._line_0 = self.HistogramLineMarker(self._histplot, color="grey50", linestyle=Qt.SolidLine,
                                                align=Qt.AlignTop | Qt.AlignLeft, z=90)
        self._line_mean = self.HistogramLineMarker(self._histplot, color="black", linestyle=Qt.SolidLine,
                                                   align=Qt.AlignBottom | Qt.AlignRight, z=91,
                                                   label="mean", zlabel=151)
        self._line_std = self.HistogramLineMarker(self._histplot, color="black", linestyle=Qt.SolidLine,
                                                  align=Qt.AlignTop | Qt.AlignRight, z=91,
                                                  label="std", zlabel=151)
        sym = QwtSymbol()
        sym.setStyle(QwtSymbol.VLine)
        sym.setSize(8)
        self._line_std.line.setSymbol(sym)
        self._line_maxbin = self.HistogramLineMarker(self._histplot, color="green", linestyle=Qt.DotLine,
                                                     align=Qt.AlignTop | Qt.AlignRight, z=92,
                                                     label="max bin", zlabel=150)
        self._line_halfmax = self.HistogramLineMarker(self._histplot, color="green", linestyle=Qt.DotLine,
                                                      align=Qt.AlignBottom | Qt.AlignRight, z=90,
                                                      label="half-max", yaxis=QwtPlot.yLeft)
        # add current range
        self._rangebox = TiggerPlotCurve()
        self._rangebox.setStyle(QwtPlotCurve.Steps)
        self._rangebox.setYAxis(QwtPlot.yRight)
        self._rangebox.setPen(QPen(Qt.NoPen))
        self._rangebox.setBrush(QBrush(QColor("darkgray")))
        self._rangebox.setZ(50)
        self._rangebox.attach(self._histplot)
        self._rangebox2 = TiggerPlotCurve()
        self._rangebox2.setStyle(QwtPlotCurve.Sticks)
        self._rangebox2.setYAxis(QwtPlot.yRight)
        self._rangebox2.setZ(60)
        #  self._rangebox2.attach(self._histplot)
        # add intensity transfer function
        self._itfcurve = TiggerPlotCurve()
        self._itfcurve.setStyle(QwtPlotCurve.Lines)
        self._itfcurve.setPen(QPen(QColor("blue")))
        self._itfcurve.setYAxis(QwtPlot.yRight)
        self._itfcurve.setZ(120)
        self._itfcurve.attach(self._histplot)
        self._itfmarker = TiggerPlotMarker()
        label = QwtText("ITF")
        label.setColor(QColor("blue"))
        self._itfmarker.setLabel(label)
        try:
            self._itfmarker.setSpacing(0)
        except AttributeError:
            pass
        self._itfmarker.setLabelAlignment(Qt.AlignTop | Qt.AlignRight)
        self._itfmarker.setZ(120)
        self._itfmarker.attach(self._histplot)
        # add colorbar
        self._cb_item = self.ColorBarPlotItem(1, 1 + self.ColorBarHeight)
        self._cb_item.setYAxis(QwtPlot.yRight)
        self._cb_item.attach(self._histplot)
        # add pickers
        self._hist_minpicker = self.HistLimitPicker(self._histplot, "low: %(x).4g")
        self._hist_minpicker.setMousePattern(QwtEventPattern.MouseSelect1, Qt.LeftButton)
        QObject.connect(self._hist_minpicker, SIGNAL("selected(const QwtDoublePoint &)"), self._selectLowLimit)
        self._hist_maxpicker = self.HistLimitPicker(self._histplot, "high: %(x).4g")
        self._hist_maxpicker.setMousePattern(QwtEventPattern.MouseSelect1, Qt.RightButton)
        QObject.connect(self._hist_maxpicker, SIGNAL("selected(const QwtDoublePoint &)"), self._selectHighLimit)
        self._hist_maxpicker1 = self.HistLimitPicker(self._histplot, "high: %(x).4g")
        self._hist_maxpicker1.setMousePattern(QwtEventPattern.MouseSelect1, Qt.LeftButton, Qt.CTRL)
        QObject.connect(self._hist_maxpicker1, SIGNAL("selected(const QwtDoublePoint &)"), self._selectHighLimit)
        self._hist_zoompicker = self.HistLimitPicker(self._histplot, label="zoom",
                                                     tracker_mode=QwtPicker.AlwaysOn, track=self._trackHistCoordinates,
                                                     color="black",
                                                     mode=QwtPicker.RectSelection,
                                                     rubber_band=QwtPicker.RectRubberBand)
        self._hist_zoompicker.setMousePattern(QwtEventPattern.MouseSelect1, Qt.LeftButton, Qt.SHIFT)
        QObject.connect(self._hist_zoompicker, SIGNAL("selected(const QwtDoubleRect &)"), self._zoomHistogramIntoRect)

    def _trackHistCoordinates(self, x, y):
        self._wlab_histpos.setText((DataValueFormat + " %d") % (x, y) if x is not None else self._wlab_histpos_text)
        return QwtText()

    def _updateITF(self):
        """Updates current ITF array."""
        # do nothing if no histogram -- means we're not visible
        if self._hist is not None:
            xdata = self._itf_bins
            ydata = self.image.intensityMap().remap(xdata)
            self._rangebox.setData(self._rc.displayRange(), [1, 1])
            self._rangebox2.setData(self._rc.displayRange(), [1, 1])
            self._itfcurve.setData(xdata, ydata)
            self._itfmarker.setValue(xdata[0], 1)

    def _updateHistogram(self, hmin=None, hmax=None):
        """Recomputes histogram. If no arguments, computes full histogram for
        data subset. If hmin/hmax is specified, computes zoomed-in histogram."""
        busy = BusyIndicator()
        self._prev_range = self._display_range
        dmin, dmax = self._subset_range
        hmin0, hmax0 = dmin, dmax
        if hmin0 >= hmax0:
            hmax0 = hmin0 + 1
        subset, mask = self.image.optimalRavel(self._subset)
        # compute full-subset hi-res histogram, if we don't have one (for percentile stats)
        if self._hist_hires is None:
            dprint(1, "computing histogram for full subset range", hmin0, hmax0)
            self._hist_hires = measurements.histogram(subset, hmin0, hmax0, self.NumHistBinsHi, labels=mask,
                                                      index=None if mask is None else False)
            self._hist_bins_hires = hmin0 + (hmax0 - hmin0) * (numpy.arange(self.NumHistBinsHi) + 0.5) / float(
                self.NumHistBinsHi)
            self._hist_binsize_hires = (hmax0 - hmin0) / self.NumHistBins
        # if hist limits not specified, then compute lo-res histogram based on the hi-res one
        if hmin is None:
            hmin, hmax = hmin0, hmax0
            # downsample to low-res histogram
            self._hist = self._hist_hires.reshape((self.NumHistBins, self.NumHistBinsHi / self.NumHistBins)).sum(1)
        else:
            # zoomed-in low-res histogram
            # bracket limits at subset range
            hmin, hmax = max(hmin, dmin), min(hmax, dmax)
            if hmin >= hmax:
                hmax = hmin + 1
            dprint(1, "computing histogram for", self._subset.shape, self._subset.dtype, hmin, hmax)
            self._hist = measurements.histogram(subset, hmin, hmax, self.NumHistBins, labels=mask,
                                                index=None if mask is None else False)
        dprint(1, "histogram computed")
        # compute bins
        self._itf_bins = hmin + (hmax - hmin) * (numpy.arange(self.NumItfBins)) / (float(self.NumItfBins) - 1)
        self._hist_bins = hmin + (hmax - hmin) * (numpy.arange(self.NumHistBins) + 0.5) / float(self.NumHistBins)
        # histogram range and position of peak
        self._hist_range = hmin, hmax
        self._hist_min, self._hist_max, self._hist_imin, self._hist_imax = measurements.extrema(self._hist)
        self._hist_peak = self._hist_bins[self._hist_imax]
        # set controls accordingly
        if dmin >= dmax:
            dmax = dmin + 1
        zoom = math.log10((dmax - dmin) / (hmax - hmin))
        self._whistzoom.setValue(zoom)
        self._whistunzoom.setEnabled(zoom > 0)
        self._whistzoomout.setEnabled(zoom > 0)
        # reset scales
        self._histplot.setAxisScale(QwtPlot.xBottom, hmin, hmax)
        self._histplot.setAxisScale(QwtPlot.yRight, 0, 1 + self.ColorBarHeight)
        # update curves
        # call _setHistLogScale() (with current setting) to update axis scales and set data
        self._setHistLogScale(self._ylogscale, replot=False)
        # set plot lines
        self._line_0.line.setData([0, 0], [0, 1])
        self._line_0.marker.setValue(0, 0)
        self._line_maxbin.line.setData([self._hist_peak, self._hist_peak], [0, 1])
        self._line_maxbin.marker.setValue(self._hist_peak, 0)
        self._line_maxbin.setText(("max bin:" + DataValueFormat) % self._hist_peak)
        # set half-max line
        self._line_halfmax.line.setData(self._hist_range, [self._hist_max / 2, self._hist_max / 2])
        self._line_halfmax.marker.setValue(hmin, self._hist_max / 2)
        # update ITF
        self._updateITF()

    def _updateStats(self, subset, minmax):
        """Recomputes subset statistics."""
        if subset.size <= (2048 * 2048):
            self._showMeanStd(busy=False)
        else:
            self._wlab_stats.setText(
                ("min: %s  max: %s  np: %d" % (DataValueFormat, DataValueFormat, self._subset.size)) % minmax)
            self._wmore_stats.show()

    def _updateDataSubset(self, subset, minmax, desc, subset_type):
        """Called when the displayed data subset is changed. Updates the histogram."""
        self._subset = subset
        self._subset_range = minmax
        self._wlab_subset.setText("Subset: %s" % desc)
        self._hist = self._hist_hires = None
        self._wreset_full.setVisible(subset_type is not RenderControl.SUBSET_FULL)
        self._wreset_slice and self._wreset_slice.setVisible(subset_type is not RenderControl.SUBSET_SLICE)
        # hide the mean/std markers, they will only be shown when _showMeanStd() is called
        self._line_mean.hide()
        self._line_std.hide()
        # if we're visibile, recompute histograms and stats
        if self.isVisible():
            # if subset is sufficiently small, compute extended stats on-the-fly. Else show the "more" button to compute them later
            self._updateHistogram()
            self._updateStats(subset, minmax)
            self._histplot.replot()

    def _showMeanStd(self, busy=True):
        if busy:
            busy = BusyIndicator()
        dmin, dmax = self._subset_range
        subset, mask = self.image.optimalRavel(self._subset)
        dprint(5, "computing mean")
        mean = measurements.mean(subset, labels=mask, index=None if mask is None else False)
        dprint(5, "computing std")
        std = measurements.standard_deviation(subset, labels=mask, index=None if mask is None else False)
        dprint(5, "done")
        text = "  ".join([("%s: " + DataValueFormat) % (name, value) for name, value in
                          ("min", dmin), ("max", dmax), ("mean", mean), ("std", std)] + ["np: %d" % self._subset.size])
        self._wlab_stats.setText(text)
        self._wmore_stats.hide()
        # update markers
        ypos = 0.3
        self._line_mean.line.setData([mean, mean], [0, 1])
        self._line_mean.marker.setValue(mean, ypos)
        self._line_mean.setText(("\u03BC=" + DataValueFormat) % mean)
        self._line_mean.show()
        self._line_std.line.setData([mean - std, mean + std], [ypos, ypos])
        self._line_std.marker.setValue(mean, ypos)
        self._line_std.setText(("\u03C3=" + DataValueFormat) % std)
        self._line_std.show()
        self._histplot.replot()

    def _setIntensityLogCyclesLabel(self, value):
        self._wlogcycles_label.setText("Log cycles: %4.1f" % value)

    def _previewIntensityLogCycles(self, value):
        self._setIntensityLogCycles(value, notify_image=False, write_config=False)
        self._wlogcycles_timer.start(500)

    def _setIntensityLogCycles(self, value=None, notify_image=True, write_config=True):
        if value is None:
            value = self._wlogcycles.value()
        # stop timer if being called to finalize the change in value
        if notify_image:
            self._wlogcycles_timer.stop()
        if not self._updating_imap:
            self._setIntensityLogCyclesLabel(value)
            self._rc.setIntensityMapLogCycles(value, notify_image=notify_image, write_config=write_config)
            self._updateITF()
            self._histplot.replot()

    def _updateDisplayRange(self, dmin, dmax):
        self._rangebox.setData([dmin, dmax], [.9, .9])
        self._wrange[0].setText(DataValueFormat % dmin)
        self._wrange[1].setText(DataValueFormat % dmax)
        self._wrangeleft0.setEnabled(dmin != 0)
        self._display_range = dmin, dmax
        # if auto-zoom is on, zoom the histogram
        # try to be a little clever about this. Zoom only if (a) both limits have changed (so that adjusting one end of the range
        # does not cause endless rezooms), or (b) display range is < 1/10 of the histogram range
        if self._wautozoom.isChecked() and self._hist is not None:
            if (dmax - dmin) / (self._hist_range[1] - self._hist_range[0]) < .1 or (
                    dmin != self._prev_range[0] and dmax != self._prev_range[1]):
                margin = (dmax - dmin) / 8
                self._updateHistogram(dmin - margin, dmax + margin)
        self._updateITF()
        self._histplot.replot()

    def _updateIntensityMap(self, imap, index):
        self._updating_imap = True
        try:
            self._cb_item.setIntensityMap(imap)
            self._updateITF()
            self._histplot.replot()
            self._wimap.setCurrentIndex(index)
            if isinstance(imap, Colormaps.LogIntensityMap):
                self._wlogcycles.setValue(imap.log_cycles)
                self._setIntensityLogCyclesLabel(imap.log_cycles)
                self._wlogcycles.show()
                self._wlogcycles_label.show()
            else:
                self._wlogcycles.hide()
                self._wlogcycles_label.hide()
        finally:
            self._updating_imap = False

    def _updateColorMap(self, cmap):
        self._cb_item.setColorMap(cmap)
        self._histplot.replot()
        try:
            index = self._rc.getColormapList().index(cmap)
        except:
            return
        self._setCurrentColormapNumber(index, cmap)

    def _previewColormapParameters(self, index, cmap):
        """Called to preview a new colormap parameter value"""
        self._histplot.replot()
        self._wcolmaps.setItemIcon(index, QIcon(cmap.makeQPixmap(128, 16)))

    def _setCurrentColormapNumber(self, index, cmap):
        self._wcolmaps.setCurrentIndex(index)
        # show controls for colormap
        self._wcolmap_control_stack.setCurrentWidget(self._colmap_controls[index])

    def _changeDisplayRange(self):
        """Gets display range from widgets and updates the image with it."""
        try:
            newrange = [float(str(w.text())) for w in self._wrange]
        except ValueError:
            return
        self._rc.setDisplayRange(*newrange)

    def _setHistDisplayRange(self):
        self._rc.setDisplayRange(*self._hist_range)

    def _updateImageSlice(self, slice):
        for i, (iextra, name, labels) in enumerate(self._rc.slicedAxes()):
            self._wslicers[i].setCurrentIndex(slice[iextra])

    def _changeDisplayRangeToPercent(self, percent):
        busy = BusyIndicator()
        if self._hist is None:
            self._updateHistogram()
            self._updateStats(self._subset, self._subset_range)
        # delta: we need the [delta,100-delta] interval of the total distribution
        delta = self._subset.size * ((100. - percent) / 200.)
        # get F(x): cumulative sum
        cumsum = numpy.zeros(len(self._hist_hires) + 1, dtype=int)
        cumsum[1:] = numpy.cumsum(self._hist_hires)
        bins = numpy.zeros(len(self._hist_hires) + 1, dtype=float)
        bins[0] = self._subset_range[0]
        bins[1:] = self._hist_bins_hires + self._hist_binsize_hires / 2
        # use interpolation to find value interval corresponding to [delta,100-delta] of the distribution
        dprint(2, self._subset.size, delta, self._subset.size - delta)
        dprint(2, cumsum, self._hist_bins_hires)
        # if first bin is already > delta, then set colour range to first bin
        x0, x1 = numpy.interp([delta, self._subset.size - delta], cumsum, bins)
        # and change the display range (this will also cause a histplot.replot() via _updateDisplayRange above)
        self._rc.setDisplayRange(x0, x1)

    def _setZeroLeftLimit(self):
        self._rc.setDisplayRange(0., self._rc.displayRange()[1])

    def _selectLowLimit(self, pos):
        self._rc.setDisplayRange(pos.x(), self._rc.displayRange()[1])

    def _selectHighLimit(self, pos):
        self._rc.setDisplayRange(self._rc.displayRange()[0], pos.x())

    def _unzoomHistogram(self):
        self._updateHistogram()
        self._histplot.replot()

    def _zoomHistogramByFactor(self, factor):
        """Changes histogram limits by specified factor"""
        # get max distance of plot limit from peak
        dprint(1, "zooming histogram by", factor)
        halfdist = (self._hist_range[1] - self._hist_range[0]) / (factor * 2)
        self._updateHistogram(self._hist_peak - halfdist, self._hist_peak + halfdist)
        self._histplot.replot()

    def _zoomHistogramIntoRect(self, rect):
        hmin, hmax = rect.bottomLeft().x(), rect.bottomRight().x()
        if hmax > hmin:
            self._updateHistogram(rect.bottomLeft().x(), rect.bottomRight().x())
            self._histplot.replot()

    def _zoomHistogramPreview(self, value):
        dprint(2, "wheel moved to", value)
        self._zoomHistogramFinalize(value, preview=True)
        self._whistzoom_timer.start()

    def _zoomHistogramFinalize(self, value=None, preview=False):
        if self._zooming_histogram:
            return
        self._zooming_histogram = True
        try:
            if value is not None:
                dmin, dmax = self._subset_range
                dist = max(dmax - self._hist_peak, self._hist_peak - dmin) / 10 ** value
                self._preview_hist_range = max(self._hist_peak - dist, dmin), min(self._hist_peak + dist, dmax)
            if preview:
                self._histplot.setAxisScale(QwtPlot.xBottom, *self._preview_hist_range)
            else:
                dprint(2, "wheel finalized at", value)
                self._whistzoom_timer.stop()
                self._updateHistogram(*self._preview_hist_range)
            self._histplot.replot()
        finally:
            self._zooming_histogram = False

    def _setHistLogScale(self, logscale, replot=True):
        self._ylogscale = logscale
        if logscale:
            self._histplot.setAxisScaleEngine(QwtPlot.yLeft, QwtLog10ScaleEngine())
            ymax = max(1, self._hist_max)
            self._histplot.setAxisScale(QwtPlot.yLeft, 1, 10 ** (math.log10(ymax) * (1 + self.ColorBarHeight)))
            y = self._hist.copy()
            y[y == 0] = 1
            self._histcurve1.setData(self._hist_bins, y)
            self._histcurve2.setData(self._hist_bins, y)
        else:
            self._histplot.setAxisScaleEngine(QwtPlot.yLeft, QwtLinearScaleEngine())
            self._histplot.setAxisScale(QwtPlot.yLeft, 0, self._hist_max * (1 + self.ColorBarHeight))
            self._histcurve1.setData(self._hist_bins, self._hist)
            self._histcurve2.setData(self._hist_bins, self._hist)
        if replot:
            self._histplot.replot()
示例#22
0
class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
    def __init__(self, parent, db, id_to_select, select_sort, select_link):
        QDialog.__init__(self, parent)
        Ui_EditAuthorsDialog.__init__(self)
        self.setupUi(self)
        # Remove help icon on title bar
        icon = self.windowIcon()
        self.setWindowFlags(self.windowFlags()
                            & (~Qt.WindowContextHelpButtonHint))
        self.setWindowIcon(icon)

        try:
            self.table_column_widths = \
                        gprefs.get('manage_authors_table_widths', None)
            geom = gprefs.get('manage_authors_dialog_geometry', bytearray(''))
            self.restoreGeometry(QByteArray(geom))
        except:
            pass

        self.buttonBox.accepted.connect(self.accepted)

        # Set up the column headings
        self.table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table.setColumnCount(3)
        self.down_arrow_icon = QIcon(I('arrow-down.png'))
        self.up_arrow_icon = QIcon(I('arrow-up.png'))
        self.blank_icon = QIcon(I('blank.png'))
        self.auth_col = QTableWidgetItem(_('Author'))
        self.table.setHorizontalHeaderItem(0, self.auth_col)
        self.auth_col.setIcon(self.blank_icon)
        self.aus_col = QTableWidgetItem(_('Author sort'))
        self.table.setHorizontalHeaderItem(1, self.aus_col)
        self.aus_col.setIcon(self.up_arrow_icon)
        self.aul_col = QTableWidgetItem(_('Link'))
        self.table.setHorizontalHeaderItem(2, self.aul_col)
        self.aus_col.setIcon(self.blank_icon)

        # Add the data
        self.authors = {}
        auts = db.get_authors_with_ids()
        self.table.setRowCount(len(auts))
        select_item = None
        for row, (id, author, sort, link) in enumerate(auts):
            author = author.replace('|', ',')
            self.authors[id] = (author, sort, link)
            aut = tableItem(author)
            aut.setData(Qt.UserRole, id)
            sort = tableItem(sort)
            link = tableItem(link)
            self.table.setItem(row, 0, aut)
            self.table.setItem(row, 1, sort)
            self.table.setItem(row, 2, link)
            if id == id_to_select:
                if select_sort:
                    select_item = sort
                elif select_link:
                    select_item = link
                else:
                    select_item = aut
        self.table.resizeColumnsToContents()
        if self.table.columnWidth(2) < 200:
            self.table.setColumnWidth(2, 200)

        # set up the cellChanged signal only after the table is filled
        self.table.cellChanged.connect(self.cell_changed)

        # set up sort buttons
        self.sort_by_author.setCheckable(True)
        self.sort_by_author.setChecked(False)
        self.sort_by_author.clicked.connect(self.do_sort_by_author)
        self.author_order = 1

        self.table.sortByColumn(1, Qt.AscendingOrder)
        self.sort_by_author_sort.clicked.connect(self.do_sort_by_author_sort)
        self.sort_by_author_sort.setCheckable(True)
        self.sort_by_author_sort.setChecked(True)
        self.author_sort_order = 1

        self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort)
        self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author)

        # Position on the desired item
        if select_item is not None:
            self.table.setCurrentItem(select_item)
            self.table.editItem(select_item)
            self.start_find_pos = select_item.row() * 2 + select_item.column()
        else:
            self.table.setCurrentCell(0, 0)
            self.start_find_pos = -1

        # set up the search box
        self.find_box.initialize('manage_authors_search')
        self.find_box.lineEdit().returnPressed.connect(self.do_find)
        self.find_box.editTextChanged.connect(self.find_text_changed)
        self.find_button.clicked.connect(self.do_find)

        l = QLabel(self.table)
        self.not_found_label = l
        l.setFrameStyle(QFrame.StyledPanel)
        l.setAutoFillBackground(True)
        l.setText(_('No matches found'))
        l.setAlignment(Qt.AlignVCenter)
        l.resize(l.sizeHint())
        l.move(10, 20)
        l.setVisible(False)
        self.not_found_label.move(40, 40)
        self.not_found_label_timer = QTimer()
        self.not_found_label_timer.setSingleShot(True)
        self.not_found_label_timer.timeout.connect(
            self.not_found_label_timer_event, type=Qt.QueuedConnection)

        self.table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.table.customContextMenuRequested.connect(self.show_context_menu)

    def save_state(self):
        self.table_column_widths = []
        for c in range(0, self.table.columnCount()):
            self.table_column_widths.append(self.table.columnWidth(c))
        gprefs['manage_authors_table_widths'] = self.table_column_widths
        gprefs['manage_authors_dialog_geometry'] = bytearray(
            self.saveGeometry())

    def resizeEvent(self, *args):
        QDialog.resizeEvent(self, *args)
        if self.table_column_widths is not None:
            for c, w in enumerate(self.table_column_widths):
                self.table.setColumnWidth(c, w)
        else:
            # the vertical scroll bar might not be rendered, so might not yet
            # have a width. Assume 25. Not a problem because user-changed column
            # widths will be remembered
            w = self.table.width() - 25 - self.table.verticalHeader().width()
            w /= self.table.columnCount()
            for c in range(0, self.table.columnCount()):
                self.table.setColumnWidth(c, w)
        self.save_state()

    def show_context_menu(self, point):
        self.context_item = self.table.itemAt(point)
        case_menu = QMenu(_('Change Case'))
        action_upper_case = case_menu.addAction(_('Upper Case'))
        action_lower_case = case_menu.addAction(_('Lower Case'))
        action_swap_case = case_menu.addAction(_('Swap Case'))
        action_title_case = case_menu.addAction(_('Title Case'))
        action_capitalize = case_menu.addAction(_('Capitalize'))

        action_upper_case.triggered.connect(self.upper_case)
        action_lower_case.triggered.connect(self.lower_case)
        action_swap_case.triggered.connect(self.swap_case)
        action_title_case.triggered.connect(self.title_case)
        action_capitalize.triggered.connect(self.capitalize)

        m = self.au_context_menu = QMenu()
        ca = m.addAction(_('Copy'))
        ca.triggered.connect(self.copy_to_clipboard)
        ca = m.addAction(_('Paste'))
        ca.triggered.connect(self.paste_from_clipboard)
        m.addSeparator()

        if self.context_item is not None and self.context_item.column() == 0:
            ca = m.addAction(_('Copy to author sort'))
            ca.triggered.connect(self.copy_au_to_aus)
        else:
            ca = m.addAction(_('Copy to author'))
            ca.triggered.connect(self.copy_aus_to_au)
        m.addSeparator()
        m.addMenu(case_menu)
        m.exec_(self.table.mapToGlobal(point))

    def copy_to_clipboard(self):
        cb = QApplication.clipboard()
        cb.setText(unicode(self.context_item.text()))

    def paste_from_clipboard(self):
        cb = QApplication.clipboard()
        self.context_item.setText(cb.text())

    def upper_case(self):
        self.context_item.setText(icu_upper(unicode(self.context_item.text())))

    def lower_case(self):
        self.context_item.setText(icu_lower(unicode(self.context_item.text())))

    def swap_case(self):
        self.context_item.setText(unicode(self.context_item.text()).swapcase())

    def title_case(self):
        from calibre.utils.titlecase import titlecase
        self.context_item.setText(titlecase(unicode(self.context_item.text())))

    def capitalize(self):
        from calibre.utils.icu import capitalize
        self.context_item.setText(capitalize(unicode(
            self.context_item.text())))

    def copy_aus_to_au(self):
        row = self.context_item.row()
        dest = self.table.item(row, 0)
        dest.setText(self.context_item.text())

    def copy_au_to_aus(self):
        row = self.context_item.row()
        dest = self.table.item(row, 1)
        dest.setText(self.context_item.text())

    def not_found_label_timer_event(self):
        self.not_found_label.setVisible(False)

    def find_text_changed(self):
        self.start_find_pos = -1

    def do_find(self):
        self.not_found_label.setVisible(False)
        # For some reason the button box keeps stealing the RETURN shortcut.
        # Steal it back
        self.buttonBox.button(QDialogButtonBox.Ok).setDefault(False)
        self.buttonBox.button(QDialogButtonBox.Ok).setAutoDefault(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False)
        st = icu_lower(unicode(self.find_box.currentText()))

        for i in range(0, self.table.rowCount() * 2):
            self.start_find_pos = (self.start_find_pos +
                                   1) % (self.table.rowCount() * 2)
            r = (self.start_find_pos / 2) % self.table.rowCount()
            c = self.start_find_pos % 2
            item = self.table.item(r, c)
            text = icu_lower(unicode(item.text()))
            if st in text:
                self.table.setCurrentItem(item)
                self.table.setFocus(True)
                return
        # Nothing found. Pop up the little dialog for 1.5 seconds
        self.not_found_label.setVisible(True)
        self.not_found_label_timer.start(1500)

    def do_sort_by_author(self):
        self.author_order = 1 if self.author_order == 0 else 0
        self.table.sortByColumn(0, self.author_order)
        self.sort_by_author.setChecked(True)
        self.sort_by_author_sort.setChecked(False)
        self.auth_col.setIcon(
            self.down_arrow_icon if self.author_order else self.up_arrow_icon)
        self.aus_col.setIcon(self.blank_icon)

    def do_sort_by_author_sort(self):
        self.author_sort_order = 1 if self.author_sort_order == 0 else 0
        self.table.sortByColumn(1, self.author_sort_order)
        self.sort_by_author.setChecked(False)
        self.sort_by_author_sort.setChecked(True)
        self.aus_col.setIcon(self.down_arrow_icon if self.
                             author_sort_order else self.up_arrow_icon)
        self.auth_col.setIcon(self.blank_icon)

    def accepted(self):
        self.save_state()
        self.result = []
        for row in range(0, self.table.rowCount()):
            id = self.table.item(row, 0).data(Qt.UserRole).toInt()[0]
            aut = unicode(self.table.item(row, 0).text()).strip()
            sort = unicode(self.table.item(row, 1).text()).strip()
            link = unicode(self.table.item(row, 2).text()).strip()
            orig_aut, orig_sort, orig_link = self.authors[id]
            if orig_aut != aut or orig_sort != sort or orig_link != link:
                self.result.append((id, orig_aut, aut, sort, link))

    def do_recalc_author_sort(self):
        self.table.cellChanged.disconnect()
        for row in range(0, self.table.rowCount()):
            item = self.table.item(row, 0)
            aut = unicode(item.text()).strip()
            c = self.table.item(row, 1)
            # Sometimes trailing commas are left by changing between copy algs
            c.setText(author_to_author_sort(aut).rstrip(','))
        self.table.setFocus(Qt.OtherFocusReason)
        self.table.cellChanged.connect(self.cell_changed)

    def do_auth_sort_to_author(self):
        self.table.cellChanged.disconnect()
        for row in range(0, self.table.rowCount()):
            item = self.table.item(row, 1)
            aus = unicode(item.text()).strip()
            c = self.table.item(row, 0)
            # Sometimes trailing commas are left by changing between copy algs
            c.setText(aus)
        self.table.setFocus(Qt.OtherFocusReason)
        self.table.cellChanged.connect(self.cell_changed)

    def cell_changed(self, row, col):
        if col == 0:
            item = self.table.item(row, 0)
            aut = unicode(item.text()).strip()
            amper = aut.find('&')
            if amper >= 0:
                error_dialog(
                    self.parent(), _('Invalid author name'),
                    _('Author names cannot contain & characters.')).exec_()
                aut = aut.replace('&', '%')
                self.table.item(row, 0).setText(aut)
            c = self.table.item(row, 1)
            c.setText(author_to_author_sort(aut))
            item = c
        else:
            item = self.table.item(row, col)
        self.table.setCurrentItem(item)
        self.table.scrollToItem(item)
示例#23
0
class GamessJob(SimJob):
    """
    A Gamess job is a setup used to run Gamess simulation.
    Two ways exist to create a Gamess Job:
    (1). Create a Gamess Jig.
    (2). A gamess job coming from a set of existing files
    in a particular location. 
    """
    def __init__(self,  job_parms, **job_prop):
        """
        To support the 2 ways of gamess job creation.
        """
        name = "Gamess Job 1"
        [self.job_batfile, self.job_outputfile] = job_prop.get('job_from_file', [None, None])
        if self.job_outputfile:
            self.job_outputfile = self.job_outputfile.strip('"')
        self.gamessJig = job_prop.get('jig', None)
        
        if self.job_batfile:
            server_id = job_parms['Server_id']
            self.server = ServerManager().getServerById(int(server_id))
            if not self.server:
                raise ValueError, "The server of %d can't be found." % server_id  
        
        SimJob.__init__(self, name, job_parms)
        
        self.edit_cntl = GamessProp()
        
        #Huaicai 7/6/05: try to fix the problem when run a gamess jig coming from mmp file 
        #and without opening the jig property windows and save it.
        if not self.__dict__.has_key('server'):
            sManager = ServerManager()
            self.server = sManager.getServers()[0]
        return
        
    def edit(self):
        self.edit_cntl.showDialog(self)
        return
    
    def launch(self): # this method should probably exist in the superclass API too [bruce 071216 comment]
        """
        Launch GAMESS job.
        Returns: 0 = Success
                 1 = Cancelled
                 2 = Failed
        """
        # Get a unique Job Id and the Job Id directory for this run.
        from analysis.GAMESS.JobManager import get_job_manager_job_id_and_dir
            ### this causes an import cycle.
            ### FIX - pass in an obj providing this func? or just move this func into SimJob.py?
            # [bruce 071216 comment]
        job_id, job_id_dir = get_job_manager_job_id_and_dir()
        self.Job_id = job_id
        self.Status = 'Queued'

        basename = self.gamessJig.name + "-" + self.gamessJig.gms_parms_info('_')
      
        # GAMESS Job INP, OUT and BAT files.

        self.job_inputfile = os.path.join(job_id_dir, "%s.inp" % basename)

        self.job_outputfile = os.path.join(job_id_dir, "%s.out" %  basename)
        self.job_batfile = os.path.join(job_id_dir, "%s.bat" %  basename)
         
        # Write INP file (in ~/Nanorex/JobManager/Job Id subdirectory)
        writegms_inpfile(self.job_inputfile, self.gamessJig)
        
        # Create BAT file (in ~/Nanorex/JobManager/Job Id subdirectory)
        ## writegms_batfile(self.job_batfile, self)
        
        # Open INP file in editor if user checked checkbox in GAMESS jig properties UI.
#        if self.edit_cntl.edit_input_file_cbox.isChecked():
#            open_file_in_editor(self.job_inputfile)

        self.starttime = time.time() # Save the start time for this job.
        
        # Validate Gamess Program
        r = self._validate_gamess_program()
        
        if r: # Gamess program was not valid.
            return 1 # Job Cancelled.
           
        try:
            if sys.platform == 'win32': # Windows
                return self._launch_pcgamess()
            else: # Linux or MacOS
                return self._launch_gamess()
        except:
             print_compact_traceback("Exception happened when run gamess")    
        return
    
    def _validate_gamess_program(self):
        """
        Private method:
        Checks that the GAMESS program path exists in the user pref database
        and that the file it points to exists.  If the GAMESS path does not exist, the
        user will be prompted with the file chooser to select the GAMESS executable.
        This function does not check whether the GAMESS path is actually GAMESS 
        or if it is the correct version of GAMESS for this platform (i.e. PC GAMESS for Windows).
        Returns:  0 = Valid
                  1 = Invalid
        """
        # Get GAMESS executable path from the user preferences
        prefs = preferences.prefs_context()
        self.server.program = prefs.get(gmspath_prefs_key)
        
        if not self.server.program:
            msg = "The GAMESS executable path is not set.\n"
        elif os.path.exists(self.server.program):
            return 0
        else:
            msg = self.server.program + " does not exist.\n"
            
        # GAMESS Prop Dialog is the parent for messagebox and file chooser.
        parent = self.edit_cntl 
            
        ret = QMessageBox.warning( parent, "GAMESS Executable Path",
            msg + "Please select OK to set the location of GAMESS for this computer.",
            "&OK", "Cancel", "",
            0, 1 )
        
        if ret == 0: # OK
            self.server.program = \
                get_filename_and_save_in_prefs(parent, gmspath_prefs_key, "Choose GAMESS Executable")
            if not self.server.program:
                return 1 # Cancelled from file chooser.
            
            # Enable GAMESS Plug-in. Mark 060112.
            env.prefs[gamess_enabled_prefs_key] = True
        
        elif ret == 1: # Cancel
            return 1

        return 0

    def _readFromStdout(self):
        """
        Slot method to read stdout of the gamess process
        """
        #while self.process.canReadLineStdout():
        #    lineStr = str(self.process.readLineStdout()) + '\n'
        #    self.outputFile.write(lineStr)
        bytes = self.process.readStdout()
        self.outputFile.writeBlock(bytes)
        return
    
    def startFileWriting(self):
        self.fwThread.start()
        return
    
    def readOutData(self):
        bytes = self.process.readStdout()
        if bytes:
           self.fwThread.putData(bytes)
        return
    
    def processTimeout(self):
        if self.process.isRunning():
            if not self.progressDialog.isShown() or self.progressDialog.Rejected:
                return
                
            msgLabel = self.progressDialog.getMsgLabel()
            duration = time.time() - self.stime
            elapmsg = "Elapsed Time: " + hhmmss_str(int(duration))
            msgLabel.setText(elapmsg)
            
            ####bytes = self.process.readStdout()
            ####if bytes:
            ####    self.fwThread.putData(bytes)
        return
    
    def processDone(self):
        #self.fwThread.stop()
        self.jobTimer.stop()
        
        if self.process.normalExit():
            print "The process is done!"
            QDialog.accept(self.progressDialog)
        else:
            print "The process is cancelled!"
            QDialog.reject(self.progressDialog)
        return
    
    def _launch_gamess(self):
        oldir = os.getcwd() # Save current directory
        
        ####self.outputFile = QFile(self.job_outputfile)
        ####self.outputFile.open( IO_WriteOnly | IO_Append )
            
        jobInputfile = os.path.basename(self.job_inputfile)
        jobInputFile = jobInputfile[:-4]
        jobOutputfile = self.job_outputfile #os.path.basename(self.job_outputfile)    
        
        ### Notes: The following way to get the 'bin' works by assuming user didn't change the
        ### working directory after the atom runs, otherwise it will get problem.
        filePath = os.path.dirname(os.path.abspath(sys.argv[0]))
        program = os.path.normpath(filePath + '/../bin/rungms')
        
        jobDir = os.path.dirname(self.job_batfile)
        os.chdir(jobDir) # Change directory to the GAMESS temp directory.
#        print "Current directory is: ", jobDir
        
        
        executableFile = self.server.program
            
        args = [program, jobInputFile, jobOutputfile, executableFile]#, '01', '1']
        
            
        self.process = QProcess()
        for s in args:
            self.process.addArgument(s)
        ####self.process.setCommunication(QProcess.Stdout)
        print "The params for the QProcess run are: ", args
            
        ####self.fwThread = _FileWriting(self.outputFile)
        self.jobTimer = QTimer(self)
        
        self.progressDialog = JobProgressDialog(self.process, self.Calculation)
        self.connect(self.process, SIGNAL('processExited ()'), self.processDone)
        
        #self.connect(self.process, SIGNAL('readyReadStdout()'), self.readOutData)
        ####self.fwThread.start()   
        
        if not self.process.start():
            print "The process can't be started."
            return 2
        
        self.connect(self.jobTimer, SIGNAL('timeout()'), self.processTimeout)
        self.stime = time.time()
        self.jobTimer.start(1)
        
        ret = self.progressDialog.exec_()
        if ret == QDialog.Accepted:
            retValue = 0
        else:
            retValue = 1
        
        ####self.fwThread.wait()        
        ####bytes = self.process.readStdout()
        ####if bytes:
            #####self.outputFile.writeBlock(bytes)
            #####self.outputFile.flush()
            
        self.process = None
        ####self.outputFile.close()
        
        os.chdir(oldir)
        self.gamessJig.outputfile = self.job_outputfile
                
        return retValue
        
    def _launch_pcgamess(self):
        """
        Run PC GAMESS (Windows only).
        PC GAMESS creates 2 output files:
          - the DAT file, called "PUNCH", is written to the directory from which
            PC GAMESS is started.  This is why we chdir to the Gamess temp directory
            before we run PC GAMESS.
          - the OUT file (aka the log file), which we name jigname.out.
        Returns: 0 = Success
                       1 = Cancelled
                       2 = Failed
        """
        oldir = os.getcwd() # Save current directory
        
        jobDir = os.path.dirname(self.job_batfile)
        os.chdir(jobDir) # Change directory to the GAMESS temp directory.
##        print "Current directory is: ", jobDir
        
        DATfile = os.path.join(jobDir, "PUNCH")
        if os.path.exists(DATfile): # Remove any previous DAT (PUNCH) file.
            print "run_pcgamess: Removing DAT file: ", DATfile
            os.remove(DATfile)
        
        # Hours wasted testing this undocumented tripe.  Here's the deal: When using spawnv
        # on Windows, any args that might have spaces must be delimited by double quotes.
        # Mark 050530.
        
        #program = "\"" + self.job_batfile + "\""
        #args = [program, ]
        
        # Here's the infuriating part.  The 2nd arg to spawnv cannot have double quotes, but the
        # first arg in args (the program name) must have the double quotes if there is a space in
        # self.gms_program.
        
        #print  "program = ", program
        #print  "Spawnv args are %r" % (args,) # this %r remains (see above)
        #os.spawnv(os.P_WAIT, self.job_batfile, args)
        
        arg_list = ['-i', self.job_inputfile, '-o', self.job_outputfile]
        args = QStringList()        
        for s in arg_list:
            args.append(str(s))          
            
        process = QProcess()
        process.start(self.server.program, args)
        # Blocks for n millisconds until the process has started and started() 
        # signal is emitted. Returns true if the process was started successfullly. 
        if not process.waitForStarted(2000): 
            print "The process can't be started."
            return 2
        progressDialog = self.showProgress()
        progressDialog.show()
        i = 55
        pInc = True
        while process.state() == QProcess.Running:
            env.call_qApp_processEvents() #bruce 050908 replaced qApp.processEvents()
            if progressDialog.wasCanceled():
                process.kill()
                os.chdir(oldir)
                return 1 # Job cancelled.
                           
            progressDialog.setValue(i)
            if pInc:
                if i < 75: i += 1
                else: pInc = False
            else:
                if i > 55:  i -= 1
                else: pInc = True
            # Do sth here
            time.sleep(0.05)
            if not process.state() == QProcess.Running:
                 break
              
        progressDialog.setValue(100)
        progressDialog.accept()
        
        os.chdir(oldir)
        self.gamessJig.outputfile = self.job_outputfile
        
        return 0 # Success

    def showProgress(self, modal = True):
        """
        Open the progress dialog to show the current job progress.
        """
        #Replace "self.edit_cntl.win" with "None"
        #---Huaicai 7/7/05: To fix bug 751, the "win" may be none.
        #Feel awkward for the design of our code.        
        simProgressDialog = QProgressDialog()
        simProgressDialog.setModal(True)
        simProgressDialog.setObjectName("progressDialog")
        simProgressDialog.setWindowIcon(geticon('ui/border/MainWindow'))
        if self.Calculation == 'Energy':
            simProgressDialog.setWindowTitle("Calculating Energy ...Please Wait")
        else:
            simProgressDialog.setWindowTitle("Optimizing ...Please Wait")
       
        progBar = QProgressBar(simProgressDialog)
        progBar.setMaximum(0)
        progBar.setMinimum(0)
        progBar.setValue(0)
        progBar.setTextVisible(False)
        simProgressDialog.setBar(progBar)
        simProgressDialog.setAutoReset(False)
        simProgressDialog.setAutoClose(False)
        
        return simProgressDialog

    # Added by Mark 050919 for bug #915.  Not used yet.   
    def _job_cancelation_confirmed(self):
        ret = QMessageBox.warning( None, "Confirm",
            "Please confirm you want to abort the GAMESS simulation.\n",
            "Confirm",
            "Cancel", 
            "", 
            1,  # The "default" button, when user presses Enter or Return (1 = Cancel)
            1)  # Escape (1= Cancel)
          
        if ret == 0: # Confirmed
            print "CONFIRMED"
            return True
        else:
            print "CANCELLED"
            return False

    pass # end of class GamessJob
示例#24
0
class Scheduler(QObject):

    INTERVAL = 1  # minutes

    delete_old_news = pyqtSignal(object)
    start_recipe_fetch = pyqtSignal(object)

    def __init__(self, parent, db):
        QObject.__init__(self, parent)
        self.internet_connection_failed = False
        self._parent = parent
        self.no_internet_msg = _('Cannot download news as no internet connection '
                'is active')
        self.no_internet_dialog = d = error_dialog(self._parent,
                self.no_internet_msg, _('No internet connection'),
                show_copy_button=False)
        d.setModal(False)

        self.recipe_model = RecipeModel()
        self.db = db
        self.lock = QMutex(QMutex.Recursive)
        self.download_queue = set([])

        self.news_menu = QMenu()
        self.news_icon = QIcon(I('news.png'))
        self.scheduler_action = QAction(QIcon(I('scheduler.png')), _('Schedule news download'), self)
        self.news_menu.addAction(self.scheduler_action)
        self.scheduler_action.triggered[bool].connect(self.show_dialog)
        self.cac = QAction(QIcon(I('user_profile.png')), _('Add a custom news source'), self)
        self.cac.triggered[bool].connect(self.customize_feeds)
        self.news_menu.addAction(self.cac)
        self.news_menu.addSeparator()
        self.all_action = self.news_menu.addAction(
                _('Download all scheduled news sources'),
                self.download_all_scheduled)

        self.timer = QTimer(self)
        self.timer.start(int(self.INTERVAL * 60 * 1000))
        self.timer.timeout.connect(self.check)
        self.oldest = gconf['oldest_news']
        QTimer.singleShot(5 * 1000, self.oldest_check)

    def database_changed(self, db):
        self.db = db

    def oldest_check(self):
        if self.oldest > 0:
            delta = timedelta(days=self.oldest)
            try:
                ids = list(self.db.tags_older_than(_('News'),
                    delta, must_have_authors=['calibre']))
            except:
                # Happens if library is being switched
                ids = []
            if ids:
                if ids:
                    self.delete_old_news.emit(ids)
        QTimer.singleShot(60 * 60 * 1000, self.oldest_check)

    def show_dialog(self, *args):
        self.lock.lock()
        try:
            d = SchedulerDialog(self.recipe_model)
            d.download.connect(self.download_clicked)
            d.exec_()
            gconf['oldest_news'] = self.oldest = d.old_news.value()
            d.break_cycles()
        finally:
            self.lock.unlock()

    def customize_feeds(self, *args):
        from calibre.gui2.dialogs.user_profiles import UserProfiles
        d = UserProfiles(self._parent, self.recipe_model)
        d.exec_()
        d.break_cycles()

    def do_download(self, urn):
        self.lock.lock()
        try:
            account_info = self.recipe_model.get_account_info(urn)
            customize_info = self.recipe_model.get_customize_info(urn)
            recipe = self.recipe_model.recipe_from_urn(urn)
            un = pw = None
            if account_info is not None:
                un, pw = account_info
            add_title_tag, custom_tags, keep_issues = customize_info
            script = self.recipe_model.get_recipe(urn)
            pt = PersistentTemporaryFile('_builtin.recipe')
            pt.write(script)
            pt.close()
            arg = {
                    'username': un,
                    'password': pw,
                    'add_title_tag':add_title_tag,
                    'custom_tags':custom_tags,
                    'recipe':pt.name,
                    'title':recipe.get('title',''),
                    'urn':urn,
                    'keep_issues':keep_issues
                   }
            self.download_queue.add(urn)
            self.start_recipe_fetch.emit(arg)
        finally:
            self.lock.unlock()

    def recipe_downloaded(self, arg):
        self.lock.lock()
        try:
            self.recipe_model.update_last_downloaded(arg['urn'])
            self.download_queue.remove(arg['urn'])
        finally:
            self.lock.unlock()

    def recipe_download_failed(self, arg):
        self.lock.lock()
        try:
            self.recipe_model.update_last_downloaded(arg['urn'])
            self.download_queue.remove(arg['urn'])
        finally:
            self.lock.unlock()

    def download_clicked(self, urn):
        if urn is not None:
            return self.download(urn)
        for urn in self.recipe_model.scheduled_urns():
            if not self.download(urn):
                break

    def download_all_scheduled(self):
        self.download_clicked(None)

    def has_internet_connection(self):
        if not internet_connected():
            if not self.internet_connection_failed:
                self.internet_connection_failed = True
                if self._parent.is_minimized_to_tray:
                    self._parent.status_bar.show_message(self.no_internet_msg,
                            5000)
                elif not self.no_internet_dialog.isVisible():
                    self.no_internet_dialog.show()
            return False
        self.internet_connection_failed = False
        if self.no_internet_dialog.isVisible():
            self.no_internet_dialog.hide()
        return True

    def download(self, urn):
        self.lock.lock()
        if not self.has_internet_connection():
            return False
        doit = urn not in self.download_queue
        self.lock.unlock()
        if doit:
            self.do_download(urn)
        return True

    def check(self):
        recipes = self.recipe_model.get_to_be_downloaded_recipes()
        for urn in recipes:
            if not self.download(urn):
                # No internet connection, we will try again in a minute
                break
示例#25
0
文件: preview.py 项目: Kielek/calibre
class Preview(QWidget):

    sync_requested = pyqtSignal(object, object)
    split_requested = pyqtSignal(object, object, object)
    split_start_requested = pyqtSignal()
    link_clicked = pyqtSignal(object, object)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout()
        self.setLayout(l)
        l.setContentsMargins(0, 0, 0, 0)
        self.view = WebView(self)
        self.view.page().sync_requested.connect(self.request_sync)
        self.view.page().split_requested.connect(self.request_split)
        self.view.page().loadFinished.connect(self.load_finished)
        self.inspector = self.view.inspector
        self.inspector.setPage(self.view.page())
        l.addWidget(self.view)
        self.bar = QToolBar(self)
        l.addWidget(self.bar)

        ac = actions['auto-reload-preview']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.auto_reload_toggled)
        self.auto_reload_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['sync-preview-to-editor']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.sync_toggled)
        self.sync_toggled(ac.isChecked())
        self.bar.addAction(ac)

        self.bar.addSeparator()

        ac = actions['split-in-preview']
        ac.setCheckable(True)
        ac.setChecked(False)
        ac.toggled.connect(self.split_toggled)
        self.split_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['reload-preview']
        ac.triggered.connect(self.refresh)
        self.bar.addAction(ac)

        actions['preview-dock'].toggled.connect(self.visibility_changed)

        self.current_name = None
        self.last_sync_request = None
        self.refresh_timer = QTimer(self)
        self.refresh_timer.timeout.connect(self.refresh)
        parse_worker.start()
        self.current_sync_request = None

        self.search = HistoryLineEdit2(self)
        self.search.initialize('tweak_book_preview_search')
        self.search.setPlaceholderText(_('Search in preview'))
        self.search.returnPressed.connect(partial(self.find, 'next'))
        self.bar.addSeparator()
        self.bar.addWidget(self.search)
        for d in ('next', 'prev'):
            ac = actions['find-%s-preview' % d]
            ac.triggered.connect(partial(self.find, d))
            self.bar.addAction(ac)

    def find(self, direction):
        text = unicode(self.search.text())
        self.view.findText(text, QWebPage.FindWrapsAroundDocument | (
            QWebPage.FindBackward if direction == 'prev' else QWebPage.FindFlags(0)))

    def request_sync(self, tagname, href, lnum):
        if self.current_name:
            c = current_container()
            if tagname == 'a' and href:
                if href and href.startswith('#'):
                    name = self.current_name
                else:
                    name = c.href_to_name(href, self.current_name) if href else None
                if name == self.current_name:
                    return self.view.page().go_to_anchor(urlparse(href).fragment, lnum)
                if name and c.exists(name) and c.mime_map[name] in OEB_DOCS:
                    return self.link_clicked.emit(name, urlparse(href).fragment or TOP)
            self.sync_requested.emit(self.current_name, lnum)

    def request_split(self, loc, totals):
        if self.current_name:
            self.split_requested.emit(self.current_name, loc, totals)

    def sync_to_editor(self, name, lnum):
        self.current_sync_request = (name, lnum)
        QTimer.singleShot(100, self._sync_to_editor)

    def _sync_to_editor(self):
        if not actions['sync-preview-to-editor'].isChecked():
            return
        try:
            if self.refresh_timer.isActive() or self.current_sync_request[0] != self.current_name:
                return QTimer.singleShot(100, self._sync_to_editor)
        except TypeError:
            return  # Happens if current_sync_request is None
        lnum = self.current_sync_request[1]
        self.current_sync_request = None
        self.view.page().go_to_line(lnum)

    def show(self, name):
        if name != self.current_name:
            self.refresh_timer.stop()
            self.current_name = name
            parse_worker.add_request(name)
            self.view.setUrl(QUrl.fromLocalFile(current_container().name_to_abspath(name)))
            return True

    def refresh(self):
        if self.current_name:
            self.refresh_timer.stop()
            # This will check if the current html has changed in its editor,
            # and re-parse it if so
            parse_worker.add_request(self.current_name)
            # Tell webkit to reload all html and associated resources
            current_url = QUrl.fromLocalFile(current_container().name_to_abspath(self.current_name))
            if current_url != self.view.url():
                # The container was changed
                self.view.setUrl(current_url)
            else:
                self.view.refresh()

    def clear(self):
        self.view.clear()
        self.current_name = None

    @property
    def is_visible(self):
        return actions['preview-dock'].isChecked()

    def start_refresh_timer(self):
        if self.is_visible and actions['auto-reload-preview'].isChecked():
            self.refresh_timer.start(tprefs['preview_refresh_time'] * 1000)

    def stop_refresh_timer(self):
        self.refresh_timer.stop()

    def auto_reload_toggled(self, checked):
        actions['auto-reload-preview'].setToolTip(_(
            'Auto reload preview when text changes in editor') if not checked else _(
                'Disable auto reload of preview'))

    def sync_toggled(self, checked):
        actions['sync-preview-to-editor'].setToolTip(_(
            'Disable syncing of preview position to editor position') if checked else _(
                'Enable syncing of preview position to editor position'))

    def visibility_changed(self, is_visible):
        if is_visible:
            self.refresh()

    def split_toggled(self, checked):
        actions['split-in-preview'].setToolTip(textwrap.fill(_(
            'Abort file split') if checked else _(
                'Split this file at a specified location.\n\nAfter clicking this button, click'
                ' inside the preview panel above at the location you want the file to be split.')))
        if checked:
            self.split_start_requested.emit()
        else:
            self.view.page().split_mode(False)

    def do_start_split(self):
        self.view.page().split_mode(True)

    def stop_split(self):
        actions['split-in-preview'].setChecked(False)

    def load_finished(self, ok):
        if actions['split-in-preview'].isChecked():
            if ok:
                self.do_start_split()
            else:
                self.stop_split()

    def apply_settings(self):
        s = self.view.page().settings()
        s.setFontSize(s.DefaultFontSize, tprefs['preview_base_font_size'])
        s.setFontSize(s.DefaultFixedFontSize, tprefs['preview_mono_font_size'])
        s.setFontSize(s.MinimumLogicalFontSize, tprefs['preview_minimum_font_size'])
        s.setFontSize(s.MinimumFontSize, tprefs['preview_minimum_font_size'])
        sf, ssf, mf = tprefs['preview_serif_family'], tprefs['preview_sans_family'], tprefs['preview_mono_family']
        s.setFontFamily(s.StandardFont, {'serif':sf, 'sans':ssf, 'mono':mf, None:sf}[tprefs['preview_standard_font_family']])
        s.setFontFamily(s.SerifFont, sf)
        s.setFontFamily(s.SansSerifFont, ssf)
        s.setFontFamily(s.FixedFont, mf)
示例#26
0
class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):

    def __init__(self, parent, db, id_to_select, select_sort, select_link):
        QDialog.__init__(self, parent)
        Ui_EditAuthorsDialog.__init__(self)
        self.setupUi(self)
        # Remove help icon on title bar
        icon = self.windowIcon()
        self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
        self.setWindowIcon(icon)

        try:
            self.table_column_widths = \
                        gprefs.get('manage_authors_table_widths', None)
            geom = gprefs.get('manage_authors_dialog_geometry', bytearray(''))
            self.restoreGeometry(QByteArray(geom))
        except:
            pass

        self.buttonBox.accepted.connect(self.accepted)

        # Set up the column headings
        self.table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table.setColumnCount(3)
        self.down_arrow_icon = QIcon(I('arrow-down.png'))
        self.up_arrow_icon = QIcon(I('arrow-up.png'))
        self.blank_icon = QIcon(I('blank.png'))
        self.auth_col = QTableWidgetItem(_('Author'))
        self.table.setHorizontalHeaderItem(0, self.auth_col)
        self.auth_col.setIcon(self.blank_icon)
        self.aus_col = QTableWidgetItem(_('Author sort'))
        self.table.setHorizontalHeaderItem(1, self.aus_col)
        self.aus_col.setIcon(self.up_arrow_icon)
        self.aul_col = QTableWidgetItem(_('Link'))
        self.table.setHorizontalHeaderItem(2, self.aul_col)
        self.aus_col.setIcon(self.blank_icon)

        # Add the data
        self.authors = {}
        auts = db.get_authors_with_ids()
        self.table.setRowCount(len(auts))
        select_item = None
        for row, (id, author, sort, link) in enumerate(auts):
            author = author.replace('|', ',')
            self.authors[id] = (author, sort, link)
            aut = tableItem(author)
            aut.setData(Qt.UserRole, id)
            sort = tableItem(sort)
            link = tableItem(link)
            self.table.setItem(row, 0, aut)
            self.table.setItem(row, 1, sort)
            self.table.setItem(row, 2, link)
            if id == id_to_select:
                if select_sort:
                    select_item = sort
                elif select_link:
                    select_item = link
                else:
                    select_item = aut
        self.table.resizeColumnsToContents()
        if self.table.columnWidth(2) < 200:
            self.table.setColumnWidth(2, 200)

        # set up the cellChanged signal only after the table is filled
        self.table.cellChanged.connect(self.cell_changed)

        # set up sort buttons
        self.sort_by_author.setCheckable(True)
        self.sort_by_author.setChecked(False)
        self.sort_by_author.clicked.connect(self.do_sort_by_author)
        self.author_order = 1

        self.table.sortByColumn(1, Qt.AscendingOrder)
        self.sort_by_author_sort.clicked.connect(self.do_sort_by_author_sort)
        self.sort_by_author_sort.setCheckable(True)
        self.sort_by_author_sort.setChecked(True)
        self.author_sort_order = 1

        self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort)
        self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author)

        # Position on the desired item
        if select_item is not None:
            self.table.setCurrentItem(select_item)
            self.table.editItem(select_item)
            self.start_find_pos = select_item.row() * 2 + select_item.column()
        else:
            self.table.setCurrentCell(0, 0)
            self.start_find_pos = -1

        # set up the search box
        self.find_box.initialize('manage_authors_search')
        self.find_box.lineEdit().returnPressed.connect(self.do_find)
        self.find_box.editTextChanged.connect(self.find_text_changed)
        self.find_button.clicked.connect(self.do_find)

        l = QLabel(self.table)
        self.not_found_label = l
        l.setFrameStyle(QFrame.StyledPanel)
        l.setAutoFillBackground(True)
        l.setText(_('No matches found'))
        l.setAlignment(Qt.AlignVCenter)
        l.resize(l.sizeHint())
        l.move(10,20)
        l.setVisible(False)
        self.not_found_label.move(40, 40)
        self.not_found_label_timer = QTimer()
        self.not_found_label_timer.setSingleShot(True)
        self.not_found_label_timer.timeout.connect(
                self.not_found_label_timer_event, type=Qt.QueuedConnection)

        self.table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.table.customContextMenuRequested .connect(self.show_context_menu)

    def save_state(self):
        self.table_column_widths = []
        for c in range(0, self.table.columnCount()):
            self.table_column_widths.append(self.table.columnWidth(c))
        gprefs['manage_authors_table_widths'] = self.table_column_widths
        gprefs['manage_authors_dialog_geometry'] = bytearray(self.saveGeometry())

    def resizeEvent(self, *args):
        QDialog.resizeEvent(self, *args)
        if self.table_column_widths is not None:
            for c,w in enumerate(self.table_column_widths):
                self.table.setColumnWidth(c, w)
        else:
            # the vertical scroll bar might not be rendered, so might not yet
            # have a width. Assume 25. Not a problem because user-changed column
            # widths will be remembered
            w = self.table.width() - 25 - self.table.verticalHeader().width()
            w /= self.table.columnCount()
            for c in range(0, self.table.columnCount()):
                self.table.setColumnWidth(c, w)
        self.save_state()

    def show_context_menu(self, point):
        self.context_item = self.table.itemAt(point)
        case_menu = QMenu(_('Change Case'))
        action_upper_case = case_menu.addAction(_('Upper Case'))
        action_lower_case = case_menu.addAction(_('Lower Case'))
        action_swap_case = case_menu.addAction(_('Swap Case'))
        action_title_case = case_menu.addAction(_('Title Case'))
        action_capitalize = case_menu.addAction(_('Capitalize'))

        action_upper_case.triggered.connect(self.upper_case)
        action_lower_case.triggered.connect(self.lower_case)
        action_swap_case.triggered.connect(self.swap_case)
        action_title_case.triggered.connect(self.title_case)
        action_capitalize.triggered.connect(self.capitalize)

        m = self.au_context_menu = QMenu()
        ca = m.addAction(_('Copy'))
        ca.triggered.connect(self.copy_to_clipboard)
        ca = m.addAction(_('Paste'))
        ca.triggered.connect(self.paste_from_clipboard)
        m.addSeparator()

        if self.context_item is not None and self.context_item.column() == 0:
            ca = m.addAction(_('Copy to author sort'))
            ca.triggered.connect(self.copy_au_to_aus)
        else:
            ca = m.addAction(_('Copy to author'))
            ca.triggered.connect(self.copy_aus_to_au)
        m.addSeparator()
        m.addMenu(case_menu)
        m.exec_(self.table.mapToGlobal(point))

    def copy_to_clipboard(self):
        cb = QApplication.clipboard()
        cb.setText(unicode(self.context_item.text()))

    def paste_from_clipboard(self):
        cb = QApplication.clipboard()
        self.context_item.setText(cb.text())

    def upper_case(self):
        self.context_item.setText(icu_upper(unicode(self.context_item.text())))

    def lower_case(self):
        self.context_item.setText(icu_lower(unicode(self.context_item.text())))

    def swap_case(self):
        self.context_item.setText(unicode(self.context_item.text()).swapcase())

    def title_case(self):
        from calibre.utils.titlecase import titlecase
        self.context_item.setText(titlecase(unicode(self.context_item.text())))

    def capitalize(self):
        from calibre.utils.icu import capitalize
        self.context_item.setText(capitalize(unicode(self.context_item.text())))

    def copy_aus_to_au(self):
        row = self.context_item.row()
        dest = self.table.item(row, 0)
        dest.setText(self.context_item.text())

    def copy_au_to_aus(self):
        row = self.context_item.row()
        dest = self.table.item(row, 1)
        dest.setText(self.context_item.text())

    def not_found_label_timer_event(self):
        self.not_found_label.setVisible(False)

    def find_text_changed(self):
        self.start_find_pos = -1

    def do_find(self):
        self.not_found_label.setVisible(False)
        # For some reason the button box keeps stealing the RETURN shortcut.
        # Steal it back
        self.buttonBox.button(QDialogButtonBox.Ok).setDefault(False)
        self.buttonBox.button(QDialogButtonBox.Ok).setAutoDefault(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False)
        st = icu_lower(unicode(self.find_box.currentText()))

        for i in range(0, self.table.rowCount()*2):
            self.start_find_pos = (self.start_find_pos + 1) % (self.table.rowCount()*2)
            r = (self.start_find_pos/2)%self.table.rowCount()
            c = self.start_find_pos % 2
            item = self.table.item(r, c)
            text = icu_lower(unicode(item.text()))
            if st in text:
                self.table.setCurrentItem(item)
                self.table.setFocus(True)
                return
        # Nothing found. Pop up the little dialog for 1.5 seconds
        self.not_found_label.setVisible(True)
        self.not_found_label_timer.start(1500)

    def do_sort_by_author(self):
        self.author_order = 1 if self.author_order == 0 else 0
        self.table.sortByColumn(0, self.author_order)
        self.sort_by_author.setChecked(True)
        self.sort_by_author_sort.setChecked(False)
        self.auth_col.setIcon(self.down_arrow_icon if self.author_order
                                                    else self.up_arrow_icon)
        self.aus_col.setIcon(self.blank_icon)

    def do_sort_by_author_sort(self):
        self.author_sort_order = 1 if self.author_sort_order == 0 else 0
        self.table.sortByColumn(1, self.author_sort_order)
        self.sort_by_author.setChecked(False)
        self.sort_by_author_sort.setChecked(True)
        self.aus_col.setIcon(self.down_arrow_icon if self.author_sort_order
                                                    else self.up_arrow_icon)
        self.auth_col.setIcon(self.blank_icon)

    def accepted(self):
        self.save_state()
        self.result = []
        for row in range(0,self.table.rowCount()):
            id   = self.table.item(row, 0).data(Qt.UserRole).toInt()[0]
            aut  = unicode(self.table.item(row, 0).text()).strip()
            sort = unicode(self.table.item(row, 1).text()).strip()
            link = unicode(self.table.item(row, 2).text()).strip()
            orig_aut,orig_sort,orig_link = self.authors[id]
            if orig_aut != aut or orig_sort != sort or orig_link != link:
                self.result.append((id, orig_aut, aut, sort, link))

    def do_recalc_author_sort(self):
        self.table.cellChanged.disconnect()
        for row in range(0,self.table.rowCount()):
            item = self.table.item(row, 0)
            aut  = unicode(item.text()).strip()
            c = self.table.item(row, 1)
            # Sometimes trailing commas are left by changing between copy algs
            c.setText(author_to_author_sort(aut).rstrip(','))
        self.table.setFocus(Qt.OtherFocusReason)
        self.table.cellChanged.connect(self.cell_changed)

    def do_auth_sort_to_author(self):
        self.table.cellChanged.disconnect()
        for row in range(0,self.table.rowCount()):
            item = self.table.item(row, 1)
            aus  = unicode(item.text()).strip()
            c = self.table.item(row, 0)
            # Sometimes trailing commas are left by changing between copy algs
            c.setText(aus)
        self.table.setFocus(Qt.OtherFocusReason)
        self.table.cellChanged.connect(self.cell_changed)

    def cell_changed(self, row, col):
        if col == 0:
            item = self.table.item(row, 0)
            aut  = unicode(item.text()).strip()
            amper = aut.find('&')
            if amper >= 0:
                error_dialog(self.parent(), _('Invalid author name'),
                        _('Author names cannot contain & characters.')).exec_()
                aut = aut.replace('&', '%')
                self.table.item(row, 0).setText(aut)
            c = self.table.item(row, 1)
            c.setText(author_to_author_sort(aut))
            item = c
        else:
            item  = self.table.item(row, col)
        self.table.setCurrentItem(item)
        self.table.scrollToItem(item)
示例#27
0
class LiveCSS(QWidget):

    goto_declaration = pyqtSignal(object)

    def __init__(self, preview, parent=None):
        QWidget.__init__(self, parent)
        self.preview = preview
        self.preview_is_refreshing = False
        self.refresh_needed = False
        preview.refresh_starting.connect(self.preview_refresh_starting)
        preview.refreshed.connect(self.preview_refreshed)
        self.apply_theme()
        self.setAutoFillBackground(True)
        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_data)
        self.update_timer.setSingleShot(True)
        self.update_timer.setInterval(500)
        self.now_showing = (None, None, None)

        self.stack = s = QStackedLayout(self)
        self.setLayout(s)

        self.clear_label = la = QLabel('<h3>' + _(
            'No style information found') + '</h3><p>' + _(
                'Move the cursor inside a HTML tag to see what styles'
                ' apply to that tag.'))
        la.setWordWrap(True)
        la.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        s.addWidget(la)

        self.box = box = Box(self)
        box.hyperlink_activated.connect(self.goto_declaration, type=Qt.QueuedConnection)
        self.scroll = sc = QScrollArea(self)
        sc.setWidget(box)
        sc.setWidgetResizable(True)
        s.addWidget(sc)

    def preview_refresh_starting(self):
        self.preview_is_refreshing = True

    def preview_refreshed(self):
        self.preview_is_refreshing = False
        # We must let the event loop run otherwise the webview will return
        # stale data in read_data()
        self.refresh_needed = True
        self.start_update_timer()

    def apply_theme(self):
        f = self.font()
        f.setFamily(tprefs['editor_font_family'] or default_font_family())
        f.setPointSize(tprefs['editor_font_size'])
        self.setFont(f)
        theme = get_theme(tprefs['editor_theme'])
        pal = self.palette()
        pal.setColor(pal.Window, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.WindowText, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.AlternateBase, theme_color(theme, 'HighlightRegion', 'bg'))
        pal.setColor(pal.LinkVisited, theme_color(theme, 'Keyword', 'fg'))
        self.setPalette(pal)
        if hasattr(self, 'box'):
            self.box.relayout()
        self.update()

    def clear(self):
        self.stack.setCurrentIndex(0)

    def show_data(self, editor_name, sourceline, tags):
        if self.preview_is_refreshing:
            return
        if sourceline is None:
            self.clear()
        else:
            data = self.read_data(sourceline, tags)
            if data is None or len(data['computed_css']) < 1:
                if editor_name == self.current_name and (editor_name, sourceline, tags) == self.now_showing:
                    # Try again in a little while in case there was a transient
                    # error in the web view
                    self.start_update_timer()
                    return
                if self.now_showing == (None, None, None) or self.now_showing[0] != self.current_name:
                    self.clear()
                    return
                # Try to refresh the data for the currently shown tag instead
                # of clearing
                editor_name, sourceline, tags = self.now_showing
                data = self.read_data(sourceline, tags)
                if data is None or len(data['computed_css']) < 1:
                    self.clear()
                    return
            self.now_showing = (editor_name, sourceline, tags)
            data['html_name'] = editor_name
            self.box.show_data(data)
            self.refresh_needed = False
            self.stack.setCurrentIndex(1)

    def read_data(self, sourceline, tags):
        mf = self.preview.view.page().mainFrame()
        tags = [x.lower() for x in tags]
        result = unicode(mf.evaluateJavaScript(
            'window.calibre_preview_integration.live_css(%s, %s)' % (
                json.dumps(sourceline), json.dumps(tags))).toString())
        result = json.loads(result)
        if result is not None:
            maximum_specificities = {}
            for node in result['nodes']:
                is_ancestor = node['is_ancestor']
                for rule in node['css']:
                    self.process_rule(rule, is_ancestor, maximum_specificities)
            for node in result['nodes']:
                for rule in node['css']:
                    for prop in rule['properties']:
                        if prop.specificity < maximum_specificities[prop.name]:
                            prop.is_overriden = True

        return result

    def process_rule(self, rule, is_ancestor, maximum_specificities):
        selector = rule['selector']
        sheet_index = rule['sheet_index']
        rule_address = rule['rule_address'] or ()
        if selector is not None:
            try:
                specificity = [0] + list(parse(selector)[0].specificity())
            except (AttributeError, TypeError):
                specificity = [0, 0, 0, 0]
        else:  # style attribute
            specificity = [1, 0, 0, 0]
        specificity.extend((sheet_index, tuple(rule_address)))
        ancestor_specificity = 0 if is_ancestor else 1
        properties = []
        for prop in rule['properties']:
            important = 1 if prop[-1] == 'important' else 0
            p = Property(prop, [ancestor_specificity] + [important] + specificity)
            properties.append(p)
            if p.specificity > maximum_specificities.get(p.name, (0,0,0,0,0,0)):
                maximum_specificities[p.name] = p.specificity
        rule['properties'] = properties

        href = rule['href']
        if hasattr(href, 'startswith') and href.startswith('file://'):
            href = href[len('file://'):]
            if iswindows and href.startswith('/'):
                href = href[1:]
            if href:
                rule['href'] = current_container().abspath_to_name(href, root=self.preview.current_root)

    @property
    def current_name(self):
        return self.preview.current_name

    @property
    def is_visible(self):
        return self.isVisible()

    def showEvent(self, ev):
        self.update_timer.start()
        actions['auto-reload-preview'].setEnabled(True)
        return QWidget.showEvent(self, ev)

    def sync_to_editor(self, name):
        self.start_update_timer()

    def update_data(self):
        if not self.is_visible or self.preview_is_refreshing:
            return
        editor_name = self.current_name
        ed = editors.get(editor_name, None)
        if self.update_timer.isActive() or (ed is None and editor_name is not None):
            return QTimer.singleShot(100, self.update_data)
        if ed is not None:
            sourceline, tags = ed.current_tag()
            if self.refresh_needed or self.now_showing != (editor_name, sourceline, tags):
                self.show_data(editor_name, sourceline, tags)

    def start_update_timer(self):
        if self.is_visible:
            self.update_timer.start()

    def stop_update_timer(self):
        self.update_timer.stop()

    def navigate_to_declaration(self, data, editor):
        if data['type'] == 'inline':
            sourceline, tags = data['sourceline_address']
            editor.goto_sourceline(sourceline, tags, attribute='style')
        elif data['type'] == 'sheet':
            editor.goto_css_rule(data['rule_address'])
        elif data['type'] == 'elem':
            editor.goto_css_rule(data['rule_address'], sourceline_address=data['sourceline_address'])
示例#28
0
文件: main.py 项目: kmshi/calibre
class EbookViewer(MainWindow, Ui_EbookViewer):

    STATE_VERSION = 1
    FLOW_MODE_TT = _('Switch to paged mode - where the text is broken up '
            'into pages like a paper book')
    PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up '
            'into pages')

    def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None,
                 start_in_fullscreen=False):
        MainWindow.__init__(self, None)
        self.setupUi(self)
        self.view.initialize_view(debug_javascript)
        self.view.magnification_changed.connect(self.magnification_changed)
        self.show_toc_on_open = False
        self.current_book_has_toc = False
        self.base_window_title = unicode(self.windowTitle())
        self.iterator          = None
        self.current_page      = None
        self.pending_search    = None
        self.pending_search_dir= None
        self.pending_anchor    = None
        self.pending_reference = None
        self.pending_bookmark  = None
        self.pending_restore   = False
        self.existing_bookmarks= []
        self.selected_text     = None
        self.was_maximized     = False
        self.read_settings()
        self.dictionary_box.hide()
        self.close_dictionary_view.clicked.connect(lambda
                x:self.dictionary_box.hide())
        self.history = History(self.action_back, self.action_forward)
        self.metadata = Metadata(self)
        self.pos = DoubleSpinBox()
        self.pos.setDecimals(1)
        self.pos.setSuffix('/'+_('Unknown')+'     ')
        self.pos.setMinimum(1.)
        self.pos.value_changed.connect(self.update_pos_label)
        self.splitter.setCollapsible(0, False)
        self.splitter.setCollapsible(1, False)
        self.pos.setMinimumWidth(150)
        self.tool_bar2.insertWidget(self.action_find_next, self.pos)
        self.reference = Reference()
        self.tool_bar2.insertSeparator(self.action_find_next)
        self.tool_bar2.insertWidget(self.action_find_next, self.reference)
        self.tool_bar2.insertSeparator(self.action_find_next)
        self.setFocusPolicy(Qt.StrongFocus)
        self.search = SearchBox2(self)
        self.search.setMinimumContentsLength(20)
        self.search.initialize('viewer_search_history')
        self.search.setToolTip(_('Search for text in book'))
        self.search.setMinimumWidth(200)
        self.tool_bar2.insertWidget(self.action_find_next, self.search)
        self.view.set_manager(self)
        self.pi = ProgressIndicator(self)
        self.toc.setVisible(False)
        self.action_quit = QAction(_('&Quit'), self)
        self.addAction(self.action_quit)
        self.view_resized_timer = QTimer(self)
        self.view_resized_timer.timeout.connect(self.viewport_resize_finished)
        self.view_resized_timer.setSingleShot(True)
        self.resize_in_progress = False
        self.action_quit.triggered.connect(self.quit)
        self.action_copy.setDisabled(True)
        self.action_metadata.setCheckable(True)
        self.action_table_of_contents.setCheckable(True)
        self.toc.setMinimumWidth(80)
        self.action_reference_mode.setCheckable(True)
        self.action_reference_mode.triggered[bool].connect(self.view.reference_mode)
        self.action_metadata.triggered[bool].connect(self.metadata.setVisible)
        self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible)
        self.action_copy.triggered[bool].connect(self.copy)
        self.action_font_size_larger.triggered.connect(self.font_size_larger)
        self.action_font_size_smaller.triggered.connect(self.font_size_smaller)
        self.action_open_ebook.triggered[bool].connect(self.open_ebook)
        self.action_next_page.triggered.connect(self.view.next_page)
        self.action_previous_page.triggered.connect(self.view.previous_page)
        self.action_find_next.triggered.connect(self.find_next)
        self.action_find_previous.triggered.connect(self.find_previous)
        self.action_full_screen.triggered[bool].connect(self.toggle_fullscreen)
        self.action_full_screen.setToolTip(_('Toggle full screen [%s]') %
                _(' or ').join([x for x in self.view.shortcuts.get_shortcuts('Fullscreen')]))
        self.action_back.triggered[bool].connect(self.back)
        self.action_forward.triggered[bool].connect(self.forward)
        self.action_preferences.triggered.connect(self.do_config)
        self.pos.editingFinished.connect(self.goto_page_num)
        self.vertical_scrollbar.valueChanged[int].connect(lambda
                x:self.goto_page(x/100.))
        self.search.search.connect(self.find)
        self.search.focus_to_library.connect(lambda: self.view.setFocus(Qt.OtherFocusReason))
        self.toc.pressed[QModelIndex].connect(self.toc_clicked)
        self.reference.goto.connect(self.goto)

        self.bookmarks_menu = QMenu()
        self.action_bookmark.setMenu(self.bookmarks_menu)
        self.set_bookmarks([])

        self.themes_menu = QMenu()
        self.action_load_theme.setMenu(self.themes_menu)
        self.tool_bar.widgetForAction(self.action_load_theme).setPopupMode(QToolButton.InstantPopup)
        self.load_theme_menu()

        if pathtoebook is not None:
            f = functools.partial(self.load_ebook, pathtoebook, open_at=open_at)
            QTimer.singleShot(50, f)
        self.view.setMinimumSize(100, 100)
        self.toc.setCursor(Qt.PointingHandCursor)
        self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
        self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu)
        self.tool_bar.widgetForAction(self.action_bookmark).setPopupMode(QToolButton.InstantPopup)
        self.action_full_screen.setCheckable(True)
        self.full_screen_label = QLabel('''
                <center>
                <h1>%s</h1>
                <h3>%s</h3>
                <h3>%s</h3>
                <h3>%s</h3>
                </center>
                '''%(_('Full screen mode'),
                    _('Right click to show controls'),
                    _('Tap in the left or right page margin to turn pages'),
                    _('Press Esc to quit')),
                    self)
        self.full_screen_label.setVisible(False)
        self.full_screen_label.setStyleSheet('''
        QLabel {
            text-align: center;
            background-color: white;
            color: black;
            border-width: 1px;
            border-style: solid;
            border-radius: 20px;
        }
        ''')
        self.window_mode_changed = None
        self.toggle_toolbar_action = QAction(_('Show/hide controls'), self)
        self.toggle_toolbar_action.setCheckable(True)
        self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars)
        self.toolbar_hidden = None
        self.addAction(self.toggle_toolbar_action)
        self.full_screen_label_anim = QPropertyAnimation(
                self.full_screen_label, 'size')
        self.clock_label = QLabel('99:99', self)
        self.clock_label.setVisible(False)
        self.clock_label.setFocusPolicy(Qt.NoFocus)
        self.info_label_style = '''
            QLabel {
                text-align: center;
                border-width: 1px;
                border-style: solid;
                border-radius: 8px;
                background-color: %s;
                color: %s;
                font-family: monospace;
                font-size: larger;
                padding: 5px;
        }'''
        self.original_frame_style = self.frame.frameStyle()
        self.pos_label = QLabel('2000/4000', self)
        self.pos_label.setVisible(False)
        self.pos_label.setFocusPolicy(Qt.NoFocus)
        self.clock_timer = QTimer(self)
        self.clock_timer.timeout.connect(self.update_clock)

        self.print_menu = QMenu()
        self.print_menu.addAction(QIcon(I('print-preview.png')), _('Print Preview'))
        self.action_print.setMenu(self.print_menu)
        self.tool_bar.widgetForAction(self.action_print).setPopupMode(QToolButton.MenuButtonPopup)
        self.action_print.triggered.connect(self.print_book)
        self.print_menu.actions()[0].triggered.connect(self.print_preview)
        self.open_history_menu = QMenu()
        self.clear_recent_history_action = QAction(
                _('Clear list of recently opened books'), self)
        self.clear_recent_history_action.triggered.connect(self.clear_recent_history)
        self.build_recent_menu()
        self.action_open_ebook.setMenu(self.open_history_menu)
        self.open_history_menu.triggered[QAction].connect(self.open_recent)
        w = self.tool_bar.widgetForAction(self.action_open_ebook)
        w.setPopupMode(QToolButton.MenuButtonPopup)

        for x in ('tool_bar', 'tool_bar2'):
            x = getattr(self, x)
            for action in x.actions():
                # So that the keyboard shortcuts for these actions will
                # continue to function even when the toolbars are hidden
                self.addAction(action)

        for plugin in self.view.document.all_viewer_plugins:
            plugin.customize_ui(self)
        self.view.document.settings_changed.connect(self.settings_changed)

        self.restore_state()
        self.settings_changed()
        self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode)
        if (start_in_fullscreen or self.view.document.start_in_fullscreen):
            self.action_full_screen.trigger()

    def toggle_paged_mode(self, checked, at_start=False):
        in_paged_mode = not self.action_toggle_paged_mode.isChecked()
        self.view.document.in_paged_mode = in_paged_mode
        self.action_toggle_paged_mode.setToolTip(self.FLOW_MODE_TT if
                self.action_toggle_paged_mode.isChecked() else
                self.PAGED_MODE_TT)
        if at_start:
            return
        self.reload()

    def settings_changed(self):
        for x in ('', '2'):
            x = getattr(self, 'tool_bar'+x)
            x.setVisible(self.view.document.show_controls)

    def reload(self):
        if hasattr(self, 'current_index') and self.current_index > -1:
            self.view.document.page_position.save(overwrite=False)
            self.pending_restore = True
            self.load_path(self.view.last_loaded_path)

    def set_toc_visible(self, yes):
        self.toc.setVisible(yes)

    def clear_recent_history(self, *args):
        vprefs.set('viewer_open_history', [])
        self.build_recent_menu()

    def build_recent_menu(self):
        m = self.open_history_menu
        m.clear()
        recent = vprefs.get('viewer_open_history', [])
        if recent:
            m.addAction(self.clear_recent_history_action)
            m.addSeparator()
        count = 0
        for path in recent:
            if count > 9:
                break
            if os.path.exists(path):
                m.addAction(RecentAction(path, m))
                count += 1

    def shutdown(self):
        if self.isFullScreen() and not self.view.document.start_in_fullscreen:
            self.action_full_screen.trigger()
            return False
        self.save_state()
        return True

    def quit(self):
        if self.shutdown():
            QApplication.instance().quit()

    def closeEvent(self, e):
        if self.shutdown():
            return MainWindow.closeEvent(self, e)
        else:
            e.ignore()

    def toggle_toolbars(self):
        for x in ('tool_bar', 'tool_bar2'):
            x = getattr(self, x)
            x.setVisible(not x.isVisible())

    def save_state(self):
        state = bytearray(self.saveState(self.STATE_VERSION))
        vprefs['viewer_toolbar_state'] = state
        if not self.isFullScreen():
            vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry()))
        if self.current_book_has_toc:
            vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible()))
        if self.toc.isVisible():
            vprefs.set('viewer_splitter_state',
                bytearray(self.splitter.saveState()))
        vprefs['multiplier'] = self.view.multiplier
        vprefs['in_paged_mode'] = not self.action_toggle_paged_mode.isChecked()

    def restore_state(self):
        state = vprefs.get('viewer_toolbar_state', None)
        if state is not None:
            try:
                state = QByteArray(state)
                self.restoreState(state, self.STATE_VERSION)
            except:
                pass
        mult = vprefs.get('multiplier', None)
        if mult:
            self.view.multiplier = mult
        # On windows Qt lets the user hide toolbars via a right click in a very
        # specific location, ensure they are visible.
        self.tool_bar.setVisible(True)
        self.tool_bar2.setVisible(True)
        self.action_toggle_paged_mode.setChecked(not vprefs.get('in_paged_mode',
            True))
        self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(),
                at_start=True)

    def lookup(self, word):
        from calibre.gui2.viewer.documentview import config
        opts = config().parse()
        settings = self.dictionary_view.page().settings()
        settings.setFontSize(settings.DefaultFontSize, opts.default_font_size)
        settings.setFontSize(settings.DefaultFixedFontSize, opts.mono_font_size)
        self.dictionary_view.setHtml('<html><body><p>'+
            _('Connecting to dict.org to lookup: <b>%s</b>&hellip;')%word +
            '</p></body></html>')
        self.dictionary_box.show()
        self._lookup = Lookup(word, parent=self)
        self._lookup.finished.connect(self.looked_up)
        self._lookup.start()

    def looked_up(self, *args):
        html = self._lookup.html_result
        self._lookup = None
        self.dictionary_view.setHtml(html)

    def get_remember_current_page_opt(self):
        from calibre.gui2.viewer.documentview import config
        c = config().parse()
        return c.remember_current_page

    def print_book(self):
        p = Printing(self.iterator, self)
        p.start_print()

    def print_preview(self):
        p = Printing(self.iterator, self)
        p.start_preview()

    def toggle_fullscreen(self):
        if self.isFullScreen():
            self.showNormal()
        else:
            self.showFullScreen()

    def showFullScreen(self):
        self.view.document.page_position.save()
        self.window_mode_changed = 'fullscreen'
        self.tool_bar.setVisible(False)
        self.tool_bar2.setVisible(False)
        self.was_maximized = self.isMaximized()
        if not self.view.document.fullscreen_scrollbar:
            self.vertical_scrollbar.setVisible(False)
            self.frame.layout().setSpacing(0)
        self._original_frame_margins = (
            self.centralwidget.layout().contentsMargins(),
            self.frame.layout().contentsMargins())
        self.frame.layout().setContentsMargins(0, 0, 0, 0)
        self.centralwidget.layout().setContentsMargins(0, 0, 0, 0)
        self.frame.setFrameStyle(self.frame.NoFrame|self.frame.Plain)

        super(EbookViewer, self).showFullScreen()

    def show_full_screen_label(self):
        f = self.full_screen_label
        height = 200
        width = int(0.7*self.view.width())
        f.resize(width, height)
        f.move((self.view.width() - width)//2, (self.view.height()-height)//2)
        if self.view.document.show_fullscreen_help:
            f.setVisible(True)
            a = self.full_screen_label_anim
            a.setDuration(500)
            a.setStartValue(QSize(width, 0))
            a.setEndValue(QSize(width, height))
            a.start()
            QTimer.singleShot(3500, self.full_screen_label.hide)
        self.view.document.switch_to_fullscreen_mode()
        if self.view.document.fullscreen_clock:
            self.show_clock()
        if self.view.document.fullscreen_pos:
            self.show_pos_label()

    def show_clock(self):
        self.clock_label.setVisible(True)
        self.clock_label.setText(QTime(22, 33,
            33).toString(Qt.SystemLocaleShortDate))
        self.clock_timer.start(1000)
        self.clock_label.setStyleSheet(self.info_label_style%(
                'rgba(0, 0, 0, 0)', self.view.document.colors()[1]))
        self.clock_label.resize(self.clock_label.sizeHint())
        sw = QApplication.desktop().screenGeometry(self.view)
        vswidth = (self.vertical_scrollbar.width() if
                self.vertical_scrollbar.isVisible() else 0)
        self.clock_label.move(sw.width() - vswidth - 15
                - self.clock_label.width(), sw.height() -
                self.clock_label.height()-10)
        self.update_clock()

    def show_pos_label(self):
        self.pos_label.setVisible(True)
        self.pos_label.setStyleSheet(self.info_label_style%(
                'rgba(0, 0, 0, 0)', self.view.document.colors()[1]))
        sw = QApplication.desktop().screenGeometry(self.view)
        self.pos_label.move(15, sw.height() - self.pos_label.height()-10)
        self.update_pos_label()

    def update_clock(self):
        self.clock_label.setText(QTime.currentTime().toString(Qt.SystemLocaleShortDate))

    def update_pos_label(self, *args):
        if self.pos_label.isVisible():
            try:
                value, maximum = args
            except:
                value, maximum = self.pos.value(), self.pos.maximum()
            text = '%g/%g'%(value, maximum)
            self.pos_label.setText(text)
            self.pos_label.resize(self.pos_label.sizeHint())

    def showNormal(self):
        self.view.document.page_position.save()
        self.clock_label.setVisible(False)
        self.pos_label.setVisible(False)
        self.frame.setFrameStyle(self.original_frame_style)
        self.frame.layout().setSpacing(-1)
        self.clock_timer.stop()
        self.vertical_scrollbar.setVisible(True)
        self.window_mode_changed = 'normal'
        self.settings_changed()
        self.full_screen_label.setVisible(False)
        if hasattr(self, '_original_frame_margins'):
            om = self._original_frame_margins
            self.centralwidget.layout().setContentsMargins(om[0])
            self.frame.layout().setContentsMargins(om[1])
        if self.was_maximized:
            super(EbookViewer, self).showMaximized()
        else:
            super(EbookViewer, self).showNormal()

    def handle_window_mode_toggle(self):
        if self.window_mode_changed:
            fs = self.window_mode_changed == 'fullscreen'
            self.window_mode_changed = None
            if fs:
                self.show_full_screen_label()
            else:
                self.view.document.switch_to_window_mode()
            self.view.document.page_position.restore()
            self.scrolled(self.view.scroll_fraction)

    def goto(self, ref):
        if ref:
            tokens = ref.split('.')
            if len(tokens) > 1:
                spine_index = int(tokens[0]) -1
                if spine_index == self.current_index:
                    self.view.goto(ref)
                else:
                    self.pending_reference = ref
                    self.load_path(self.iterator.spine[spine_index])

    def goto_bookmark(self, bm):
        spine_index = bm['spine']
        if spine_index > -1 and self.current_index == spine_index:
            if self.resize_in_progress:
                self.view.document.page_position.set_pos(bm['pos'])
            else:
                self.view.goto_bookmark(bm)
        else:
            self.pending_bookmark = bm
            if spine_index < 0 or spine_index >= len(self.iterator.spine):
                spine_index = 0
                self.pending_bookmark = None
            self.load_path(self.iterator.spine[spine_index])

    def toc_clicked(self, index, force=False):
        if force or QApplication.mouseButtons() & Qt.LeftButton:
            item = self.toc_model.itemFromIndex(index)
            if item.abspath is not None:
                if not os.path.exists(item.abspath):
                    return error_dialog(self, _('No such location'),
                            _('The location pointed to by this item'
                                ' does not exist.'), det_msg=item.abspath, show=True)
                url = QUrl.fromLocalFile(item.abspath)
                if item.fragment:
                    url.setFragment(item.fragment)
                self.link_clicked(url)
        self.view.setFocus(Qt.OtherFocusReason)

    def selection_changed(self, selected_text):
        self.selected_text = selected_text.strip()
        self.action_copy.setEnabled(bool(self.selected_text))

    def copy(self, x):
        if self.selected_text:
            QApplication.clipboard().setText(self.selected_text)

    def back(self, x):
        pos = self.history.back(self.pos.value())
        if pos is not None:
            self.goto_page(pos)

    def goto_page_num(self):
        num = self.pos.value()
        self.goto_page(num)

    def forward(self, x):
        pos = self.history.forward(self.pos.value())
        if pos is not None:
            self.goto_page(pos)

    def goto_start(self):
        self.goto_page(1)

    def goto_end(self):
        self.goto_page(self.pos.maximum())

    def goto_page(self, new_page, loaded_check=True):
        if self.current_page is not None or not loaded_check:
            for page in self.iterator.spine:
                if new_page >= page.start_page and new_page <= page.max_page:
                    try:
                        frac = float(new_page-page.start_page)/(page.pages-1)
                    except ZeroDivisionError:
                        frac = 0
                    if page == self.current_page:
                        self.view.scroll_to(frac)
                    else:
                        self.load_path(page, pos=frac)

    def open_ebook(self, checked):
        files = choose_files(self, 'ebook viewer open dialog',
                     _('Choose ebook'),
                     [(_('Ebooks'), available_input_formats())],
                     all_files=False,
                     select_only_single_file=True)
        if files:
            self.load_ebook(files[0])

    def open_recent(self, action):
        self.load_ebook(action.path)

    def font_size_larger(self):
        self.view.magnify_fonts()

    def font_size_smaller(self):
        self.view.shrink_fonts()

    def magnification_changed(self, val):
        tt = '%(action)s [%(sc)s]\n'+_('Current magnification: %(mag).1f')
        sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Font larger'))
        self.action_font_size_larger.setToolTip(
                tt %dict(action=unicode(self.action_font_size_larger.text()),
                         mag=val, sc=sc))
        sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Font smaller'))
        self.action_font_size_smaller.setToolTip(
                tt %dict(action=unicode(self.action_font_size_smaller.text()),
                         mag=val, sc=sc))
        self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
        self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)

    def find(self, text, repeat=False, backwards=False):
        if not text:
            self.view.search('')
            return self.search.search_done(False)
        if self.view.search(text, backwards=backwards):
            self.scrolled(self.view.scroll_fraction)
            return self.search.search_done(True)
        index = self.iterator.search(text, self.current_index,
                backwards=backwards)
        if index is None:
            if self.current_index > 0:
                index = self.iterator.search(text, 0)
                if index is None:
                    info_dialog(self, _('No matches found'),
                                _('No matches found for: %s')%text).exec_()
                    return self.search.search_done(True)
            return self.search.search_done(True)
        self.pending_search = text
        self.pending_search_dir = 'backwards' if backwards else 'forwards'
        self.load_path(self.iterator.spine[index])

    def find_next(self):
        self.find(unicode(self.search.text()), repeat=True)

    def find_previous(self):
        self.find(unicode(self.search.text()), repeat=True, backwards=True)

    def do_search(self, text, backwards):
        self.pending_search = None
        self.pending_search_dir = None
        if self.view.search(text, backwards=backwards):
            self.scrolled(self.view.scroll_fraction)

    def internal_link_clicked(self, frac):
        self.update_page_number()  # Ensure page number is accurate as it is used for history
        self.history.add(self.pos.value())

    def link_clicked(self, url):
        path = os.path.abspath(unicode(url.toLocalFile()))
        frag = None
        if path in self.iterator.spine:
            self.update_page_number()  # Ensure page number is accurate as it is used for history
            self.history.add(self.pos.value())
            path = self.iterator.spine[self.iterator.spine.index(path)]
            if url.hasFragment():
                frag = unicode(url.fragment())
            if path != self.current_page:
                self.pending_anchor = frag
                self.load_path(path)
            else:
                oldpos = self.view.document.ypos
                if frag:
                    self.view.scroll_to(frag)
                else:
                    # Scroll to top
                    self.view.scroll_to(0)
                if self.view.document.ypos == oldpos:
                    # If we are coming from goto_next_section() call this will
                    # cause another goto next section call with the next toc
                    # entry, since this one did not cause any scrolling at all.
                    QTimer.singleShot(10, self.update_indexing_state)
        else:
            open_url(url)

    def load_started(self):
        self.open_progress_indicator(_('Loading flow...'))

    def load_finished(self, ok):
        self.close_progress_indicator()
        path = self.view.path()
        try:
            index = self.iterator.spine.index(path)
        except (ValueError, AttributeError):
            return -1
        self.current_page = self.iterator.spine[index]
        self.current_index = index
        self.set_page_number(self.view.scroll_fraction)
        QTimer.singleShot(100, self.update_indexing_state)
        if self.pending_search is not None:
            self.do_search(self.pending_search,
                    self.pending_search_dir=='backwards')
            self.pending_search = None
            self.pending_search_dir = None
        if self.pending_anchor is not None:
            self.view.scroll_to(self.pending_anchor)
            self.pending_anchor = None
        if self.pending_reference is not None:
            self.view.goto(self.pending_reference)
            self.pending_reference = None
        if self.pending_bookmark is not None:
            self.goto_bookmark(self.pending_bookmark)
            self.pending_bookmark = None
        if self.pending_restore:
            self.view.document.page_position.restore()
        return self.current_index

    def goto_next_section(self):
        if hasattr(self, 'current_index'):
            entry = self.toc_model.next_entry(self.current_index,
                    self.view.document.read_anchor_positions(),
                    self.view.viewport_rect, self.view.document.in_paged_mode)
            if entry is not None:
                self.pending_goto_next_section = (
                        self.toc_model.currently_viewed_entry, entry, False)
                self.toc_clicked(entry.index(), force=True)

    def goto_previous_section(self):
        if hasattr(self, 'current_index'):
            entry = self.toc_model.next_entry(self.current_index,
                    self.view.document.read_anchor_positions(),
                    self.view.viewport_rect, self.view.document.in_paged_mode,
                    backwards=True)
            if entry is not None:
                self.pending_goto_next_section = (
                        self.toc_model.currently_viewed_entry, entry, True)
                self.toc_clicked(entry.index(), force=True)

    def update_indexing_state(self, anchor_positions=None):
        pgns = getattr(self, 'pending_goto_next_section', None)
        if hasattr(self, 'current_index'):
            if anchor_positions is None:
                anchor_positions = self.view.document.read_anchor_positions()
            items = self.toc_model.update_indexing_state(self.current_index,
                        self.view.viewport_rect, anchor_positions,
                        self.view.document.in_paged_mode)
            if items:
                self.toc.scrollTo(items[-1].index())
            if pgns is not None:
                self.pending_goto_next_section = None
                # Check that we actually progressed
                if pgns[0] is self.toc_model.currently_viewed_entry:
                    entry = self.toc_model.next_entry(self.current_index,
                            self.view.document.read_anchor_positions(),
                            self.view.viewport_rect,
                            self.view.document.in_paged_mode,
                            backwards=pgns[2], current_entry=pgns[1])
                    if entry is not None:
                        self.pending_goto_next_section = (
                                self.toc_model.currently_viewed_entry, entry,
                                pgns[2])
                        self.toc_clicked(entry.index(), force=True)

    def load_path(self, path, pos=0.0):
        self.open_progress_indicator(_('Laying out %s')%self.current_title)
        self.view.load_path(path, pos=pos)

    def viewport_resize_started(self, event):
        old, curr = event.size(), event.oldSize()
        if not self.window_mode_changed and old.width() == curr.width():
            # No relayout changes, so page position does not need to be saved
            # This is needed as Qt generates a viewport resized event that
            # changes only the height after a file has been loaded. This can
            # cause the last read position bookmark to become slightly
            # inaccurate
            return
        if not self.resize_in_progress:
            # First resize, so save the current page position
            self.resize_in_progress = True
            if not self.window_mode_changed:
                # The special handling for window mode changed will already
                # have saved page position, so only save it if this is not a
                # mode change
                self.view.document.page_position.save()

        if self.resize_in_progress:
            self.view_resized_timer.start(75)

    def viewport_resize_finished(self):
        # There hasn't been a resize event for some time
        # restore the current page position.
        self.resize_in_progress = False
        if self.window_mode_changed:
            # This resize is part of a window mode change, special case it
            self.handle_window_mode_toggle()
        else:
            self.view.document.page_position.restore()
        self.view.document.after_resize()
        # For some reason scroll_fraction returns incorrect results in paged
        # mode for some time after a resize is finished. No way of knowing
        # exactly how long, so we update it in a second, in the hopes that it
        # will be enough *most* of the time.
        QTimer.singleShot(1000, self.update_page_number)

    def update_page_number(self):
        self.set_page_number(self.view.document.scroll_fraction)

    def close_progress_indicator(self):
        self.pi.stop()
        for o in ('tool_bar', 'tool_bar2', 'view', 'horizontal_scrollbar', 'vertical_scrollbar'):
            getattr(self, o).setEnabled(True)
        self.unsetCursor()
        self.view.setFocus(Qt.PopupFocusReason)

    def open_progress_indicator(self, msg=''):
        self.pi.start(msg)
        for o in ('tool_bar', 'tool_bar2', 'view', 'horizontal_scrollbar', 'vertical_scrollbar'):
            getattr(self, o).setEnabled(False)
        self.setCursor(Qt.BusyCursor)

    def load_theme_menu(self):
        from calibre.gui2.viewer.config import load_themes
        self.themes_menu.clear()
        for key in load_themes():
            title = key[len('theme_'):]
            self.themes_menu.addAction(title, partial(self.load_theme,
                key))

    def load_theme(self, theme_id):
        self.view.load_theme(theme_id)

    def do_config(self):
        self.view.config(self)
        self.load_theme_menu()
        from calibre.gui2 import config
        if not config['viewer_search_history']:
            self.search.clear_history()

    def bookmark(self, *args):
        num = 1
        bm = None
        while True:
            bm = _('Bookmark #%d')%num
            if bm not in self.existing_bookmarks:
                break
            num += 1
        title, ok = QInputDialog.getText(self, _('Add bookmark'),
                _('Enter title for bookmark:'), text=bm)
        title = unicode(title).strip()
        if ok and title:
            bm = self.view.bookmark()
            bm['spine'] = self.current_index
            bm['title'] = title
            self.iterator.add_bookmark(bm)
            self.set_bookmarks(self.iterator.bookmarks)

    def set_bookmarks(self, bookmarks):
        self.bookmarks_menu.clear()
        self.bookmarks_menu.addAction(_("Bookmark this location"), self.bookmark)
        self.bookmarks_menu.addAction(_("Manage Bookmarks"), self.manage_bookmarks)
        self.bookmarks_menu.addSeparator()
        current_page = None
        self.existing_bookmarks = []
        for bm in bookmarks:
            if bm['title'] == 'calibre_current_page_bookmark':
                if self.get_remember_current_page_opt():
                    current_page = bm
            else:
                self.existing_bookmarks.append(bm['title'])
                self.bookmarks_menu.addAction(bm['title'], partial(self.goto_bookmark, bm))
        return current_page

    def manage_bookmarks(self):
        bmm = BookmarkManager(self, self.iterator.bookmarks)
        if bmm.exec_() != BookmarkManager.Accepted:
            return

        bookmarks = bmm.get_bookmarks()

        if bookmarks != self.iterator.bookmarks:
            self.iterator.set_bookmarks(bookmarks)
            self.iterator.save_bookmarks()
            self.set_bookmarks(bookmarks)

    def save_current_position(self):
        if not self.get_remember_current_page_opt():
            return
        if hasattr(self, 'current_index'):
            try:
                bm = self.view.bookmark()
                bm['spine'] = self.current_index
                bm['title'] = 'calibre_current_page_bookmark'
                self.iterator.add_bookmark(bm)
            except:
                traceback.print_exc()

    def load_ebook(self, pathtoebook, open_at=None):
        if self.iterator is not None:
            self.save_current_position()
            self.iterator.__exit__()
        self.iterator = EbookIterator(pathtoebook)
        self.open_progress_indicator(_('Loading ebook...'))
        worker = Worker(target=partial(self.iterator.__enter__,
            extract_embedded_fonts_for_qt=True))
        worker.start()
        while worker.isAlive():
            worker.join(0.1)
            QApplication.processEvents()
        if worker.exception is not None:
            if isinstance(worker.exception, DRMError):
                from calibre.gui2.dialogs.drm_error import DRMErrorMessage
                DRMErrorMessage(self).exec_()
            else:
                r = getattr(worker.exception, 'reason', worker.exception)
                error_dialog(self, _('Could not open ebook'),
                        as_unicode(r) or _('Unknown error'),
                        det_msg=worker.traceback, show=True)
            self.close_progress_indicator()
        else:
            self.metadata.show_opf(self.iterator.opf,
                    self.iterator.book_format)
            self.view.current_language = self.iterator.language
            title = self.iterator.opf.title
            if not title:
                title = os.path.splitext(os.path.basename(pathtoebook))[0]
            if self.iterator.toc:
                self.toc_model = TOC(self.iterator.spine, self.iterator.toc)
                self.toc.setModel(self.toc_model)
                if self.show_toc_on_open:
                    self.action_table_of_contents.setChecked(True)
            else:
                self.toc_model = TOC(self.iterator.spine)
                self.toc.setModel(self.toc_model)
                self.action_table_of_contents.setChecked(False)
            if isbytestring(pathtoebook):
                pathtoebook = force_unicode(pathtoebook, filesystem_encoding)
            vh = vprefs.get('viewer_open_history', [])
            try:
                vh.remove(pathtoebook)
            except:
                pass
            vh.insert(0, pathtoebook)
            vprefs.set('viewer_open_history', vh[:50])
            self.build_recent_menu()

            self.action_table_of_contents.setDisabled(not self.iterator.toc)
            self.current_book_has_toc = bool(self.iterator.toc)
            self.current_title = title
            self.setWindowTitle(self.base_window_title+' - '+title +
                    ' [%s]'%self.iterator.book_format)
            self.pos.setMaximum(sum(self.iterator.pages))
            self.pos.setSuffix(' / %d'%sum(self.iterator.pages))
            self.vertical_scrollbar.setMinimum(100)
            self.vertical_scrollbar.setMaximum(100*sum(self.iterator.pages))
            self.vertical_scrollbar.setSingleStep(10)
            self.vertical_scrollbar.setPageStep(100)
            self.set_vscrollbar_value(1)
            self.current_index = -1
            QApplication.instance().alert(self, 5000)
            previous = self.set_bookmarks(self.iterator.bookmarks)
            if open_at is None and previous is not None:
                self.goto_bookmark(previous)
            else:
                if open_at is None:
                    self.next_document()
                else:
                    if open_at > self.pos.maximum():
                        open_at = self.pos.maximum()
                    if open_at < self.pos.minimum():
                        open_at = self.pos.minimum()
                    self.goto_page(open_at, loaded_check=False)

    def set_vscrollbar_value(self, pagenum):
        self.vertical_scrollbar.blockSignals(True)
        self.vertical_scrollbar.setValue(int(pagenum*100))
        self.vertical_scrollbar.blockSignals(False)

    def set_page_number(self, frac):
        if getattr(self, 'current_page', None) is not None:
            page = self.current_page.start_page + frac*float(self.current_page.pages-1)
            self.pos.set_value(page)
            self.set_vscrollbar_value(page)

    def scrolled(self, frac, onload=False):
        self.set_page_number(frac)
        if not onload:
            ap = self.view.document.read_anchor_positions()
            self.update_indexing_state(ap)

    def next_document(self):
        if (hasattr(self, 'current_index') and self.current_index <
                len(self.iterator.spine) - 1):
            self.load_path(self.iterator.spine[self.current_index+1])

    def previous_document(self):
        if hasattr(self, 'current_index') and self.current_index > 0:
            self.load_path(self.iterator.spine[self.current_index-1], pos=1.0)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            if self.metadata.isVisible():
                self.metadata.setVisible(False)
                event.accept()
                return
            if self.isFullScreen():
                self.toggle_fullscreen()
                event.accept()
                return
        try:
            key = self.view.shortcuts.get_match(event)
        except AttributeError:
            return MainWindow.keyPressEvent(self, event)
        action = {
            'Quit':self.action_quit,
            'Show metadata':self.action_metadata,
            'Copy':self.view.copy_action,
            'Font larger': self.action_font_size_larger,
            'Font smaller': self.action_font_size_smaller,
            'Fullscreen': self.action_full_screen,
            'Find next': self.action_find_next,
            'Find previous': self.action_find_previous,
            'Search online': self.view.search_online_action,
            'Lookup word': self.view.dictionary_action,
            'Next occurrence': self.view.search_action,
        }.get(key, None)
        if action is not None:
            event.accept()
            action.trigger()
            return
        if key == 'Focus Search':
            self.search.setFocus(Qt.OtherFocusReason)
        if not self.view.handle_key_press(event):
            event.ignore()

    def __enter__(self):
        return self

    def __exit__(self, *args):
        if self.iterator is not None:
            self.save_current_position()
            self.iterator.__exit__(*args)

    def read_settings(self):
        c = config().parse()
        self.splitter.setSizes([1, 300])
        if c.remember_window_size:
            wg = vprefs.get('viewer_window_geometry', None)
            if wg is not None:
                self.restoreGeometry(wg)
            ss = vprefs.get('viewer_splitter_state', None)
            if ss is not None:
                self.splitter.restoreState(ss)
            self.show_toc_on_open = vprefs.get('viewer_toc_isvisible', False)
        av = available_height() - 30
        if self.height() > av:
            self.resize(self.width(), av)
        self.splitter.setCollapsible(0, False)
        self.splitter.setCollapsible(1, False)
示例#29
0
文件: view.py 项目: mrmac123/calibre
class DiffView(QWidget):  # {{{

    SYNC_POSITION = 0.4
    line_activated = pyqtSignal(object, object, object)

    def __init__(self, parent=None, show_open_in_editor=False):
        QWidget.__init__(self, parent)
        self.changes = [[], [], []]
        self.delta = 0
        self.l = l = QHBoxLayout(self)
        self.setLayout(l)
        self.syncpos = 0
        l.setMargin(0), l.setSpacing(0)
        self.view = DiffSplit(self, show_open_in_editor=show_open_in_editor)
        l.addWidget(self.view)
        self.add_diff = self.view.add_diff
        self.scrollbar = QScrollBar(self)
        l.addWidget(self.scrollbar)
        self.syncing = False
        self.bars = []
        self.resize_timer = QTimer(self)
        self.resize_timer.setSingleShot(True)
        self.resize_timer.timeout.connect(self.resize_debounced)
        for i, bar in enumerate(
            (self.scrollbar, self.view.left.verticalScrollBar(),
             self.view.right.verticalScrollBar())):
            self.bars.append(bar)
            bar.valueChanged[int].connect(partial(self.scrolled, i))
        self.view.left.resized.connect(self.resized)
        for i, v in enumerate(
            (self.view.left, self.view.right, self.view.handle(1))):
            v.wheel_event.connect(self.scrollbar.wheelEvent)
            if i < 2:
                v.next_change.connect(self.next_change)
                v.line_activated.connect(self.line_activated)
                v.scrolled.connect(partial(self.scrolled, i + 1))

    def next_change(self, delta):
        assert delta in (1, -1)
        position = self.get_position_from_scrollbar(0)
        if position[0] == 'in':
            p = n = position[1]
        else:
            p, n = position[1], position[1] + 1
            if p < 0:
                p = None
            if n >= len(self.changes[0]):
                n = None
        if p == n:
            nc = p + delta
            if nc < 0 or nc >= len(self.changes[0]):
                nc = None
        else:
            nc = {1: n, -1: p}[delta]
        if nc is None:
            self.scrollbar.setValue(0 if delta ==
                                    -1 else self.scrollbar.maximum())
        else:
            val = self.scrollbar.value()
            self.scroll_to(0, ('in', nc, 0))
            nval = self.scrollbar.value()
            if nval == val:
                nval += 5 * delta
                if 0 <= nval <= self.scrollbar.maximum():
                    self.scrollbar.setValue(nval)

    def resized(self):
        self.resize_timer.start(300)

    def resize_debounced(self):
        self.view.resized()
        self.calculate_length()
        self.adjust_range()
        self.view.handle(1).update()

    def get_position_from_scrollbar(self, which):
        changes = self.changes[which]
        bar = self.bars[which]
        syncpos = self.syncpos + bar.value()
        prev = 0
        for i, (top, bot, kind) in enumerate(changes):
            if syncpos <= bot:
                if top <= syncpos:
                    # syncpos is inside a change
                    try:
                        ratio = float(syncpos - top) / (bot - top)
                    except ZeroDivisionError:
                        ratio = 0
                    return 'in', i, ratio
                else:
                    # syncpos is after the previous change
                    offset = syncpos - prev
                    return 'after', i - 1, offset
            else:
                # syncpos is after the current change
                prev = bot
        offset = syncpos - prev
        return 'after', len(changes) - 1, offset

    def scroll_to(self, which, position):
        changes = self.changes[which]
        bar = self.bars[which]
        val = None
        if position[0] == 'in':
            change_idx, ratio = position[1:]
            start, end = changes[change_idx][:2]
            val = start + int((end - start) * ratio)
        else:
            change_idx, offset = position[1:]
            start = 0 if change_idx < 0 else changes[change_idx][1]
            val = start + offset
        bar.setValue(val - self.syncpos)

    def scrolled(self, which, *args):
        if self.syncing:
            return
        position = self.get_position_from_scrollbar(which)
        with self:
            for x in {0, 1, 2} - {which}:
                self.scroll_to(x, position)
        self.view.handle(1).update()

    def __enter__(self):
        self.syncing = True

    def __exit__(self, *args):
        self.syncing = False

    def clear(self):
        with self:
            self.view.clear()
            self.changes = [[], [], []]
            self.delta = 0
            self.scrollbar.setRange(0, 0)

    def adjust_range(self):
        ls, rs = self.view.left.verticalScrollBar(
        ), self.view.right.verticalScrollBar()
        self.scrollbar.setPageStep(min(ls.pageStep(), rs.pageStep()))
        self.scrollbar.setSingleStep(min(ls.singleStep(), rs.singleStep()))
        self.scrollbar.setRange(0, ls.maximum() + self.delta)
        self.scrollbar.setVisible(
            self.view.left.blockCount() > ls.pageStep()
            or self.view.right.blockCount() > rs.pageStep())
        self.syncpos = int(ceil(self.scrollbar.pageStep() *
                                self.SYNC_POSITION))

    def finalize(self):
        self.view.finalize()
        self.changes = [[], [], []]
        self.calculate_length()
        self.adjust_range()

    def calculate_length(self):
        delta = 0
        line_number_changes = ([], [])
        for v, lmap, changes in zip((self.view.left, self.view.right),
                                    ({}, {}), line_number_changes):
            b = v.document().firstBlock()
            ebl = v.document().documentLayout().ensureBlockLayout
            last_line_count = 0
            while b.isValid():
                ebl(b)
                lmap[b.blockNumber()] = last_line_count
                last_line_count += b.layout().lineCount()
                b = b.next()
            for top, bot, kind in v.changes:
                changes.append((lmap[top], lmap[bot], kind))

        changes = []
        for (l_top, l_bot, kind), (r_top, r_bot,
                                   kind) in zip(*line_number_changes):
            height = max(l_bot - l_top, r_bot - r_top)
            top = delta + l_top
            changes.append((top, top + height, kind))
            delta = top + height - l_bot
        self.changes, self.delta = (changes, ) + line_number_changes, delta

    def handle_key(self, ev):
        amount, d = None, 1
        key = ev.key()
        if key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_J, Qt.Key_K):
            amount = self.scrollbar.singleStep()
            if key in (Qt.Key_Up, Qt.Key_K):
                d = -1
        elif key in (Qt.Key_PageUp, Qt.Key_PageDown):
            amount = self.scrollbar.pageStep()
            if key in (Qt.Key_PageUp, ):
                d = -1
        elif key in (Qt.Key_Home, Qt.Key_End):
            self.scrollbar.setValue(0 if key ==
                                    Qt.Key_Home else self.scrollbar.maximum())
            return True
        elif key in (Qt.Key_N, Qt.Key_P):
            self.next_change(1 if key == Qt.Key_N else -1)
            return True

        if amount is not None:
            self.scrollbar.setValue(self.scrollbar.value() + d * amount)
            return True
        return False
示例#30
0
class Widget(QWidget, ScreenWidget):
    name = "summary"

    def __init__(self):
        QWidget.__init__(self)
        self.ui = Ui_SummaryWidget()
        self.ui.setupUi(self)

        self.ui.content.setText("")
        self.timer = QTimer()
        self.start_time = 0

        try:
            self.connect(self.timer, SIGNAL("timeout()"), self.updateCounter)
        except:
            pass

    def slotReboot(self):
        reply = QuestionDialog(
            _("Restart"),
            _('''<b><p>Are you sure you want to restart the computer?</p></b>'''
              ))
        if reply == "yes":
            yali.util.reboot()

    def startBombCounter(self):
        self.start_time = int(time.time())
        self.timer.start(1000)

    def backCheck(self):
        self.timer.stop()
        ctx.interface.informationWindow.hide()
        if ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT:
            ctx.mainScreen.ui.buttonNext.setText(_("Next"))

        if (ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT) \
           and not ctx.flags.collection:
            ctx.mainScreen.step_increment = 2
        return True

    def updateCounter(self):
        remain = 20 - (int(time.time()) - self.start_time)
        ctx.interface.informationWindow.update(
            _("Installation starts in <b>%s</b> seconds") % remain)
        if remain <= 0:
            self.timer.stop()
            ctx.mainScreen.slotNext()

    def shown(self):
        #ctx.mainScreen.disableNext()
        if ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT:
            ctx.mainScreen.ui.buttonNext.setText(_("Start Installation"))
        if ctx.flags.install_type == ctx.STEP_FIRST_BOOT:
            ctx.mainScreen.ui.buttonNext.setText(_("Apply Settings"))

        if ctx.installData.isKahyaUsed:
            self.startBombCounter()

        self.fillContent()

    def fillContent(self):
        subject = "<p><li><b>%s</b></li><ul>"
        item = "<li>%s</li>"
        end = "</ul></p>"
        content = QString("")

        content.append("""<html><body><ul>""")

        # Keyboard Layout
        if ctx.installData.keyData:
            content.append(subject % _("Keyboard Settings"))
            content.append(item % _("Selected keyboard layout is <b>%s</b>") %
                           ctx.installData.keyData["name"])
            content.append(end)

        # TimeZone
        if ctx.installData.timezone:
            content.append(subject % _("Date/Time Settings"))
            content.append(item % _("Selected TimeZone is <b>%s</b>") %
                           ctx.installData.timezone)
            content.append(end)

        # Users
        if len(yali.users.PENDING_USERS) > 0:
            content.append(subject % _("User Settings"))
            for user in yali.users.PENDING_USERS:
                state = _("User %(username)s (<b>%(realname)s</b>) added.")
                if "wheel" in user.groups:
                    state = _(
                        "User %(username)s (<b>%(realname)s</b>) added with <u>administrator privileges</u>."
                    )
                content.append(item % state % {
                    "username": user.realname,
                    "realname": user.username
                })
            content.append(end)

        # HostName
        if ctx.installData.hostName:
            content.append(subject % _("Hostname Settings"))
            content.append(item % _("Hostname is set as <b>%s</b>") %
                           ctx.installData.hostName)
            content.append(end)

        # Partition
        if ctx.storage.clearPartType is not None:
            content.append(subject % _("Partition Settings"))
            devices = ""
            for disk in ctx.storage.clearPartDisks:
                device = ctx.storage.devicetree.getDeviceByName(disk)
                devices += "(%s on %s)" % (device.model, device.name)

            if ctx.storage.doAutoPart:
                content.append(item % _("Automatic Partitioning selected."))
                if ctx.storage.clearPartType == CLEARPART_TYPE_ALL:
                    content.append(item % _("Use All Space"))
                    content.append(item % _("Removes all partitions on the selected "\
                                            "%s device(s). <b><u>This includes partitions "\
                                            "created by other operating systems.</u></b>") % devices)
                elif ctx.storage.clearPartType == CLEARPART_TYPE_LINUX:
                    content.append(item %
                                   _("Replace Existing Linux System(s)"))
                    content.append(item % _("Removes all Linux partitions on the selected" \
                                            "%s device(s). This does not remove "\
                                            "other partitions you may have on your " \
                                            "storage device(s) (such as VFAT or FAT32)") % devices)
                elif ctx.storage.clearPartType == CLEARPART_TYPE_NONE:
                    content.append(item % _("Use Free Space"))
                    content.append(item % _("Retains your current data and partitions" \
                                            " and uses only the unpartitioned space on " \
                                            "the selected %s device(s), assuming you have "\
                                            "enough free space available.") % devices)

            else:
                content.append(item % _("Manual Partitioning selected."))
                for operation in ctx.storage.devicetree.operations:
                    content.append(item % operation)

            content.append(end)

        # Bootloader
        if ctx.bootloader.stage1Device:
            content.append(subject % _("Bootloader Settings"))
            grubstr = _("GRUB will be installed to <b>%s</b>.")
            if ctx.bootloader.bootType == BOOT_TYPE_NONE:
                content.append(item % _("GRUB will not be installed."))
            else:
                content.append(item % grubstr % ctx.bootloader.stage1Device)

            content.append(end)

        if ctx.flags.collection and ctx.installData.autoCollection:
            content.append(subject % _("Package Installation Settings"))
            content.append(item % _("Collection <b>%s</b> selected") %
                           ctx.installData.autoCollection.title)

            content.append(end)

        content.append("""</ul></body></html>""")

        self.ui.content.setHtml(content)

    def execute(self):
        self.timer.stop()

        if ctx.flags.dryRun:
            ctx.logger.debug("dryRun activated Yali stopped")
            ctx.mainScreen.enableBack()
            return False

        if ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT:
            self.createPackageList()

            rc = ctx.interface.messageWindow(
                _("Confirm"),
                _("The partitioning options you have selected "
                  "will now be written to disk. Any "
                  "data on deleted or reformatted partitions "
                  "will be lost."),
                type="custom",
                customIcon="question",
                customButtons=[_("Write Changes to Disk"),
                               _("Go Back")],
                default=1)

            ctx.mainScreen.processEvents()

            if not rc:
                if yali.storage.complete(ctx.storage, ctx.interface):
                    ctx.storage.turnOnSwap()
                    ctx.storage.mountFilesystems(readOnly=False,
                                                 skipRoot=False)
                    ctx.mainScreen.step_increment = 1
                    ctx.mainScreen.ui.buttonNext.setText(_("Next"))
                    return True

            ctx.mainScreen.enableBack()
            return False

        elif ctx.flags.install_type == ctx.STEP_FIRST_BOOT:
            ctx.mainScreen.ui.buttonNext.setText(_("Next"))
            return True

        # Auto Partitioning
        #if not ctx.storage.storageset.swapDevices:
        #    size = 0
        #    if yali.util.memInstalled() > 512:
        #        size = 300
        #    else:
        #        size = 600
        #    ctx.storage.storageset.createSwapFile(ctx.storage.storageset.rootDevice, ctx.consts.target_dir, size)

    def createPackageList(self):
        ctx.logger.debug("Generating package list...")

        if ctx.flags.collection:
            # Get only collection packages with collection Name
            packages = yali.pisiiface.getAllPackagesWithPaths(
                collectionIndex=ctx.installData.autoCollection.index)
        else:
            # Check for just installing system.base packages
            if ctx.flags.baseonly:
                packages = yali.pisiiface.getBasePackages()
            else:
                packages = yali.pisiiface.getAllPackagesWithPaths()

        # Check for extra languages
        packages = list(
            set(packages) - set(yali.pisiiface.getNotNeededLanguagePackages()))
        ctx.logger.debug("Not needed lang packages will not be installing...")

        packages = self.filterDriverPacks(packages)
        packages.sort()

        # Place baselayout package on the top of package list
        baselayout = None
        for path in packages:
            if "/baselayout-" in path:
                baselayout = packages.index(path)
                break

        if baselayout:
            packages.insert(0, packages.pop(baselayout))

        ctx.packagesToInstall = packages

    def filterDriverPacks(self, paths):
        try:
            from panda import Panda
        except ImportError:
            ctx.logger.debug(
                "Installing all driver packages since panda module is not installed."
            )
            return paths

        panda = Panda()

        # filter all driver packages
        foundDriverPackages = set(
            yali.pisiiface.getPathsByPackageName(
                panda.get_all_driver_packages()))
        ctx.logger.debug("Found driver packages: %s" % foundDriverPackages)

        allPackages = set(paths)
        packages = allPackages - foundDriverPackages

        # detect hardware
        neededDriverPackages = set(
            yali.pisiiface.getPathsByPackageName(
                panda.get_needed_driver_packages()))
        ctx.logger.debug("Known driver packages for this hardware: %s" %
                         neededDriverPackages)

        # if alternatives are available ask to user, otherwise return
        if neededDriverPackages and neededDriverPackages.issubset(allPackages):
            answer = ctx.interface.messageWindow(
                _("Proprietary Hardware Drivers"),
                _("<qt>Proprietary drivers are available which may be required "
                  "to utilize the full capabilities of your video card. "
                  "These drivers are developed by the hardware manufacturer "
                  "and not supported by Pisi Linux developers since their "
                  "source code is not publicly available."
                  "<br><br>"
                  "<b>Do you want to install and use these proprietary drivers "
                  "instead of the default drivers?</b></qt>"),
                type="custom",
                customIcon="question",
                customButtons=[_("Yes"), _("No")])

            if answer == 0:
                packages.update(neededDriverPackages)
                ctx.blacklistedKernelModules.append(
                    panda.get_blacklisted_module())
                ctx.logger.debug(
                    "These driver packages will be installed: %s" %
                    neededDriverPackages)

        return list(packages)
示例#31
0
class SearchDialog(QDialog, Ui_Dialog):
    def __init__(self, gui, parent=None, query=''):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.config = JSONConfig('store/search')
        self.search_edit.initialize('store_search_search')

        # Loads variables that store various settings.
        # This needs to be called soon in __init__ because
        # the variables it sets up are used later.
        self.load_settings()

        self.gui = gui

        # Setup our worker threads.
        self.search_pool = SearchThreadPool(self.search_thread_count)
        self.cache_pool = CacheUpdateThreadPool(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(
            self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(
            self.details_thread_count)
        self.results_view.setCursor(Qt.PointingHandCursor)

        # Check for results and hung threads.
        self.checker = QTimer()
        self.progress_checker = QTimer()
        self.hang_check = 0

        # Update store caches silently.
        for p in self.gui.istores.values():
            self.cache_pool.add_task(p, self.timeout)

        self.store_checks = {}
        self.setup_store_checks()

        # Set the search query
        self.search_edit.setText(query)
        self.search_edit.setSizeAdjustPolicy(
            QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.search_edit.setMinimumContentsLength(25)

        # Create and add the progress indicator
        self.pi = ProgressIndicator(self, 24)
        self.top_layout.addWidget(self.pi)

        self.adv_search_button.setIcon(QIcon(I('search.png')))
        self.configure.setIcon(QIcon(I('config.png')))

        self.adv_search_button.clicked.connect(self.build_adv_search)
        self.search.clicked.connect(self.do_search)
        self.checker.timeout.connect(self.get_results)
        self.progress_checker.timeout.connect(self.check_progress)
        self.results_view.activated.connect(self.result_item_activated)
        self.results_view.download_requested.connect(self.download_book)
        self.results_view.open_requested.connect(self.open_store)
        self.results_view.model().total_changed.connect(self.update_book_total)
        self.select_all_stores.clicked.connect(self.stores_select_all)
        self.select_invert_stores.clicked.connect(self.stores_select_invert)
        self.select_none_stores.clicked.connect(self.stores_select_none)
        self.configure.clicked.connect(self.do_config)
        self.finished.connect(self.dialog_closed)

        self.progress_checker.start(100)

        self.restore_state()

    def setup_store_checks(self):
        first_run = self.config.get('first_run', True)

        # Add check boxes for each store so the user
        # can disable searching specific stores on a
        # per search basis.
        existing = {}
        for n in self.store_checks:
            existing[n] = self.store_checks[n].isChecked()

        self.store_checks = {}

        stores_check_widget = QWidget()
        store_list_layout = QGridLayout()
        stores_check_widget.setLayout(store_list_layout)

        icon = QIcon(I('donate.png'))
        for i, x in enumerate(
                sorted(self.gui.istores.keys(), key=lambda x: x.lower())):
            cbox = QCheckBox(x)
            cbox.setChecked(existing.get(x, first_run))
            store_list_layout.addWidget(cbox, i, 0, 1, 1)
            if self.gui.istores[x].base_plugin.affiliate:
                iw = QLabel(self)
                iw.setToolTip('<p>' + _(
                    'Buying from this store supports the calibre developer: %s</p>'
                ) % self.gui.istores[x].base_plugin.author + '</p>')
                iw.setPixmap(icon.pixmap(16, 16))
                store_list_layout.addWidget(iw, i, 1, 1, 1)
            self.store_checks[x] = cbox
        store_list_layout.setRowStretch(store_list_layout.rowCount(), 10)
        self.store_list.setWidget(stores_check_widget)

        self.config['first_run'] = False

    def build_adv_search(self):
        adv = AdvSearchBuilderDialog(self)
        if adv.exec_() == QDialog.Accepted:
            self.search_edit.setText(adv.search_string())

    def resize_columns(self):
        total = 600
        # Cover
        self.results_view.setColumnWidth(0, 85)
        total = total - 85
        # Title / Author
        self.results_view.setColumnWidth(1, int(total * .40))
        # Price
        self.results_view.setColumnWidth(2, int(total * .12))
        # DRM
        self.results_view.setColumnWidth(3, int(total * .15))
        # Store / Formats
        self.results_view.setColumnWidth(4, int(total * .25))
        # Download
        self.results_view.setColumnWidth(5, 20)
        # Affiliate
        self.results_view.setColumnWidth(6, 20)

    def do_search(self):
        # Stop all running threads.
        self.checker.stop()
        self.search_pool.abort()
        # Clear the visible results.
        self.results_view.model().clear_results()

        # Don't start a search if there is nothing to search for.
        query = unicode(self.search_edit.text())
        if not query.strip():
            return
        # Give the query to the results model so it can do
        # futher filtering.
        self.results_view.model().set_query(query)

        # Plugins are in random order that does not change.
        # Randomize the ord of the plugin names every time
        # there is a search. This way plugins closer
        # to a don't have an unfair advantage over
        # plugins further from a.
        store_names = self.store_checks.keys()
        if not store_names:
            return
        # Remove all of our internal filtering logic from the query.
        query = self.clean_query(query)
        shuffle(store_names)
        # Add plugins that the user has checked to the search pool's work queue.
        for n in store_names:
            if self.store_checks[n].isChecked():
                self.search_pool.add_task(query, n, self.gui.istores[n],
                                          self.max_results, self.timeout)
        self.hang_check = 0
        self.checker.start(100)
        self.pi.startAnimation()

    def clean_query(self, query):
        query = query.lower()
        # Remove control modifiers.
        query = query.replace('\\', '')
        query = query.replace('!', '')
        query = query.replace('=', '')
        query = query.replace('~', '')
        query = query.replace('>', '')
        query = query.replace('<', '')
        # Remove the prefix.
        for loc in ('all', 'author', 'authors', 'title'):
            query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query)
            query = query.replace('%s:' % loc, '')
        # Remove the prefix and search text.
        for loc in ('cover', 'download', 'downloads', 'drm', 'format',
                    'formats', 'price', 'store'):
            query = re.sub(r'%s:"[^"]"' % loc, '', query)
            query = re.sub(r'%s:[^\s]*' % loc, '', query)
        # Remove logic.
        query = re.sub(r'(^|\s)(and|not|or|a|the|is|of)(\s|$)', ' ', query)
        # Remove "
        query = query.replace('"', '')
        # Remove excess whitespace.
        query = re.sub(r'\s{2,}', ' ', query)
        query = query.strip()
        return query.encode('utf-8')

    def save_state(self):
        self.config['geometry'] = bytearray(self.saveGeometry())
        self.config['store_splitter_state'] = bytearray(
            self.store_splitter.saveState())
        self.config['results_view_column_width'] = [
            self.results_view.columnWidth(i)
            for i in range(self.results_view.model().columnCount())
        ]
        self.config['sort_col'] = self.results_view.model().sort_col
        self.config['sort_order'] = self.results_view.model().sort_order
        self.config['open_external'] = self.open_external.isChecked()

        store_check = {}
        for k, v in self.store_checks.items():
            store_check[k] = v.isChecked()
        self.config['store_checked'] = store_check

    def restore_state(self):
        geometry = self.config.get('geometry', None)
        if geometry:
            self.restoreGeometry(geometry)

        splitter_state = self.config.get('store_splitter_state', None)
        if splitter_state:
            self.store_splitter.restoreState(splitter_state)

        results_cwidth = self.config.get('results_view_column_width', None)
        if results_cwidth:
            for i, x in enumerate(results_cwidth):
                if i >= self.results_view.model().columnCount():
                    break
                self.results_view.setColumnWidth(i, x)
        else:
            self.resize_columns()

        self.open_external.setChecked(self.should_open_external)

        store_check = self.config.get('store_checked', None)
        if store_check:
            for n in store_check:
                if n in self.store_checks:
                    self.store_checks[n].setChecked(store_check[n])

        self.results_view.model().sort_col = self.config.get('sort_col', 2)
        self.results_view.model().sort_order = self.config.get(
            'sort_order', Qt.AscendingOrder)
        self.results_view.header().setSortIndicator(
            self.results_view.model().sort_col,
            self.results_view.model().sort_order)

    def load_settings(self):
        # Seconds
        self.timeout = self.config.get('timeout', 75)
        # Milliseconds
        self.hang_time = self.config.get('hang_time', 75) * 1000

        self.max_results = self.config.get('max_results', 15)
        self.should_open_external = self.config.get('open_external', True)

        # Number of threads to run for each type of operation
        self.search_thread_count = self.config.get('search_thread_count', 4)
        self.cache_thread_count = self.config.get('cache_thread_count', 2)
        self.cover_thread_count = self.config.get('cover_thread_count', 2)
        self.details_thread_count = self.config.get('details_thread_count', 4)

    def do_config(self):
        # Save values that need to be synced between the dialog and the
        # search widget.
        self.config['open_external'] = self.open_external.isChecked()

        # Create the config dialog. It's going to put two config widgets
        # into a QTabWidget for displaying all of the settings.
        d = QDialog(self)
        button_box = QDialogButtonBox(QDialogButtonBox.Close)
        v = QVBoxLayout(d)
        button_box.accepted.connect(d.accept)
        button_box.rejected.connect(d.reject)
        d.setWindowTitle(_('Customize get books search'))

        tab_widget = QTabWidget(d)
        v.addWidget(tab_widget)
        v.addWidget(button_box)

        chooser_config_widget = StoreChooserWidget()
        search_config_widget = StoreConfigWidget(self.config)

        tab_widget.addTab(chooser_config_widget, _('Choose stores'))
        tab_widget.addTab(search_config_widget, _('Configure search'))

        # Restore dialog state.
        geometry = self.config.get('config_dialog_geometry', None)
        if geometry:
            d.restoreGeometry(geometry)
        else:
            d.resize(800, 600)
        tab_index = self.config.get('config_dialog_tab_index', 0)
        tab_index = min(tab_index, tab_widget.count() - 1)
        tab_widget.setCurrentIndex(tab_index)

        d.exec_()

        # Save dialog state.
        self.config['config_dialog_geometry'] = bytearray(d.saveGeometry())
        self.config['config_dialog_tab_index'] = tab_widget.currentIndex()

        search_config_widget.save_settings()
        self.config_changed()
        self.gui.load_store_plugins()
        self.setup_store_checks()

    def config_changed(self):
        self.load_settings()

        self.open_external.setChecked(self.should_open_external)
        self.search_pool.set_thread_count(self.search_thread_count)
        self.cache_pool.set_thread_count(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(
            self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(
            self.details_thread_count)

    def get_results(self):
        # We only want the search plugins to run
        # a maximum set amount of time before giving up.
        self.hang_check += 1
        if self.hang_check >= self.hang_time:
            self.search_pool.abort()
            self.checker.stop()
        else:
            # Stop the checker if not threads are running.
            if not self.search_pool.threads_running(
            ) and not self.search_pool.has_tasks():
                self.checker.stop()

        while self.search_pool.has_results():
            res, store_plugin = self.search_pool.get_result()
            if res:
                self.results_view.model().add_result(res, store_plugin)

        if not self.search_pool.threads_running(
        ) and not self.results_view.model().has_results():
            info_dialog(self,
                        _('No matches'),
                        _('Couldn\'t find any books matching your query.'),
                        show=True,
                        show_copy_button=False)

    def update_book_total(self, total):
        self.total.setText('%s' % total)

    def result_item_activated(self, index):
        result = self.results_view.model().get_result(index)

        if result.downloads:
            self.download_book(result)
        else:
            self.open_store(result)

    def download_book(self, result):
        d = ChooseFormatDialog(self,
                               _('Choose format to download to your library.'),
                               result.downloads.keys())
        if d.exec_() == d.Accepted:
            ext = d.format()
            fname = result.title[:60] + '.' + ext.lower()
            fname = ascii_filename(fname)
            self.gui.download_ebook(result.downloads[ext], filename=fname)

    def open_store(self, result):
        self.gui.istores[result.store_name].open(
            self, result.detail_item, self.open_external.isChecked())

    def check_progress(self):
        if not self.search_pool.threads_running(
        ) and not self.results_view.model().cover_pool.threads_running(
        ) and not self.results_view.model().details_pool.threads_running():
            self.pi.stopAnimation()
        else:
            if not self.pi.isAnimated():
                self.pi.startAnimation()

    def stores_select_all(self):
        for check in self.store_checks.values():
            check.setChecked(True)

    def stores_select_invert(self):
        for check in self.store_checks.values():
            check.setChecked(not check.isChecked())

    def stores_select_none(self):
        for check in self.store_checks.values():
            check.setChecked(False)

    def dialog_closed(self, result):
        self.results_view.model().closing()
        self.search_pool.abort()
        self.cache_pool.abort()
        self.save_state()

    def exec_(self):
        if unicode(self.search_edit.text()).strip():
            self.do_search()
        return QDialog.exec_(self)
示例#32
0
class Main(
        MainWindow,
        MainWindowMixin,
        DeviceMixin,
        EmailMixin,  # {{{
        TagBrowserMixin,
        CoverFlowMixin,
        LibraryViewMixin,
        SearchBoxMixin,
        SavedSearchBoxMixin,
        SearchRestrictionMixin,
        LayoutMixin,
        UpdateMixin,
        EbookDownloadMixin):
    'The main GUI'

    proceed_requested = pyqtSignal(object, object)

    def __init__(self, opts, parent=None, gui_debug=None):
        global _gui
        MainWindow.__init__(self,
                            opts,
                            parent=parent,
                            disable_automatic_gc=True)
        self.jobs_pointer = Pointer(self)
        self.proceed_requested.connect(self.do_proceed,
                                       type=Qt.QueuedConnection)
        self.proceed_question = ProceedQuestion(self)
        self.job_error_dialog = JobError(self)
        self.keyboard = Manager(self)
        _gui = self
        self.opts = opts
        self.device_connected = None
        self.gui_debug = gui_debug
        self.iactions = OrderedDict()
        # Actions
        for action in interface_actions():
            if opts.ignore_plugins and action.plugin_path is not None:
                continue
            try:
                ac = self.init_iaction(action)
            except:
                # Ignore errors in loading user supplied plugins
                import traceback
                traceback.print_exc()
                if action.plugin_path is None:
                    raise
                continue
            ac.plugin_path = action.plugin_path
            ac.interface_action_base_plugin = action
            self.add_iaction(ac)
        self.load_store_plugins()

    def init_iaction(self, action):
        ac = action.load_actual_plugin(self)
        ac.plugin_path = action.plugin_path
        ac.interface_action_base_plugin = action
        action.actual_iaction_plugin_loaded = True
        return ac

    def add_iaction(self, ac):
        acmap = self.iactions
        if ac.name in acmap:
            if ac.priority >= acmap[ac.name].priority:
                acmap[ac.name] = ac
        else:
            acmap[ac.name] = ac

    def load_store_plugins(self):
        from calibre.gui2.store.loader import Stores
        self.istores = Stores()
        for store in available_store_plugins():
            if self.opts.ignore_plugins and store.plugin_path is not None:
                continue
            try:
                st = self.init_istore(store)
                self.add_istore(st)
            except:
                # Ignore errors in loading user supplied plugins
                import traceback
                traceback.print_exc()
                if store.plugin_path is None:
                    raise
                continue
        self.istores.builtins_loaded()

    def init_istore(self, store):
        st = store.load_actual_plugin(self)
        st.plugin_path = store.plugin_path
        st.base_plugin = store
        store.actual_istore_plugin_loaded = True
        return st

    def add_istore(self, st):
        stmap = self.istores
        if st.name in stmap:
            if st.priority >= stmap[st.name].priority:
                stmap[st.name] = st
        else:
            stmap[st.name] = st

    def initialize(self, library_path, db, listener, actions, show_gui=True):
        opts = self.opts
        self.preferences_action, self.quit_action = actions
        self.library_path = library_path
        self.content_server = None
        self.spare_servers = []
        self.must_restart_before_config = False
        self.listener = Listener(listener)
        self.check_messages_timer = QTimer()
        self.connect(self.check_messages_timer, SIGNAL('timeout()'),
                     self.another_instance_wants_to_talk)
        self.check_messages_timer.start(1000)

        for ac in self.iactions.values():
            try:
                ac.do_genesis()
            except Exception:
                # Ignore errors in third party plugins
                import traceback
                traceback.print_exc()
                if getattr(ac, 'plugin_path', None) is None:
                    raise
        self.donate_action = QAction(QIcon(I('donate.png')),
                                     _('&Donate to support calibre'), self)
        for st in self.istores.values():
            st.do_genesis()
        MainWindowMixin.__init__(self, db)

        # Jobs Button {{{
        self.job_manager = JobManager()
        self.jobs_dialog = JobsDialog(self, self.job_manager)
        self.jobs_button = JobsButton(horizontal=True, parent=self)
        self.jobs_button.initialize(self.jobs_dialog, self.job_manager)
        # }}}

        LayoutMixin.__init__(self)
        EmailMixin.__init__(self)
        EbookDownloadMixin.__init__(self)
        DeviceMixin.__init__(self)

        self.progress_indicator = ProgressIndicator(self)
        self.progress_indicator.pos = (0, 20)
        self.verbose = opts.verbose
        self.get_metadata = GetMetadata()
        self.upload_memory = {}
        self.metadata_dialogs = []
        self.default_thumbnail = None
        self.tb_wrapper = textwrap.TextWrapper(width=40)
        self.viewers = collections.deque()
        self.system_tray_icon = SystemTrayIcon(QIcon(I('lt.png')), self)
        self.system_tray_icon.setToolTip('calibre')
        self.system_tray_icon.tooltip_requested.connect(
            self.job_manager.show_tooltip)
        if not config['systray_icon']:
            self.system_tray_icon.hide()
        else:
            self.system_tray_icon.show()
        self.system_tray_menu = QMenu(self)
        self.restore_action = self.system_tray_menu.addAction(
            QIcon(I('page.png')), _('&Restore'))
        self.system_tray_menu.addAction(self.donate_action)
        self.donate_button.setDefaultAction(self.donate_action)
        self.donate_button.setStatusTip(self.donate_button.toolTip())
        self.eject_action = self.system_tray_menu.addAction(
            QIcon(I('eject.png')), _('&Eject connected device'))
        self.eject_action.setEnabled(False)
        self.addAction(self.quit_action)
        self.system_tray_menu.addAction(self.quit_action)
        self.keyboard.register_shortcut('quit calibre',
                                        _('Quit calibre'),
                                        default_keys=('Ctrl+Q', ),
                                        action=self.quit_action)
        self.system_tray_icon.setContextMenu(self.system_tray_menu)
        self.connect(self.quit_action, SIGNAL('triggered(bool)'), self.quit)
        self.connect(self.donate_action, SIGNAL('triggered(bool)'),
                     self.donate)
        self.connect(self.restore_action, SIGNAL('triggered()'),
                     self.show_windows)
        self.system_tray_icon.activated.connect(
            self.system_tray_icon_activated)

        self.esc_action = QAction(self)
        self.addAction(self.esc_action)
        self.keyboard.register_shortcut('clear current search',
                                        _('Clear the current search'),
                                        default_keys=('Esc', ),
                                        action=self.esc_action)
        self.esc_action.triggered.connect(self.esc)

        self.shift_esc_action = QAction(self)
        self.addAction(self.shift_esc_action)
        self.keyboard.register_shortcut('focus book list',
                                        _('Focus the book list'),
                                        default_keys=('Shift+Esc', ),
                                        action=self.shift_esc_action)
        self.shift_esc_action.triggered.connect(self.shift_esc)

        self.ctrl_esc_action = QAction(self)
        self.addAction(self.ctrl_esc_action)
        self.keyboard.register_shortcut('clear virtual library',
                                        _('Clear the virtual library'),
                                        default_keys=('Ctrl+Esc', ),
                                        action=self.ctrl_esc_action)
        self.ctrl_esc_action.triggered.connect(self.ctrl_esc)

        self.alt_esc_action = QAction(self)
        self.addAction(self.alt_esc_action)
        self.keyboard.register_shortcut('clear additional restriction',
                                        _('Clear the additional restriction'),
                                        default_keys=('Alt+Esc', ),
                                        action=self.alt_esc_action)
        self.alt_esc_action.triggered.connect(
            self.clear_additional_restriction)

        ####################### Start spare job server ########################
        QTimer.singleShot(1000, self.add_spare_server)

        ####################### Location Manager ########################
        self.location_manager.location_selected.connect(self.location_selected)
        self.location_manager.unmount_device.connect(
            self.device_manager.umount_device)
        self.location_manager.configure_device.connect(
            self.configure_connected_device)
        self.location_manager.update_device_metadata.connect(
            self.update_metadata_on_device)
        self.eject_action.triggered.connect(self.device_manager.umount_device)

        #################### Update notification ###################
        UpdateMixin.__init__(self, opts)

        ####################### Search boxes ########################
        SearchRestrictionMixin.__init__(self)
        SavedSearchBoxMixin.__init__(self)

        ####################### Library view ########################
        LibraryViewMixin.__init__(self, db)
        SearchBoxMixin.__init__(self)  # Requires current_db

        if show_gui:
            self.show()

        if self.system_tray_icon.isVisible() and opts.start_in_tray:
            self.hide_windows()
        self.library_view.model().count_changed_signal.connect(
            self.iactions['Choose Library'].count_changed)
        if not gprefs.get('quick_start_guide_added', False):
            from calibre.ebooks.metadata.meta import get_metadata
            mi = get_metadata(open(P('quick_start.epub'), 'rb'), 'epub')
            self.library_view.model().add_books([P('quick_start.epub')],
                                                ['epub'], [mi])
            gprefs['quick_start_guide_added'] = True
            self.library_view.model().books_added(1)
            if hasattr(self, 'db_images'):
                self.db_images.reset()
            if self.library_view.model().rowCount(None) < 3:
                self.library_view.resizeColumnsToContents()

        for view in ('library', 'memory', 'card_a', 'card_b'):
            v = getattr(self, '%s_view' % view)
            v.selectionModel().selectionChanged.connect(self.update_status_bar)
            v.model().count_changed_signal.connect(self.update_status_bar)

        self.library_view.model().count_changed()
        self.bars_manager.database_changed(self.library_view.model().db)
        self.library_view.model().database_changed.connect(
            self.bars_manager.database_changed, type=Qt.QueuedConnection)

        ########################### Tags Browser ##############################
        TagBrowserMixin.__init__(self, db)

        ######################### Search Restriction ##########################
        if db.prefs['virtual_lib_on_startup']:
            self.apply_virtual_library(db.prefs['virtual_lib_on_startup'])
        self.rebuild_vl_tabs()

        ########################### Cover Flow ################################

        CoverFlowMixin.__init__(self)

        self._calculated_available_height = min(max_available_height() - 15,
                                                self.height())
        self.resize(self.width(), self._calculated_available_height)

        self.build_context_menus()

        for ac in self.iactions.values():
            try:
                ac.gui_layout_complete()
            except:
                import traceback
                traceback.print_exc()
                if ac.plugin_path is None:
                    raise

        if config['autolaunch_server']:
            self.start_content_server()

        self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)

        self.read_settings()
        self.finalize_layout()
        if self.bars_manager.showing_donate:
            self.donate_button.start_animation()
        self.set_window_title()

        for ac in self.iactions.values():
            try:
                ac.initialization_complete()
            except:
                import traceback
                traceback.print_exc()
                if ac.plugin_path is None:
                    raise
        self.device_manager.set_current_library_uuid(db.library_id)

        self.keyboard.finalize()
        self.auto_adder = AutoAdder(gprefs['auto_add_path'], self)

        self.save_layout_state()

        # Collect cycles now
        gc.collect()

        if show_gui and self.gui_debug is not None:
            info_dialog(
                self,
                _('Debug mode'),
                '<p>' +
                _('You have started calibre in debug mode. After you '
                  'quit calibre, the debug log will be available in '
                  'the file: %s<p>The '
                  'log will be displayed automatically.') % self.gui_debug,
                show=True)

        self.iactions['Connect Share'].check_smartdevice_menus()
        QTimer.singleShot(1, self.start_smartdevice)

    def esc(self, *args):
        self.clear_button.click()

    def shift_esc(self):
        self.current_view().setFocus(Qt.OtherFocusReason)

    def ctrl_esc(self):
        self.apply_virtual_library()
        self.current_view().setFocus(Qt.OtherFocusReason)

    def start_smartdevice(self):
        message = None
        if self.device_manager.get_option('smartdevice', 'autostart'):
            try:
                message = self.device_manager.start_plugin('smartdevice')
            except:
                message = 'start smartdevice unknown exception'
                prints(message)
                import traceback
                traceback.print_exc()
        if message:
            if not self.device_manager.is_running('Wireless Devices'):
                error_dialog(self,
                             _('Problem starting the wireless device'),
                             _('The wireless device driver did not start. '
                               'It said "%s"') % message,
                             show=True)
        self.iactions['Connect Share'].set_smartdevice_action_state()

    def start_content_server(self, check_started=True):
        from calibre.library.server.main import start_threaded_server
        from calibre.library.server import server_config
        self.content_server = start_threaded_server(
            self.library_view.model().db,
            server_config().parse())
        self.content_server.state_callback = Dispatcher(
            self.iactions['Connect Share'].content_server_state_changed)
        if check_started:
            self.content_server.start_failure_callback = \
                Dispatcher(self.content_server_start_failed)

    def content_server_start_failed(self, msg):
        error_dialog(self,
                     _('Failed to start Content Server'),
                     _('Could not start the content server. Error:\n\n%s') %
                     msg,
                     show=True)

    def resizeEvent(self, ev):
        MainWindow.resizeEvent(self, ev)
        self.search.setMaximumWidth(self.width() - 150)

    def add_spare_server(self, *args):
        self.spare_servers.append(
            Server(limit=int(config['worker_limit'] / 2.0)))

    @property
    def spare_server(self):
        # Because of the use of the property decorator, we're called one
        # extra time. Ignore.
        if not hasattr(self, '__spare_server_property_limiter'):
            self.__spare_server_property_limiter = True
            return None
        try:
            QTimer.singleShot(1000, self.add_spare_server)
            return self.spare_servers.pop()
        except:
            pass

    def do_proceed(self, func, payload):
        if callable(func):
            func(payload)

    def no_op(self, *args):
        pass

    def system_tray_icon_activated(self, r):
        if r == QSystemTrayIcon.Trigger:
            if self.isVisible():
                self.hide_windows()
            else:
                self.show_windows()

    @property
    def is_minimized_to_tray(self):
        return getattr(self, '__systray_minimized', False)

    def ask_a_yes_no_question(self,
                              title,
                              msg,
                              det_msg='',
                              show_copy_button=False,
                              ans_when_user_unavailable=True,
                              skip_dialog_name=None,
                              skipped_value=True):
        if self.is_minimized_to_tray:
            return ans_when_user_unavailable
        return question_dialog(self,
                               title,
                               msg,
                               det_msg=det_msg,
                               show_copy_button=show_copy_button,
                               skip_dialog_name=skip_dialog_name,
                               skip_dialog_skipped_value=skipped_value)

    def hide_windows(self):
        for window in QApplication.topLevelWidgets():
            if isinstance(window, (MainWindow, QDialog)) and \
                    window.isVisible():
                window.hide()
                setattr(window, '__systray_minimized', True)

    def show_windows(self):
        for window in QApplication.topLevelWidgets():
            if getattr(window, '__systray_minimized', False):
                window.show()
                setattr(window, '__systray_minimized', False)

    def test_server(self, *args):
        if self.content_server is not None and \
                self.content_server.exception is not None:
            error_dialog(self, _('Failed to start content server'),
                         unicode(self.content_server.exception)).exec_()

    @property
    def current_db(self):
        return self.library_view.model().db

    def another_instance_wants_to_talk(self):
        try:
            msg = self.listener.queue.get_nowait()
        except Empty:
            return
        if msg.startswith('launched:'):
            import json
            try:
                argv = json.loads(msg[len('launched:'):])
            except ValueError:
                prints('Failed to decode message from other instance: %r' %
                       msg)
                if DEBUG:
                    error_dialog(
                        self,
                        'Invalid message',
                        'Received an invalid message from other calibre instance.'
                        ' Do you have multiple versions of calibre installed?',
                        det_msg='Invalid msg: %r' % msg,
                        show=True)
                argv = ()
            if isinstance(argv, (list, tuple)) and len(argv) > 1:
                files = [
                    os.path.abspath(p) for p in argv[1:]
                    if not os.path.isdir(p) and os.access(p, os.R_OK)
                ]
                if files:
                    self.iactions['Add Books'].add_filesystem_book(files)
            self.setWindowState(self.windowState() & ~Qt.WindowMinimized
                                | Qt.WindowActive)
            self.show_windows()
            self.raise_()
            self.activateWindow()
        elif msg.startswith('refreshdb:'):
            m = self.library_view.model()
            m.db.new_api.reload_from_db()
            m.db.data.refresh(clear_caches=False, do_search=False)
            m.resort()
            m.research()
            self.tags_view.recount()
        elif msg.startswith('shutdown:'):
            self.quit(confirm_quit=False)
        else:
            print msg

    def current_view(self):
        '''Convenience method that returns the currently visible view '''
        idx = self.stack.currentIndex()
        if idx == 0:
            return self.library_view
        if idx == 1:
            return self.memory_view
        if idx == 2:
            return self.card_a_view
        if idx == 3:
            return self.card_b_view

    def booklists(self):
        return self.memory_view.model().db, self.card_a_view.model(
        ).db, self.card_b_view.model().db

    def library_moved(self,
                      newloc,
                      copy_structure=False,
                      call_close=True,
                      allow_rebuild=False):
        if newloc is None:
            return
        default_prefs = None
        try:
            olddb = self.library_view.model().db
            if copy_structure:
                default_prefs = olddb.prefs

            from calibre.utils.formatter_functions import unload_user_template_functions
            unload_user_template_functions(olddb.library_id)
        except:
            olddb = None
        try:
            db = LibraryDatabase(newloc, default_prefs=default_prefs)
        except apsw.Error:
            if not allow_rebuild:
                raise
            import traceback
            repair = question_dialog(
                self,
                _('Corrupted database'),
                _('The library database at %s appears to be corrupted. Do '
                  'you want calibre to try and rebuild it automatically? '
                  'The rebuild may not be completely successful.') %
                force_unicode(newloc, filesystem_encoding),
                det_msg=traceback.format_exc())
            if repair:
                from calibre.gui2.dialogs.restore_library import repair_library_at
                if repair_library_at(newloc, parent=self):
                    db = LibraryDatabase(newloc, default_prefs=default_prefs)
                else:
                    return
            else:
                return
        if self.content_server is not None:
            self.content_server.set_database(db)
        self.library_path = newloc
        prefs['library_path'] = self.library_path
        self.book_on_device(None, reset=True)
        db.set_book_on_device_func(self.book_on_device)
        self.library_view.set_database(db)
        self.tags_view.set_database(db, self.alter_tb)
        self.library_view.model().set_book_on_device_func(self.book_on_device)
        self.status_bar.clear_message()
        self.search.clear()
        self.saved_search.clear()
        self.book_details.reset_info()
        #self.library_view.model().count_changed()
        db = self.library_view.model().db
        self.iactions['Choose Library'].count_changed(db.count())
        self.set_window_title()
        self.apply_named_search_restriction('')  # reset restriction to null
        self.saved_searches_changed(
            recount=False)  # reload the search restrictions combo box
        if db.prefs['virtual_lib_on_startup']:
            self.apply_virtual_library(db.prefs['virtual_lib_on_startup'])
        self.rebuild_vl_tabs()
        for action in self.iactions.values():
            action.library_changed(db)
        if olddb is not None:
            try:
                if call_close:
                    olddb.close()
            except:
                import traceback
                traceback.print_exc()
            olddb.break_cycles()
        if self.device_connected:
            self.set_books_in_library(self.booklists(), reset=True)
            self.refresh_ondevice()
            self.memory_view.reset()
            self.card_a_view.reset()
            self.card_b_view.reset()
        self.device_manager.set_current_library_uuid(db.library_id)
        self.library_view.set_current_row(0)
        # Run a garbage collection now so that it does not freeze the
        # interface later
        gc.collect()

    def set_window_title(self):
        db = self.current_db
        restrictions = [
            x for x in (db.data.get_base_restriction_name(),
                        db.data.get_search_restriction_name()) if x
        ]
        restrictions = ' :: '.join(restrictions)
        font = QFont()
        if restrictions:
            restrictions = ' :: ' + restrictions
            font.setBold(True)
            font.setItalic(True)
        self.virtual_library.setFont(font)
        title = u'{0} - || {1}{2} ||'.format(
            __appname__, self.iactions['Choose Library'].library_name(),
            restrictions)
        self.setWindowTitle(title)

    def location_selected(self, location):
        '''
        Called when a location icon is clicked (e.g. Library)
        '''
        page = 0 if location == 'library' else 1 if location == 'main' else 2 if location == 'carda' else 3
        self.stack.setCurrentIndex(page)
        self.book_details.reset_info()
        for x in ('tb', 'cb'):
            splitter = getattr(self, x + '_splitter')
            splitter.button.setEnabled(location == 'library')
        for action in self.iactions.values():
            action.location_selected(location)
        if location == 'library':
            self.virtual_library_menu.setEnabled(True)
            self.highlight_only_button.setEnabled(True)
        else:
            self.virtual_library_menu.setEnabled(False)
            self.highlight_only_button.setEnabled(False)
            # Reset the view in case something changed while it was invisible
            self.current_view().reset()
        self.set_number_of_books_shown()
        self.update_status_bar()

    def job_exception(self, job, dialog_title=_('Conversion Error')):
        if not hasattr(self, '_modeless_dialogs'):
            self._modeless_dialogs = []
        minz = self.is_minimized_to_tray
        if self.isVisible():
            for x in list(self._modeless_dialogs):
                if not x.isVisible():
                    self._modeless_dialogs.remove(x)
        try:
            if 'calibre.ebooks.DRMError' in job.details:
                if not minz:
                    from calibre.gui2.dialogs.drm_error import DRMErrorMessage
                    d = DRMErrorMessage(
                        self,
                        _('Cannot convert') + ' ' +
                        job.description.split(':')[-1].partition('(')[-1][:-1])
                    d.setModal(False)
                    d.show()
                    self._modeless_dialogs.append(d)
                return

            if 'calibre.ebooks.oeb.transforms.split.SplitError' in job.details:
                title = job.description.split(':')[-1].partition('(')[-1][:-1]
                msg = _('<p><b>Failed to convert: %s') % title
                msg += '<p>' + _('''
                Many older ebook reader devices are incapable of displaying
                EPUB files that have internal components over a certain size.
                Therefore, when converting to EPUB, calibre automatically tries
                to split up the EPUB into smaller sized pieces.  For some
                files that are large undifferentiated blocks of text, this
                splitting fails.
                <p>You can <b>work around the problem</b> by either increasing the
                maximum split size under EPUB Output in the conversion dialog,
                or by turning on Heuristic Processing, also in the conversion
                dialog. Note that if you make the maximum split size too large,
                your ebook reader may have trouble with the EPUB.
                        ''')
                if not minz:
                    d = error_dialog(self,
                                     _('Conversion Failed'),
                                     msg,
                                     det_msg=job.details)
                    d.setModal(False)
                    d.show()
                    self._modeless_dialogs.append(d)
                return

            if 'calibre.web.feeds.input.RecipeDisabled' in job.details:
                if not minz:
                    msg = job.details
                    msg = msg[msg.
                              find('calibre.web.feeds.input.RecipeDisabled:'):]
                    msg = msg.partition(':')[-1]
                    d = error_dialog(self, _('Recipe Disabled'),
                                     '<p>%s</p>' % msg)
                    d.setModal(False)
                    d.show()
                    self._modeless_dialogs.append(d)
                return

            if 'calibre.ebooks.conversion.ConversionUserFeedBack:' in job.details:
                if not minz:
                    import json
                    payload = job.details.rpartition(
                        'calibre.ebooks.conversion.ConversionUserFeedBack:'
                    )[-1]
                    payload = json.loads('{' + payload.partition('{')[-1])
                    d = {
                        'info': info_dialog,
                        'warn': warning_dialog,
                        'error': error_dialog
                    }.get(payload['level'], error_dialog)
                    d = d(self,
                          payload['title'],
                          '<p>%s</p>' % payload['msg'],
                          det_msg=payload['det_msg'])
                    d.setModal(False)
                    d.show()
                    self._modeless_dialogs.append(d)
                return
        except:
            pass
        if job.killed:
            return
        try:
            prints(job.details, file=sys.stderr)
        except:
            pass
        if not minz:
            self.job_error_dialog.show_error(dialog_title,
                                             _('<b>Failed</b>') + ': ' +
                                             unicode(job.description),
                                             det_msg=job.details)

    def read_settings(self):
        geometry = config['main_window_geometry']
        if geometry is not None:
            self.restoreGeometry(geometry)
        self.read_layout_settings()

    def write_settings(self):
        with gprefs:  # Only write to gprefs once
            config.set('main_window_geometry', self.saveGeometry())
            dynamic.set('sort_history', self.library_view.model().sort_history)
            self.save_layout_state()

    def quit(self,
             checked=True,
             restart=False,
             debug_on_restart=False,
             confirm_quit=True):
        if confirm_quit and not self.confirm_quit():
            return
        try:
            self.shutdown()
        except:
            pass
        self.restart_after_quit = restart
        self.debug_on_restart = debug_on_restart
        QApplication.instance().quit()

    def donate(self, *args):
        open_url(QUrl('http://calibre-ebook.com/donate'))

    def confirm_quit(self):
        if self.job_manager.has_jobs():
            msg = _('There are active jobs. Are you sure you want to quit?')
            if self.job_manager.has_device_jobs():
                msg = '<p>'+__appname__ + \
                      _(''' is communicating with the device!<br>
                      Quitting may cause corruption on the device.<br>
                      Are you sure you want to quit?''')+'</p>'

            if not question_dialog(self, _('Active jobs'), msg):
                return False
        from calibre.db.delete_service import has_jobs
        if has_jobs():
            msg = _('Some deleted books are still being moved to the Recycle '
                    'Bin, if you quit now, they will be left behind. Are you '
                    'sure you want to quit?')
            if not question_dialog(self, _('Active jobs'), msg):
                return False

        return True

    def shutdown(self, write_settings=True):
        self.grid_view.shutdown()
        try:
            db = self.library_view.model().db
            cf = db.clean
        except:
            pass
        else:
            cf()
            # Save the current field_metadata for applications like calibre2opds
            # Goes here, because if cf is valid, db is valid.
            db.prefs['field_metadata'] = db.field_metadata.all_metadata()
            db.commit_dirty_cache()
            db.prefs.write_serialized(prefs['library_path'])
        for action in self.iactions.values():
            if not action.shutting_down():
                return
        if write_settings:
            self.write_settings()
        self.check_messages_timer.stop()
        self.update_checker.terminate()
        self.listener.close()
        self.job_manager.server.close()
        self.job_manager.threaded_server.close()
        while self.spare_servers:
            self.spare_servers.pop().close()
        self.device_manager.keep_going = False
        self.auto_adder.stop()
        mb = self.library_view.model().metadata_backup
        if mb is not None:
            mb.stop()

        self.hide_windows()
        try:
            try:
                if self.content_server is not None:
                    s = self.content_server
                    self.content_server = None
                    s.exit()
            except:
                pass
        except KeyboardInterrupt:
            pass
        from calibre.db.delete_service import shutdown
        shutdown()
        time.sleep(2)
        self.istores.join()
        self.hide_windows()
        # Do not report any errors that happen after the shutdown
        sys.excepthook = sys.__excepthook__
        return True

    def run_wizard(self, *args):
        if self.confirm_quit():
            self.run_wizard_b4_shutdown = True
            self.restart_after_quit = True
            try:
                self.shutdown(write_settings=False)
            except:
                pass
            QApplication.instance().quit()

    def closeEvent(self, e):
        self.write_settings()
        if self.system_tray_icon.isVisible():
            if not dynamic['systray_msg'] and not isosx:
                info_dialog(
                    self,
                    'calibre',
                    'calibre ' +
                    _('will keep running in the system tray. To close it, '
                      'choose <b>Quit</b> in the context menu of the '
                      'system tray.'),
                    show_copy_button=False).exec_()
                dynamic['systray_msg'] = True
            self.hide_windows()
            e.ignore()
        else:
            if self.confirm_quit():
                try:
                    self.shutdown(write_settings=False)
                except:
                    import traceback
                    traceback.print_exc()
                e.accept()
            else:
                e.ignore()
class SearchBox2(QComboBox):  # {{{
    '''
    To use this class:

        * Call initialize()
        * Connect to the search() and cleared() signals from this widget.
        * Connect to the changed() signal to know when the box content changes
        * Connect to focus_to_library() signal to be told to manually change focus
        * Call search_done() after every search is complete
        * Call set_search_string() to perform a search programmatically
        * You can use the current_text property to get the current search text
          Be aware that if you are using it in a slot connected to the
          changed() signal, if the connection is not queued it will not be
          accurate.
    '''

    INTERVAL = 1500  #: Time to wait before emitting search signal
    MAX_COUNT = 25

    search = pyqtSignal(object)
    cleared = pyqtSignal()
    changed = pyqtSignal()
    focus_to_library = pyqtSignal()

    def __init__(self, parent=None):
        QComboBox.__init__(self, parent)
        self.normal_background = 'rgb(255, 255, 255, 0%)'
        self.line_edit = SearchLineEdit(self)
        self.setLineEdit(self.line_edit)

        c = self.line_edit.completer()
        c.setCompletionMode(c.PopupCompletion)
        c.highlighted[QString].connect(self.completer_used)
        c.activated[QString].connect(self.history_selected)

        self.line_edit.key_pressed.connect(self.key_pressed,
                                           type=Qt.DirectConnection)
        self.activated.connect(self.history_selected)
        self.setEditable(True)
        self.as_you_type = True
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.timer_event, type=Qt.QueuedConnection)
        self.setInsertPolicy(self.NoInsert)
        self.setMaxCount(self.MAX_COUNT)
        self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
        self.setMinimumContentsLength(25)
        self._in_a_search = False
        self.tool_tip_text = self.toolTip()

    def initialize(self, opt_name, colorize=False, help_text=_('Search')):
        self.as_you_type = config['search_as_you_type']
        self.opt_name = opt_name
        items = []
        for item in config[opt_name]:
            if item not in items:
                items.append(item)
        self.addItems(QStringList(items))
        try:
            self.line_edit.setPlaceholderText(help_text)
        except:
            # Using Qt < 4.7
            pass
        self.colorize = colorize
        self.clear()

    def normalize_state(self):
        self.setToolTip(self.tool_tip_text)
        self.line_edit.setStyleSheet(
            'QLineEdit{color:black;background-color:%s;}' %
            self.normal_background)

    def text(self):
        return self.currentText()

    def clear(self, emit_search=True):
        self.normalize_state()
        self.setEditText('')
        if emit_search:
            self.search.emit('')
        self._in_a_search = False
        self.cleared.emit()

    def clear_clicked(self, *args):
        self.clear()

    def search_done(self, ok):
        if isinstance(ok, basestring):
            self.setToolTip(ok)
            ok = False
        if not unicode(self.currentText()).strip():
            self.clear(emit_search=False)
            return
        self._in_a_search = ok
        col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
        if not self.colorize:
            col = self.normal_background
        self.line_edit.setStyleSheet(
            'QLineEdit{color:black;background-color:%s;}' % col)

    # Comes from the lineEdit control
    def key_pressed(self, event):
        k = event.key()
        if k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down,
                 Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown,
                 Qt.Key_unknown):
            return
        self.normalize_state()
        if self._in_a_search:
            self.changed.emit()
            self._in_a_search = False
        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.do_search()
            self.focus_to_library.emit()
        elif self.as_you_type and unicode(event.text()):
            self.timer.start(1500)

    # Comes from the combobox itself
    def keyPressEvent(self, event):
        k = event.key()
        if k not in (Qt.Key_Up, Qt.Key_Down):
            QComboBox.keyPressEvent(self, event)
        else:
            self.blockSignals(True)
            self.normalize_state()
            QComboBox.keyPressEvent(self, event)
            self.blockSignals(False)

    def completer_used(self, text):
        self.timer.stop()
        self.normalize_state()

    def timer_event(self):
        self.do_search()

    def history_selected(self, text):
        self.changed.emit()
        self.do_search()

    def _do_search(self, store_in_history=True):
        text = unicode(self.currentText()).strip()
        if not text:
            return self.clear()
        self.search.emit(text)

        if store_in_history:
            idx = self.findText(text, Qt.MatchFixedString)
            self.block_signals(True)
            if idx < 0:
                self.insertItem(0, text)
            else:
                t = self.itemText(idx)
                self.removeItem(idx)
                self.insertItem(0, t)
            self.setCurrentIndex(0)
            self.block_signals(False)
            history = [unicode(self.itemText(i)) for i in range(self.count())]
            config[self.opt_name] = history

    def do_search(self, *args):
        self._do_search()

    def block_signals(self, yes):
        self.blockSignals(yes)
        self.line_edit.blockSignals(yes)

    def set_search_string(self,
                          txt,
                          store_in_history=False,
                          emit_changed=True):
        if not store_in_history:
            self.activated.disconnect()
        try:
            self.setFocus(Qt.OtherFocusReason)
            if not txt:
                self.clear()
            else:
                self.normalize_state()
                self.setEditText(txt)
                self.line_edit.end(False)
                if emit_changed:
                    self.changed.emit()
                self._do_search(store_in_history=store_in_history)
            self.focus_to_library.emit()
        finally:
            if not store_in_history:
                self.activated.connect(self.history_selected)

    def search_as_you_type(self, enabled):
        self.as_you_type = enabled

    def in_a_search(self):
        return self._in_a_search

    @property
    def current_text(self):
        return unicode(self.lineEdit().text())
示例#34
0
class Splitter(QSplitter):

    state_changed = pyqtSignal(object)

    def __init__(self, name, label, icon, initial_show=True,
            initial_side_size=120, connect_button=True,
            orientation=Qt.Horizontal, side_index=0, parent=None, shortcut=None):
        QSplitter.__init__(self, parent)
        self.resize_timer = QTimer(self)
        self.resize_timer.setSingleShot(True)
        self.desired_side_size = initial_side_size
        self.desired_show = initial_show
        self.resize_timer.setInterval(5)
        self.resize_timer.timeout.connect(self.do_resize)
        self.setOrientation(orientation)
        self.side_index = side_index
        self._name = name
        self.label = label
        self.initial_side_size = initial_side_size
        self.initial_show = initial_show
        self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection)
        self.button = LayoutButton(icon, label, self, shortcut=shortcut)
        if connect_button:
            self.button.clicked.connect(self.double_clicked)

        if shortcut is not None:
            self.action_toggle = QAction(QIcon(icon), _('Toggle') + ' ' + label,
                    self)
            self.action_toggle.triggered.connect(self.toggle_triggered)
            if parent is not None:
                parent.addAction(self.action_toggle)
                if hasattr(parent, 'keyboard'):
                    parent.keyboard.register_shortcut('splitter %s %s'%(name,
                        label), unicode(self.action_toggle.text()),
                        default_keys=(shortcut,), action=self.action_toggle)
                else:
                    self.action_toggle.setShortcut(shortcut)
            else:
                self.action_toggle.setShortcut(shortcut)

    def toggle_triggered(self, *args):
        self.toggle_side_pane()

    def createHandle(self):
        return SplitterHandle(self.orientation(), self)

    def initialize(self):
        for i in range(self.count()):
            h = self.handle(i)
            if h is not None:
                h.splitter_moved()
        self.state_changed.emit(not self.is_side_index_hidden)

    def splitter_moved(self, *args):
        self.desired_side_size = self.side_index_size
        self.state_changed.emit(not self.is_side_index_hidden)

    @property
    def is_side_index_hidden(self):
        sizes = list(self.sizes())
        try:
            return sizes[self.side_index] == 0
        except IndexError:
            return True

    @property
    def save_name(self):
        ori = 'horizontal' if self.orientation() == Qt.Horizontal \
                else 'vertical'
        return self._name + '_' + ori

    def print_sizes(self):
        if self.count() > 1:
            print self.save_name, 'side:', self.side_index_size, 'other:',
            print list(self.sizes())[self.other_index]

    @dynamic_property
    def side_index_size(self):
        def fget(self):
            if self.count() < 2:
                return 0
            return self.sizes()[self.side_index]

        def fset(self, val):
            if self.count() < 2:
                return
            if val == 0 and not self.is_side_index_hidden:
                self.save_state()
            sizes = list(self.sizes())
            for i in range(len(sizes)):
                sizes[i] = val if i == self.side_index else 10
            self.setSizes(sizes)
            total = sum(self.sizes())
            sizes = list(self.sizes())
            for i in range(len(sizes)):
                sizes[i] = val if i == self.side_index else total-val
            self.setSizes(sizes)
            self.initialize()

        return property(fget=fget, fset=fset)

    def do_resize(self, *args):
        orig = self.desired_side_size
        QSplitter.resizeEvent(self, self._resize_ev)
        if orig > 20 and self.desired_show:
            c = 0
            while abs(self.side_index_size - orig) > 10 and c < 5:
                self.apply_state(self.get_state(), save_desired=False)
                c += 1

    def resizeEvent(self, ev):
        if self.resize_timer.isActive():
            self.resize_timer.stop()
        self._resize_ev = ev
        self.resize_timer.start()

    def get_state(self):
        if self.count() < 2:
            return (False, 200)
        return (self.desired_show, self.desired_side_size)

    def apply_state(self, state, save_desired=True):
        if state[0]:
            self.side_index_size = state[1]
            if save_desired:
                self.desired_side_size = self.side_index_size
        else:
            self.side_index_size = 0
        self.desired_show = state[0]

    def default_state(self):
        return (self.initial_show, self.initial_side_size)

    # Public API {{{

    def update_desired_state(self):
        self.desired_show = not self.is_side_index_hidden

    def save_state(self):
        if self.count() > 1:
            gprefs[self.save_name+'_state'] = self.get_state()

    @property
    def other_index(self):
        return (self.side_index+1)%2

    def restore_state(self):
        if self.count() > 1:
            state = gprefs.get(self.save_name+'_state',
                    self.default_state())
            self.apply_state(state, save_desired=False)
            self.desired_side_size = state[1]

    def toggle_side_pane(self, hide=None):
        if hide is None:
            action = 'show' if self.is_side_index_hidden else 'hide'
        else:
            action = 'hide' if hide else 'show'
        getattr(self, action+'_side_pane')()

    def show_side_pane(self):
        if self.count() < 2 or not self.is_side_index_hidden:
            return
        if self.desired_side_size == 0:
            self.desired_side_size = self.initial_side_size
        self.apply_state((True, self.desired_side_size))

    def hide_side_pane(self):
        if self.count() < 2 or self.is_side_index_hidden:
            return
        self.apply_state((False, self.desired_side_size))

    def double_clicked(self, *args):
        self.toggle_side_pane()
示例#35
0
class Ui_WidgetViewDetailsHelper(QtCore.QObject):
  ''' '''
  updateSensorNodeInfo = pyqtSignal("QListWidgetItem *", name="updateSensorNodeInfo")

  def __init__(self, parent = None):
    super(Ui_WidgetViewDetailsHelper, self).__init__()
    self.parentWidget = parent
    self.devicesDetails = dict()
    self.devicesItems = dict()
    self.timerUpdateInfo = QTimer(self)
    self.timerUpdateInfo.setSingleShot(True)
    
    QtCore.QObject.connect(self.timerUpdateInfo, QtCore.SIGNAL("timeout()"), self.refreshWidget)
    QtCore.QObject.connect(self, QtCore.SIGNAL("updateSensorNodeInfo(QListWidgetItem *)"), self.itemSelectedFromList)
    
  
  
  @pyqtSlot()
  def refreshWidget(self):
    ''' '''
    item = self.parentWidget.lw_sensors.selectedItems()[0]
    self.updateSensorNodeInfo.emit(item)
  
  
  @pyqtSlot("QListWidgetItem *")
  def itemSelectedFromList(self, item):
    ''' '''
    _id = self.devicesItems[item]
    _device = self.devices[_id]
    low = _device.id[0:7]
    high = _device.id[8:16] 
    self.parentWidget.sensor_details.clear()
    self.parentWidget.sensor_details.append("Address: {0}-{1}".format(low,high))
    self.parentWidget.sensor_details.append("Type: {0}".format(_device.element['isPattern']))
    self.parentWidget.sensor_details.append("Id: {0}".format(_device.element['id']))
    
    for attr in _device.attributes:
      attr_obj = _device.attributes[attr]
      self.parentWidget.sensor_details.append("{0}: {1}".format(attr_obj['name'],attr_obj['value']))
    
    self.parentWidget.sensor_details.append('\n\n')
    
    for diag in _device.diagnostic:
      diag_obj = _device.diagnostic[diag]
      self.parentWidget.sensor_details.append("{0}: {1}".format(diag, diag_obj)) 
    
    self.timerUpdateInfo.start(2000)
  

  @pyqtSlot(object, name="addSensorNodeToDetails")
  def addSensorNodeToDetails(self, device):
    ''' '''
    dump = json.loads(json.dumps(device.element)) # DEBUG
    zone = dump['id']
    low = device.id[0:7]
    high = device.id[8:16] 
    
    #print dump
    #print device.id
    
    item = QtGui.QListWidgetItem("{0} - {1} {2}".format(zone, low.upper(), high.upper()))
    self.parentWidget.lw_sensors.addItem(item)
    self.devicesItems[item] = device.id
    

  
  @pyqtSlot(object)
  def showSensorNodeAttributes(self, device):
    ''' '''
    self.devices = copy.deepcopy(device) # This is not optimal... this objects exists in three different places
    #self.devices = device # This is not optimal... this objects exists in three different places
    
  


#EOF
示例#36
0
class Ui_PartWindow(QWidget):
    """
    The Ui_PartWindow class provides a Part Window UI object composed of three
    primary areas:

    - The "left area" contains the Project TabWidget which contains
    the Model Tree and Property Manager (tabs). Other tabs (widgets)
    can be added as needed.

    - The "right area" contains the Graphics Area (i.e. glpane) displaying
    the current model.

    - The "bottom area" lives below the left and right areas, spanning
    the full width of the part window. It can be used whenever a landscape
    layout is needed (i.e. the Sequence Editor). Typically, this area is
    not used and is hidden by default.

    A "part window" splitter lives between the left and right areas that
    allow the user to resize the shared area occupied by them. There is no
    splitter between the top and bottom areas.

    This class supports and is limited to a B{Single Document Interface (SDI)}.
    In time, NE1 will migrate to and support a Multiple Document Interface (MDI)
    to allow multiple project documents (i.e. parts, assemblies,
    simulations, text files, graphs, tables, etc. documents) to be available
    within the common workspace of the NE1 main window.

    @see: U{B{NE1 Main Window Framework}
    <http://www.nanoengineer-1.net/mediawiki/index.php?title=NE1_Main_Window_Framework>}
    """
    widgets = []  # For debugging purposes.
    splitterPosition = PM_DEFAULT_WIDTH
    _previous_splitterPosition = PM_DEFAULT_WIDTH

    # Used for restoring the splitter position when collapsing/expanding
    # the left area.

    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
        # note: to support MDI, self.assy would probably need to be a
        # different assembly for each PartWindow.
        # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()

        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwLeftArea (mt and pm) and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3)  # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = LeftFrame(self)
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle(QFrame.Panel | QFrame.Sunken)

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible(0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
        # _pwProjectTabWidget subclasses QTabWidget
        # Note [bruce 070829]: to fix bug 2522 I need to intercept
        # self.pwProjectTabWidget.removeTab, so I made it a subclass of
        # QTabWidget. It needs to know the GLPane, but that's not created
        # yet, so we set it later using KLUGE_setGLPane (below).
        # Note: No parent supplied. Could this be the source of the
        # minor vsplitter resizing problem I was trying to resolve a few
        # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(self.modelTreeTab,
                                       geticon("ui/modeltree/Model_Tree.png"),
                                       "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = ModelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName(
            "propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"), "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.

        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
        # note: our owner (MWsemantics) assumes
        # there is just this one GLPane for assy, and stores it
        # into assy as assy.o and assy.glpane. [bruce 080216 comment]

        # Add what's this text to self.glpane.
        # [bruce 080912 moved this here from part of a method in class GLPane.
        #  In this code's old location, Mark wrote [2007-06-01]: "Problem -
        #  I don't believe this text is processed by fix_whatsthis_text_and_links()
        #  in whatsthis_utilities.py." Now that this code is here, I don't know
        #  whether that's still true. ]
        from ne1_ui.WhatsThisText_for_MainWindow import whats_this_text_for_glpane
        self.glpane.setWhatsThis(whats_this_text_for_glpane())

        # update [re the above comment], bruce 081209:
        # I added the following explicit call of fix_whatsthis_text_and_links,
        # but it doesn't work to replace Ctrl with Cmd on Mac;
        # see today's comment in fix_whatsthis_text_and_links for likely reason.
        # So I will leave this here, but also leave in place the kluges
        # in whats_this_text_for_glpane to do that replacement itself.
        # The wiki help link in this whatsthis text doesn't work,
        # but I guess that is an independent issue, related to lack
        # of use of class QToolBar_WikiHelp or similar code, for GLPane
        # or this class or the main window class.
        from foundation.whatsthis_utilities import fix_whatsthis_text_and_links
        fix_whatsthis_text_and_links(self.glpane)  # doesn't yet work

        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
        # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
        # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle(QFrame.Panel | QFrame.Sunken)

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()

        #This widget implementation is subject to heavy revision. The purpose
        #is to implement a NFR that Mark urgently needs : The NFR is: Need a
        #way to quickly find a node in the MT by entering its name.
        #-- Ninad 2008-11-06
        self.pwSpecialDockWidgetInLeftChannel = SelectNodeByNameDockWidget(
            self.glpane.win)
        leftChannelVBoxLayout.addWidget(self.pwSpecialDockWidgetInLeftChannel)

        # See the resizeEvent() docstring for more information about
        # resizeTimer.
        self.resizeTimer = QTimer(self)
        self.resizeTimer.setSingleShot(True)
        return

    def getLeftChannelDockWidget(self):
        return self.pwSpecialDockWidgetInLeftChannel

    def updateWindowTitle(self, changed=False):
        #by mark; bruce 050810 revised this in several ways, fixed bug 785
        """
        Update the window title (caption) at the top of the part window.
        Example:  "partname.mmp"

        This implements the standard way most applications indicate that a
        document has unsaved changes. On Mac OS X the close button will have
        a modified look; on other platforms the window title will have
        an '*' (asterisk).

        @note: We'll want to experiment with this to make sure it

        @param changed: If True, the document has unsaved changes.
        @type  changed: boolean

        @see: U{B{windowTitle}<http://doc.trolltech.com/4/qwidget.html#windowTitle-prop>},
              U{B{windowModified}<http://doc.trolltech.com/4/qwidget.html#windowModified-prop>}
        """
        # WARNING: there is mostly-duplicated code in this method and in
        # MWsemantics.update_mainwindow_caption. See the comment in that
        # method for more info and todos. [bruce 081227 comment]
        caption_fullpath = env.prefs[captionFullPath_prefs_key]

        partname = "Untitled"  # fallback value if no file yet
        if self.assy.filename:  #bruce 081227 cleanup: try -> if, etc
            # self.assy.filename is always an empty string, even after a
            # file has been opened with a complete name. Need to ask Bruce
            # about this problem, resulting in a bug (i.e. the window title
            # is always "Untitled". Mark 2008-01-02.
            junk, basename = os.path.split(self.assy.filename)
            if basename:
                if caption_fullpath:
                    partname = os.path.normpath(self.assy.filename)
                    #fixed bug 453-1 ninad060721
                else:
                    partname = basename

        # WARNING: the following code differs in the two versions
        # of this routine.
        # The "[*]" placeholder below is modified or removed by Qt; see:
        # http://doc.trolltech.com/4/qwidget.html#windowModified-prop
        self.setWindowTitle(self.trUtf8(partname + '[*]'))
        self.setWindowModified(changed)  # replaces '[*]' by '' or '*'
        return

    def collapseLeftArea(self, hideLeftArea=True):
        """
        Make the left area collapsible (via the splitter). The left area
        will be hidden (collapsed,actually) if I{hideLeftArea} is True 
        (the default).
        """
        self._previous_splitterPosition = self.pwLeftArea.width()
        if hideLeftArea:
            self.pwSplitter.setCollapsible(0, True)
            self.setSplitterPosition(pos=0)
        return

    def expandLeftArea(self):
        """
        Expand the left area.

        @see: L{MWsemantics._showFullScreenCommonCode()} for an example
        showing how it is used.
        """
        self.setSplitterPosition(pos=self._previous_splitterPosition)
        self.pwSplitter.setCollapsible(0, False)
        self.pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        self.pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)
        return

    def updatePropertyManagerTab(self, tab):  #Ninad 061207
        "Update the Properties Manager tab with 'tab' "

        self.parent.glpane.gl_update_confcorner()
        #bruce 070627, since PM affects confcorner appearance

        if self.propertyManagerScrollArea.widget():
            # The following is necessary to get rid of those C object
            # deleted errors (and the resulting bugs)
            lastwidgetobject = self.propertyManagerScrollArea.takeWidget()
            if lastwidgetobject:
                # bruce 071018 revised this code; see my comment on same
                # code in PM_Dialog
                try:
                    lastwidgetobject.update_props_if_needed_before_closing
                except AttributeError:
                    if 1 or debug_flags.atom_debug:
                        msg1 = "Last PropMgr %r doesn't have method" % lastwidgetobject
                        msg2 = " update_props_if_needed_before_closing. That's"
                        msg3 = " OK (for now, only implemented for Plane PM). "
                        msg4 = "Ignoring Exception: "
                        print_compact_traceback(msg1 + msg2 + msg3 + msg4)
                else:
                    lastwidgetobject.update_props_if_needed_before_closing()

            lastwidgetobject.hide()
            # @ ninad 061212 perhaps hiding the widget is not needed

        self.pwProjectTabWidget.removeTab(
            self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea))

        # Set the PropertyManager tab scroll area to the appropriate widget.
        self.propertyManagerScrollArea.setWidget(tab)

        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"), "")

        self.pwProjectTabWidget.setCurrentIndex(
            self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea))
        return

    def KLUGE_current_PropertyManager(self):
        #bruce 070627; revised 070829 as part of fixing bug 2523
        """
        Return the current Property Manager widget (whether or not its tab is
        chosen, but only if it has a tab), or None if there is not one.

        @warning: This method's existence (not only its implementation)
        is a kluge, since the right way to access that would be by asking
        the "command sequencer";
        but that's not yet implemented, so this is the best we can do for now.
        Also, it would be better to get the top command and talk to it, not
        its PM (a QWidget). Also, whatever calls this will be making
        assumptions about that PM which are really only the command's business.
        So in short, every call of this is in need of cleanup once we have a
        working "command sequencer". (That's true of many things related to
        PMs, not only this method.)

        @warning: The return values are (presumably) widgets, but they can
        also be mode objects and generator objects, due to excessive use of
        multiple inheritance in the current PM code. So be careful what you
        do with them -- they might have lots of extra methods/attrs,
        and setting your own attrs in them might mess things up.
        """
        res = self.propertyManagerScrollArea.widget()
        if not hasattr(res, 'done_btn'):
            # not sure what widget this is otherwise, but it is a widget
            # (not None) for the default mode, at least on startup, so just
            # return None in this case
            return None
        # Sometimes this PM remains present from a prior command, even when
        # there is no longer a tab for the PM. As part of fixing bug 2523
        # we have to avoid returning it in that case. How we do that is a kluge,
        # but hopefully this entire kluge function can be dispensed with soon.
        # This change also fixes bug 2522 on the Mac (but not on Windows --
        # for that, we needed to intercept removeTab in separate code above).
        index = self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea)
        if index == -1:
            return None
        # Due to bugs in other code, sometimes the PM tab is left in place,
        # though the PM itself is hidden. To avoid finding the PM in that case,
        # also check whether it's hidden. This will fix the CC part of a new bug
        # just reported by Keith in email (when hitting Ok in DNA Gen).
        if res.isHidden():  # probably a QWidget method [bruce 080205 comment]
            return None
        return res

    def dismiss(self):
        self.parent.removePartWindow(self)
        return

    def setSplitterPosition(self, pos=PM_DEFAULT_WIDTH, setDefault=True):
        """
        Set the position of the splitter between the left area and graphics area
        so that the width of the container holding the model tree (and 
        property manager) is I{pos} pixels wide.
        
        @param pos: The splitter position (in pixel units).
        @type  pos: int
        
        @param setDefault: If True (the default), I{pos} becomes the new default
                           position.
        @type  setDefault: boolean
        """
        self.pwSplitter.moveSplitter(pos, 1)
        if _DEBUG:
            print "New Splitter Position: %d (setDefault=%d)" \
                  % (pos, setDefault)
        if setDefault:
            self.splitterPosition = pos
        return

    def resizeEvent(self, event):
        """
        This reimplementation of QWidget.resizeEvent is here to deal with the
        undesired behavior of the splitter while resizing the part window.
        Normally, the splitter will drift back and forth while resizing
        the part window. This forces the splitter to stay fixed during
        resize operations.
        """
        # When self.resizeTimer.isActive() = True, the partwindow is being
        # resized. This is checked by the resizeEvent handler in LeftFrame
        # to determine if the splitter is being moved by the user or
        # programmably by self's resizeEvent.
        if self.resizeTimer.isActive():
            self.resizeTimer.stop()  # Stop the timer.
        self.resizeTimer.start(500)  # (Re)strand a .5 second singleshot timer.
        self.setSplitterPosition(self.splitterPosition, setDefault=False)
        QWidget.resizeEvent(self, event)
        return
示例#37
0
class MainProgramm:
    
    PointList = [None]*8
    PointTimeOutList = [5]*8
    JointList = []
    #updateThread = []
    ReferencePoint = None
    TrackReferencePoint = None
    device_list = [None,None]
    renderTimer = None
    recording = False
    RecordingTmpFile = None
    DataSaved = True
    State = "S" # S-Stop/Pause , P-Play , L-Live(Record) 
    PlaybackPos = 0 
    CoM = QVector2D(0,0)
    #Trafos
    #Urbilder der Trafos
    BilderA = [None]*4
    BilderB = [None]*4   
    #PhysKS-> ViewKS

    Phys_View_KS = matrix([[200.0,0.0,500.0],[0.0,-200.0,500.0],[0.0,0.0,1.0]])
    #MoteKS-> PhysKS
    M_Phys_KS = [Phys_View_KS**-1,Phys_View_KS**-1]
    M_View_KS = [matrix(identity(3)),matrix(identity(3))]
    
    def __init__(self ):
    

        self.Window = MainWindow.MainWindow(self)
        self.Window.show()
        self.renderTimer = QTimer()
        self.renderTimer.start(20)
        self.renderTimer.timeout.connect(self.Repaint)
        self.updateTimer = QTimer()
        self.updateTimer.timeout.connect(self.report)

                
    def report(self):
        cnt = 0
        for device in self.device_list:
          
            if not device == None:
                data = device.state['ir_src']
                for i in range(0,4):
                    if not data[i] == None:
                        #Position des Punktes updateten
                        Point =  HomPoint(data[i]['pos'][0]*1.0,data[i]['pos'][1]*1.0)
                        self.PointList[i+4*cnt] = Point
                        self.PointTimeOutList[i+4*cnt] = 0
                    else:
                        #Punkt Connection Lost counter
                        if(self.PointTimeOutList[i+4*cnt] >= 5):
                            self.PointTimeOutList[i+4*cnt] = 5
                            self.PointList[i+4*cnt] = None
                        else:
                            self.PointTimeOutList[i+4*cnt] += 1
                cnt += 1
            
        if self.recording:
            self.RecordingTmpFile.append(copy(self.PointList))



    def CreateJoint(self,PointList):
        diag = MassRadDiag()
        if diag.exec_() == QDialog.Accepted:
            self.JointList.append((set([i.ID() for i in PointList]),diag.mass.value(), diag.radius.value()))
        self.Window.PointSelected()
        
    def DeleteJoint(self,PointList):
        PointsToRemove = set([i.ID() for i in PointList])
        for joint in self.JointList:
            if joint[0] == PointsToRemove:
                self.JointList.remove(joint)
                break
        self.Window.connect_button.setText("Punkte Verbinden")
        
    def DeleteAllJoints(self):
        self.JointList = []
        
    def ResetReferencePoint(self):
        self.ReferencePoint = None
        self.TrackReferencePoint = None
        
    def setReferencePoint(self, Point):
        oldRefPoint = self.ReferencePoint
        self.ReferencePoint = Point.ID()
        if not oldRefPoint == self.ReferencePoint:
            self.Window.Panel.setReferencePoint(self.ReferencePoint, oldRefPoint)
        else:
            self.ReferencePoint = None 
            self.Window.Panel.setReferencePoint(None, oldRefPoint)


    def Repaint(self):

        PointsToDraw = []
        IDstoDraw = []
        for i in range(8):
            if not self.PointList[i] == None:
                # Trafo PhysKS -> View KS
                if i < 4:
                    Point =   self.M_View_KS[0]*self.PointList[i]
                else:
                    Point =   self.M_View_KS[1]*self.PointList[i]                    
                PointsToDraw.append(QPointF(Point.item(0)/Point.item(2),Point.item(1)/Point.item(2)))
                IDstoDraw.append(i)
            else:
                PointsToDraw.append(None)
                JointsToDraw = []
        for joint in self.JointList:
            if set(IDstoDraw) >= joint[0]:
                JointsToDraw.append(joint)            
        self.Window.Panel.redraw(PointsToDraw,JointsToDraw, self.DoPhysics())

 
    def update_device_list(self):
        cnt = 0
        for i in self.device_list:
            if(isinstance(i, cwiid.Wiimote)):
                print 'Updates für Geraet ' + str(cnt) + 'gestartet'
                cnt += 1
            else:
                print 'Hier kein Gerät'
        if not cnt == 0:
            self.Window.live_radio.setEnabled(True)
            self.Window.live_radio.setChecked(True)
            self.Window.rec.show()

        else:
            self.Window.live_radio.setDisabled(True) 
            self.Window.hide()
                
                
    def DoPhysics(self):
        
        
        JointsToUse = []
        
        for joint in self.JointList:
            if  self.PointList[list(joint[0])[0]] != None and   self.PointList[list(joint[0])[1]] != None:
                JointsToUse.append(joint)      
        
        #Berechung des CoM
        old_CoM = self.CoM
        self.CoM.setX(0)
        self.CoM.setY(0)
        TotalMass = 0
        for joint in JointsToUse:

            self.CoM += joint[1] * QVector2D(self.CenterOfJoint(joint).x(), self.CenterOfJoint(joint).y())
            TotalMass += joint[1]
        
        self.CoM = self.CoM/TotalMass
        if TotalMass == 0:
            return None
        
        KE = QVector2D.dotProduct(self.CoM-old_CoM,(self.CoM-old_CoM))*TotalMass*0.5
        V = 9.81*self.CoM.y()*TotalMass
        self.Window.setEnergies(KE,V)
        return self.CoM
        
        
        
        
    def CenterOfJoint(self, Joint):
        
        return 0.5*(self.PointList[list(Joint[0])[0]]+self.PointList[list(Joint[0])[1]])

    def ConnectWiimotes(self):
            diag = ConnectDiag(self.device_list)
            diag.exec_()
            self.update_device_list()
            
    def StartRecording(self):
        self.recording = True
        self.RecordingTmpFile = []
        return
    
    def StopRecording(self):
        self.recording = False
        self.DataSaved = False

        ExportCSV(self.RecordingTmpFile,'test1.csv')
        
    def Export(self, Filename):
        if not ExportCSV(self.RecordingTmpFile, Filename):
            QMessageBox(QMessageBox.Critical, "Fehler", "Speichern der Datei fehlgeschlagen", buttons = QMessageBox.Ok).exec_()

    
    def SaveFile(self, Filenmame):
        if not SaveData(self.RecordingTmpFile, self.JointList, self.ReferencePoint, Filenmame):
            QMessageBox(QMessageBox.Critical, "Fehler", "Speichern der Datei fehlgeschlagen", buttons = QMessageBox.Ok).exec_()
        else:
            self.DataSaved = True
       
    def LoadFile(self, Filename):
        data = LoadData(Filename)
        if data[0] :#Wurde allles korrekt gelesen?
            self.RecordingTmpFile ,self.JointList, self.ReferencePoint =  data[1]
            self.Window.playback_radio.setEnabled(True)
            self.Window.playback_radio.setChecked(True)

        else:
            QMessageBox(QMessageBox.Critical, "Fehler", "Laden der Datei fehlgeschlagen", buttons = QMessageBox.Ok).exec_()
            
    
    def LiveRadioToggled(self, checked):

        if checked:
            self.State = 'L'
            print "Hier auch"
            self.updateTimer.start(10)
        
            
        return 
    
    def PlaybackRadioToggled(self, checked):
        if checked:
            self.State = 'S'
            self.updateTimer.stop()
            self.PointList = self.RecordingTmpFile[0]
            self.PlaybackPos = 0
        return
        
        
        
    def PlayButtonToggled(self):
        self.State = "P"

    def SetKSTrafo(self, device_id):      
        
        unit =  [(0,0),(1,0),(0,1),(1,1)]
        bild = []
        if device_id == 0:
            for point in self.BilderA:
                bild.append((point.x(),point.y()))
        else:
            for point in self.BilderB:
                bild.append((point.x(),point.y()))
                        
        
        
        self.M_Phys_KS[device_id] = Homography( bild,unit)
        self.M_View_KS[device_id] = self.Phys_View_KS* self.M_Phys_KS[0]
        print self.M_Phys_KS[device_id]
    def AddCalPoint(self, Point, Type):
        ID = Point.ID()
        #Punkte in die Datenbank aufnehmen
        vector = self.PointList[ID]
        print vector
        if ID <4:
            self.BilderA[Type] = vector
            print self.BilderA.count(None) 
            if self.BilderA.count(None) == 0:
                self.Window.cal_state_A.setText('Kalibriert')
                print self.BilderA
                self.SetKSTrafo(0)
        else:
            self.BilderB[Type] = vector         
            if self.BilderB.count(None) == 0:
                self.Window.cal_state_B.setText('Kalibriert')
                self.SetKSTrafo(1)            

    def resetA(self):
        self.M_Phys_KS[0] = None
        self.M_View_KS[0] = identity(3)
        self.BilderA = [None]*4

    def resetB(self):
        self.M_Phys_KS[1] = None
        self.M_View_KS[1] = identity(3)   
        self.BilderB = [None]*4    
示例#38
0
class CoverDelegate(QStyledItemDelegate):  # {{{

    needs_redraw = pyqtSignal()

    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)

        self.angle = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.frame_changed)
        self.color = parent.palette().color(QPalette.WindowText)
        self.spinner_width = 64

    def frame_changed(self, *args):
        self.angle = (self.angle + 30) % 360
        self.needs_redraw.emit()

    def start_animation(self):
        self.angle = 0
        self.timer.start(200)

    def stop_animation(self):
        self.timer.stop()

    def draw_spinner(self, painter, rect):
        width = rect.width()

        outer_radius = (width - 1) * 0.5
        inner_radius = (width - 1) * 0.5 * 0.38

        capsule_height = outer_radius - inner_radius
        capsule_width = int(capsule_height * (0.23 if width > 32 else 0.35))
        capsule_radius = capsule_width // 2

        painter.save()
        painter.setRenderHint(painter.Antialiasing)

        for i in xrange(12):
            color = QColor(self.color)
            color.setAlphaF(1.0 - (i / 12.0))
            painter.setPen(Qt.NoPen)
            painter.setBrush(color)
            painter.save()
            painter.translate(rect.center())
            painter.rotate(self.angle - i * 30.0)
            painter.drawRoundedRect(-capsule_width * 0.5,
                                    -(inner_radius + capsule_height),
                                    capsule_width, capsule_height,
                                    capsule_radius, capsule_radius)
            painter.restore()
        painter.restore()

    def paint(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, index)
        style = QApplication.style()
        waiting = self.timer.isActive() and index.data(Qt.UserRole).toBool()
        if waiting:
            rect = QRect(0, 0, self.spinner_width, self.spinner_width)
            rect.moveCenter(option.rect.center())
            self.draw_spinner(painter, rect)
        else:
            # Ensure the cover is rendered over any selection rect
            style.drawItemPixmap(painter, option.rect,
                                 Qt.AlignTop | Qt.AlignHCenter,
                                 QPixmap(index.data(Qt.DecorationRole)))
示例#39
0
class ScudCloud(QtGui.QMainWindow):

    forceClose = False
    messages = 0
    speller = Speller()
    title = 'ScudCloud'

    def __init__(self,
                 debug=False,
                 parent=None,
                 minimized=None,
                 urgent_hint=None,
                 settings_path=""):
        super(ScudCloud, self).__init__(parent)
        self.debug = debug
        self.minimized = minimized
        self.urgent_hint = urgent_hint
        self.setWindowTitle(self.title)
        self.settings_path = settings_path
        self.notifier = Notifier(Resources.APP_NAME,
                                 Resources.get_path('scudcloud.png'))
        self.settings = QSettings(self.settings_path + '/scudcloud.cfg',
                                  QSettings.IniFormat)
        self.notifier.enabled = self.settings.value('Notifications',
                                                    defaultValue=True,
                                                    type=bool)
        self.identifier = self.settings.value("Domain")
        if Unity is not None:
            self.launcher = Unity.LauncherEntry.get_for_desktop_id(
                "scudcloud.desktop")
        else:
            self.launcher = DummyLauncher(self)
        self.webSettings()
        self.leftPane = LeftPane(self)
        self.stackedWidget = QtGui.QStackedWidget()
        centralWidget = QtGui.QWidget(self)
        layout = QtGui.QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.leftPane)
        layout.addWidget(self.stackedWidget)
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)
        self.startURL = Resources.SIGNIN_URL
        if self.identifier is not None:
            if isinstance(self.identifier, str):
                self.domains = self.identifier.split(",")
            else:
                self.domains = self.identifier
            self.startURL = self.normalize(self.domains[0])
        else:
            self.domains = []
        self.addWrapper(self.startURL)
        self.addMenu()
        self.tray = Systray(self)
        self.systray(self.minimized)
        self.installEventFilter(self)
        self.statusBar().showMessage('Loading Slack...')
        self.tickler = QTimer(self)
        self.tickler.setInterval(1800000)
        # Watch for ScreenLock events
        if DBusQtMainLoop is not None:
            DBusQtMainLoop(set_as_default=True)
            sessionBus = dbus.SessionBus()
            # Ubuntu 12.04 and other distros
            sessionBus.add_match_string(
                "type='signal',interface='org.gnome.ScreenSaver'")
            # Ubuntu 14.04 and above
            sessionBus.add_match_string(
                "type='signal',interface='com.ubuntu.Upstart0_6'")
            sessionBus.add_message_filter(self.screenListener)
            self.tickler.timeout.connect(self.sendTickle)
        # If dbus is not present, tickler timer will act like a blocker to not send tickle too often
        else:
            self.tickler.setSingleShot(True)
        self.tickler.start()

    def screenListener(self, bus, message):
        event = message.get_member()
        # "ActiveChanged" for Ubuntu 12.04 and other distros. "EventEmitted" for Ubuntu 14.04 and above
        if event == "ActiveChanged" or event == "EventEmitted":
            arg = message.get_args_list()[0]
            # True for Ubuntu 12.04 and other distros. "desktop-lock" for Ubuntu 14.04 and above
            if (arg == True
                    or arg == "desktop-lock") and self.tickler.isActive():
                self.tickler.stop()
            elif (arg == False
                  or arg == "desktop-unlock") and not self.tickler.isActive():
                self.sendTickle()
                self.tickler.start()

    def sendTickle(self):
        for i in range(0, self.stackedWidget.count()):
            self.stackedWidget.widget(i).sendTickle()

    def addWrapper(self, url):
        webView = Wrapper(self)
        webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
        webView.page().networkAccessManager().setCache(self.diskCache)
        webView.load(QtCore.QUrl(url))
        webView.show()
        self.stackedWidget.addWidget(webView)
        self.stackedWidget.setCurrentWidget(webView)

    def webSettings(self):
        self.cookiesjar = PersistentCookieJar(self)
        self.zoom = self.readZoom()
        # We don't want Flash (it causes a lot of trouble in some distros)
        QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled,
                                                   False)
        # We don't need Java
        QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled,
                                                   False)
        # Enabling Local Storage (now required by Slack)
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.LocalStorageEnabled, True)
        # We need browsing history (required to not limit LocalStorage)
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.PrivateBrowsingEnabled, False)
        # Enabling Cache
        self.diskCache = QNetworkDiskCache(self)
        self.diskCache.setCacheDirectory(self.settings_path)
        # Required for copy and paste clipboard integration
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.JavascriptCanAccessClipboard, True)
        # Enabling Inspeclet only when --debug=True (requires more CPU usage)
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.DeveloperExtrasEnabled, self.debug)

    def toggleFullScreen(self):
        if self.isFullScreen():
            self.showMaximized()
        else:
            self.showFullScreen()

    def toggleMenuBar(self):
        menu = self.menuBar()
        state = menu.isHidden()
        menu.setVisible(state)
        if state:
            self.settings.setValue("Menu", "False")
        else:
            self.settings.setValue("Menu", "True")

    def restore(self):
        geometry = self.settings.value("geometry")
        if geometry is not None:
            self.restoreGeometry(geometry)
        windowState = self.settings.value("windowState")
        if windowState is not None:
            self.restoreState(windowState)
        else:
            self.setWindowState(QtCore.Qt.WindowMaximized)

    def systray(self, show=None):
        if show is None:
            show = self.settings.value("Systray") == "True"
        if show:
            self.tray.show()
            self.menus["file"]["close"].setEnabled(True)
            self.settings.setValue("Systray", "True")
        else:
            self.tray.setVisible(False)
            self.menus["file"]["close"].setEnabled(False)
            self.settings.setValue("Systray", "False")

    def readZoom(self):
        default = 1
        if self.settings.value("Zoom") is not None:
            default = float(self.settings.value("Zoom"))
        return default

    def setZoom(self, factor=1):
        if factor > 0:
            for i in range(0, self.stackedWidget.count()):
                widget = self.stackedWidget.widget(i)
                widget.setZoomFactor(factor)
            self.settings.setValue("Zoom", factor)

    def zoomIn(self):
        self.setZoom(self.current().zoomFactor() + 0.1)

    def zoomOut(self):
        self.setZoom(self.current().zoomFactor() - 0.1)

    def zoomReset(self):
        self.setZoom()

    def addTeam(self):
        self.switchTo(Resources.SIGNIN_URL)

    def addMenu(self):
        # We'll register the webpage shorcuts with the window too (Fixes #338)
        undo = self.current().pageAction(QtWebKit.QWebPage.Undo)
        redo = self.current().pageAction(QtWebKit.QWebPage.Redo)
        cut = self.current().pageAction(QtWebKit.QWebPage.Cut)
        copy = self.current().pageAction(QtWebKit.QWebPage.Copy)
        paste = self.current().pageAction(QtWebKit.QWebPage.Paste)
        back = self.current().pageAction(QtWebKit.QWebPage.Back)
        forward = self.current().pageAction(QtWebKit.QWebPage.Forward)
        reload = self.current().pageAction(QtWebKit.QWebPage.Reload)
        self.menus = {
            "file": {
                "preferences":
                self.createAction("Preferences",
                                  lambda: self.current().preferences()),
                "systray":
                self.createAction("Close to Tray", self.systray, None, True),
                "addTeam":
                self.createAction("Sign in to Another Team",
                                  lambda: self.addTeam()),
                "signout":
                self.createAction("Signout", lambda: self.current().logout()),
                "close":
                self.createAction("Close", self.close, QKeySequence.Close),
                "exit":
                self.createAction("Quit", self.exit, QKeySequence.Quit)
            },
            "edit": {
                "undo":
                self.createAction(
                    undo.text(), lambda: self.current().page().triggerAction(
                        QtWebKit.QWebPage.Undo), undo.shortcut()),
                "redo":
                self.createAction(
                    redo.text(), lambda: self.current().page().triggerAction(
                        QtWebKit.QWebPage.Redo), redo.shortcut()),
                "cut":
                self.createAction(
                    cut.text(), lambda: self.current().page().triggerAction(
                        QtWebKit.QWebPage.Cut), cut.shortcut()),
                "copy":
                self.createAction(
                    copy.text(), lambda: self.current().page().triggerAction(
                        QtWebKit.QWebPage.Copy), copy.shortcut()),
                "paste":
                self.createAction(
                    paste.text(), lambda: self.current().page().triggerAction(
                        QtWebKit.QWebPage.Paste), paste.shortcut()),
                "back":
                self.createAction(
                    back.text(), lambda: self.current().page().triggerAction(
                        QtWebKit.QWebPage.Back), back.shortcut()),
                "forward":
                self.createAction(
                    forward.text(), lambda: self.current().page()
                    .triggerAction(QtWebKit.QWebPage.Forward),
                    forward.shortcut()),
                "reload":
                self.createAction(
                    reload.text(), lambda: self.current().page().triggerAction(
                        QtWebKit.QWebPage.Reload), reload.shortcut()),
            },
            "view": {
                "zoomin":
                self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn),
                "zoomout":
                self.createAction("Zoom Out", self.zoomOut,
                                  QKeySequence.ZoomOut),
                "reset":
                self.createAction("Reset", self.zoomReset,
                                  QtCore.Qt.CTRL + QtCore.Qt.Key_0),
                "fullscreen":
                self.createAction("Toggle Full Screen", self.toggleFullScreen,
                                  QtCore.Qt.Key_F11),
                "hidemenu":
                self.createAction("Toggle Menubar", self.toggleMenuBar,
                                  QtCore.Qt.Key_F12)
            },
            "help": {
                "help":
                self.createAction("Help and Feedback",
                                  lambda: self.current().help(),
                                  QKeySequence.HelpContents),
                "center":
                self.createAction("Slack Help Center",
                                  lambda: self.current().helpCenter()),
                "about":
                self.createAction("About", lambda: self.current().about())
            }
        }
        menu = self.menuBar()
        fileMenu = menu.addMenu("&File")
        fileMenu.addAction(self.menus["file"]["preferences"])
        fileMenu.addAction(self.menus["file"]["systray"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["addTeam"])
        fileMenu.addAction(self.menus["file"]["signout"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["close"])
        fileMenu.addAction(self.menus["file"]["exit"])
        editMenu = menu.addMenu("&Edit")
        editMenu.addAction(self.menus["edit"]["undo"])
        editMenu.addAction(self.menus["edit"]["redo"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["cut"])
        editMenu.addAction(self.menus["edit"]["copy"])
        editMenu.addAction(self.menus["edit"]["paste"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["back"])
        editMenu.addAction(self.menus["edit"]["forward"])
        editMenu.addAction(self.menus["edit"]["reload"])
        viewMenu = menu.addMenu("&View")
        viewMenu.addAction(self.menus["view"]["zoomin"])
        viewMenu.addAction(self.menus["view"]["zoomout"])
        viewMenu.addAction(self.menus["view"]["reset"])
        viewMenu.addSeparator()
        viewMenu.addAction(self.menus["view"]["fullscreen"])
        viewMenu.addAction(self.menus["view"]["hidemenu"])
        helpMenu = menu.addMenu("&Help")
        helpMenu.addAction(self.menus["help"]["help"])
        helpMenu.addAction(self.menus["help"]["center"])
        helpMenu.addSeparator()
        helpMenu.addAction(self.menus["help"]["about"])
        self.enableMenus(False)
        showSystray = self.settings.value("Systray") == "True"
        self.menus["file"]["systray"].setChecked(showSystray)
        self.menus["file"]["close"].setEnabled(showSystray)
        # Restore menu visibility
        visible = self.settings.value("Menu")
        if visible is not None and visible == "False":
            menu.setVisible(False)

    def enableMenus(self, enabled):
        self.menus["file"]["preferences"].setEnabled(enabled == True)
        self.menus["file"]["addTeam"].setEnabled(enabled == True)
        self.menus["file"]["signout"].setEnabled(enabled == True)
        self.menus["help"]["help"].setEnabled(enabled == True)

    def createAction(self, text, slot, shortcut=None, checkable=False):
        action = QtGui.QAction(text, self)
        action.triggered.connect(slot)
        if shortcut is not None:
            action.setShortcut(shortcut)
            self.addAction(action)
        if checkable:
            action.setCheckable(True)
        return action

    def normalize(self, url):
        if url.endswith(".slack.com"):
            url += "/"
        elif not url.endswith(".slack.com/"):
            url = "https://" + url + ".slack.com/"
        return url

    def current(self):
        return self.stackedWidget.currentWidget()

    def teams(self, teams):
        if len(self.domains) == 0:
            self.domains.append(teams[0]['team_url'])
        team_list = [t['team_url'] for t in teams]
        for t in teams:
            for i in range(0, len(self.domains)):
                self.domains[i] = self.normalize(self.domains[i])
                # When team_icon is missing, the team already exists (Fixes #381, #391)
                if 'team_icon' in t:
                    if self.domains[i] in team_list:
                        add = next(item for item in teams
                                   if item['team_url'] == self.domains[i])
                        if 'team_icon' in add:
                            self.leftPane.addTeam(add['id'], add['team_name'],
                                                  add['team_url'],
                                                  add['team_icon']['image_44'],
                                                  add == teams[0])
                            # Adding new teams and saving loading positions
                            if t['team_url'] not in self.domains:
                                self.leftPane.addTeam(
                                    t['id'], t['team_name'], t['team_url'],
                                    t['team_icon']['image_44'], t == teams[0])
                                self.domains.append(t['team_url'])
                                self.settings.setValue("Domain", self.domains)
        if len(teams) > 1:
            self.leftPane.show()

    def switchTo(self, url):
        exists = False
        for i in range(0, self.stackedWidget.count()):
            if self.stackedWidget.widget(i).url().toString().startswith(url):
                self.stackedWidget.setCurrentIndex(i)
                self.quicklist(self.current().listChannels())
                self.current().setFocus()
                self.leftPane.click(i)
                exists = True
                break
        if not exists:
            self.addWrapper(url)

    def eventFilter(self, obj, event):
        if event.type(
        ) == QtCore.QEvent.ActivationChange and self.isActiveWindow():
            self.focusInEvent(event)
        if event.type() == QtCore.QEvent.KeyPress:
            # Ctrl + <n>
            modifiers = QtGui.QApplication.keyboardModifiers()
            if modifiers == QtCore.Qt.ControlModifier:
                if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0)
                elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1)
                elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2)
                elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3)
                elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4)
                elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5)
                elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6)
                elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7)
                elif event.key() == QtCore.Qt.Key_9:
                    self.leftPane.click(8)
                    # Ctrl + Tab
                elif event.key() == QtCore.Qt.Key_Tab:
                    self.leftPane.clickNext(1)
            # Ctrl + BackTab
            if (modifiers & QtCore.Qt.ControlModifier) and (
                    modifiers & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_Backtab:
                    self.leftPane.clickNext(-1)
            # Ctrl + Shift + <key>
            if (modifiers & QtCore.Qt.ShiftModifier) and (
                    modifiers & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_V:
                    self.current().createSnippet()
        return QtGui.QMainWindow.eventFilter(self, obj, event)

    def focusInEvent(self, event):
        self.launcher.set_property("urgent", False)
        self.tray.stopAlert()
        # Let's tickle all teams on window focus, but only if tickle was not fired in last 30 minutes
        if DBusQtMainLoop is None and not self.tickler.isActive():
            self.sendTickle()
            self.tickler.start()

    def titleChanged(self):
        self.setWindowTitle(self.current().title())

    def setForceClose(self):
        self.forceClose = True

    def closeEvent(self, event):
        if not self.forceClose and self.settings.value("Systray") == "True":
            self.hide()
            event.ignore()
        else:
            self.cookiesjar.save()
            self.settings.setValue("Domain", self.domains)
            self.settings.setValue("geometry", self.saveGeometry())
            self.settings.setValue("windowState", self.saveState())
        self.forceClose = False

    def show(self):
        self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized
                            | QtCore.Qt.WindowActive)
        self.activateWindow()
        self.setVisible(True)

    def exit(self):
        self.setForceClose()
        self.close()

    def quicklist(self, channels):
        if Dbusmenu is not None:
            if channels is not None:
                ql = Dbusmenu.Menuitem.new()
                self.launcher.set_property("quicklist", ql)
                for c in channels:
                    if hasattr(c, '__getitem__') and c['is_member']:
                        item = Dbusmenu.Menuitem.new()
                        item.property_set(Dbusmenu.MENUITEM_PROP_LABEL,
                                          "#" + c['name'])
                        item.property_set("id", c['name'])
                        item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE,
                                               True)
                        item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
                                     self.current().openChannel)
                        ql.child_append(item)
                self.launcher.set_property("quicklist", ql)

    def notify(self, title, message, icon):
        if self.debug:
            print("Notification: title [{}] message [{}] icon [{}]".format(
                title, message, icon))
        self.notifier.notify(title, message, icon)
        self.alert()

    def alert(self):
        if not self.isActiveWindow():
            self.launcher.set_property("urgent", True)
            self.tray.alert()
        if self.urgent_hint is True:
            QApplication.alert(self)

    def count(self):
        total = 0
        unreads = 0
        for i in range(0, self.stackedWidget.count()):
            widget = self.stackedWidget.widget(i)
            highlights = widget.highlights
            unreads += widget.unreads
            total += highlights
        if total > self.messages:
            self.alert()
        if 0 == total:
            self.launcher.set_property("count_visible", False)
            self.tray.setCounter(0)
            if unreads > 0:
                self.setWindowTitle("*{}".format(self.title))
            else:
                self.setWindowTitle(self.title)
        else:
            self.tray.setCounter(total)
            self.launcher.set_property("count", total)
            self.launcher.set_property("count_visible", True)
            self.setWindowTitle("[{}]{}".format(str(total), self.title))
        self.messages = total
示例#40
0
class ScudCloud(QtGui.QMainWindow):

    forceClose = False
    messages = 0
    speller = Speller()
    title = 'ScudCloud'

    def __init__(self, debug = False, parent = None, minimized = None, settings_path = ""):
        super(ScudCloud, self).__init__(parent)
        self.debug = debug
        self.minimized = minimized
        self.setWindowTitle(self.title)
        self.settings_path = settings_path
        self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png'))
        self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat)
        self.identifier = self.settings.value("Domain")
        if Unity is not None:
            self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop")
        else:
            self.launcher = DummyLauncher(self)
        self.webSettings()
        self.leftPane = LeftPane(self)
        self.stackedWidget = QtGui.QStackedWidget()
        centralWidget = QtGui.QWidget(self)
        layout = QtGui.QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.leftPane)
        layout.addWidget(self.stackedWidget)
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)
        self.startURL = Resources.SIGNIN_URL
        if self.identifier is not None:
            if isinstance(self.identifier, str):
                self.domains = self.identifier.split(",")
            else:
                self.domains = self.identifier
            self.startURL = self.normalize(self.domains[0])
        else:
            self.domains = []
        self.addWrapper(self.startURL)
        self.addMenu()
        self.tray = Systray(self)
        self.systray(self.minimized)
        self.installEventFilter(self)
        self.statusBar().showMessage('Loading Slack...')
        self.tickler = QTimer(self)
        self.tickler.setInterval(1800000)
        # Watch for ScreenLock events
        if DBusQtMainLoop is not None:
            DBusQtMainLoop(set_as_default=True)
            sessionBus = dbus.SessionBus()
            # Ubuntu 12.04 and other distros
            sessionBus.add_match_string("type='signal',interface='org.gnome.ScreenSaver'")
            # Ubuntu 14.04 and above
            sessionBus.add_match_string("type='signal',interface='com.ubuntu.Upstart0_6'")
            sessionBus.add_message_filter(self.screenListener)
            self.tickler.timeout.connect(self.sendTickle)
        # If dbus is not present, tickler timer will act like a blocker to not send tickle too often
        else:
            self.tickler.setSingleShot(True)
        self.tickler.start()

    def screenListener(self, bus, message):
        event = message.get_member()
        # "ActiveChanged" for Ubuntu 12.04 and other distros. "EventEmitted" for Ubuntu 14.04 and above
        if event == "ActiveChanged" or event == "EventEmitted":
            arg = message.get_args_list()[0]
            # True for Ubuntu 12.04 and other distros. "desktop-lock" for Ubuntu 14.04 and above
            if (arg == True or arg == "desktop-lock") and self.tickler.isActive():
                self.tickler.stop()
            elif (arg == False or arg == "desktop-unlock") and not self.tickler.isActive():
                self.sendTickle()
                self.tickler.start()

    def sendTickle(self):
        for i in range(0, self.stackedWidget.count()):
            self.stackedWidget.widget(i).sendTickle()

    def addWrapper(self, url):
        webView = Wrapper(self)
        webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
        webView.page().networkAccessManager().setCache(self.diskCache)
        webView.load(QtCore.QUrl(url))
        webView.show()
        self.stackedWidget.addWidget(webView)
        self.stackedWidget.setCurrentWidget(webView)

    def webSettings(self):
        self.cookiesjar = PersistentCookieJar(self)
        self.zoom = self.readZoom()
        # We don't want Flash (it causes a lot of trouble in some distros)
        QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, False)
        # We don't need Java
        QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False)
        # Enabling Local Storage (now required by Slack)
        QWebSettings.globalSettings().setAttribute(QWebSettings.LocalStorageEnabled, True)
        # We need browsing history (required to not limit LocalStorage)
        QWebSettings.globalSettings().setAttribute(QWebSettings.PrivateBrowsingEnabled, False)
        # Enabling Cache
        self.diskCache = QNetworkDiskCache(self)
        self.diskCache.setCacheDirectory(self.settings_path)
        # Required for copy and paste clipboard integration
        QWebSettings.globalSettings().setAttribute(QWebSettings.JavascriptCanAccessClipboard, True)
        # Enabling Inspeclet only when --debug=True (requires more CPU usage)
        QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.debug)

    def toggleFullScreen(self):
        if self.isFullScreen():
            self.showMaximized()
        else:
            self.showFullScreen()

    def toggleMenuBar(self):
        menu = self.menuBar()
        state = menu.isHidden()
        menu.setVisible(state)
        if state:
            self.settings.setValue("Menu", "False")
        else:
            self.settings.setValue("Menu", "True")

    def restore(self):
        geometry = self.settings.value("geometry")
        if geometry is not None:
            self.restoreGeometry(geometry)
        windowState = self.settings.value("windowState")
        if windowState is not None:
            self.restoreState(windowState)
        else:
            self.setWindowState(QtCore.Qt.WindowMaximized)

    def systray(self, show=None):
        if show is None:
            show = self.settings.value("Systray") == "True"
        if show:
            self.tray.show()
            self.menus["file"]["close"].setEnabled(True)
            self.settings.setValue("Systray", "True")
        else:
            self.tray.setVisible(False)
            self.menus["file"]["close"].setEnabled(False)
            self.settings.setValue("Systray", "False")

    def readZoom(self):
        default = 1
        if self.settings.value("Zoom") is not None:
            default = float(self.settings.value("Zoom"))
        return default

    def setZoom(self, factor=1):
        if factor > 0:
            for i in range(0, self.stackedWidget.count()):
                widget = self.stackedWidget.widget(i)
                widget.setZoomFactor(factor)
            self.settings.setValue("Zoom", factor)

    def zoomIn(self):
        self.setZoom(self.current().zoomFactor() + 0.1)

    def zoomOut(self):
        self.setZoom(self.current().zoomFactor() - 0.1)

    def zoomReset(self):
        self.setZoom()

    def addMenu(self):
        # We'll register the webpage shorcuts with the window too (Fixes #338)
        undo = self.current().pageAction(QtWebKit.QWebPage.Undo)
        redo = self.current().pageAction(QtWebKit.QWebPage.Redo)
        cut = self.current().pageAction(QtWebKit.QWebPage.Cut)
        copy = self.current().pageAction(QtWebKit.QWebPage.Copy)
        paste = self.current().pageAction(QtWebKit.QWebPage.Paste)
        back = self.current().pageAction(QtWebKit.QWebPage.Back)
        forward = self.current().pageAction(QtWebKit.QWebPage.Forward)
        reload = self.current().pageAction(QtWebKit.QWebPage.Reload)
        self.menus = {
            "file": {
                "preferences": self.createAction("Preferences", lambda : self.current().preferences()),
                "systray":     self.createAction("Close to Tray", self.systray, None, True),
                "addTeam":     self.createAction("Sign in to Another Team", lambda : self.switchTo(Resources.SIGNIN_URL)),
                "signout":     self.createAction("Signout", lambda : self.current().logout()),
                "close":       self.createAction("Close", self.close, QKeySequence.Close),
                "exit":        self.createAction("Quit", self.exit, QKeySequence.Quit)
            },
            "edit": {
                "undo":        self.createAction(undo.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Undo), undo.shortcut()),
                "redo":        self.createAction(redo.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Redo), redo.shortcut()),
                "cut":         self.createAction(cut.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Cut), cut.shortcut()),
                "copy":        self.createAction(copy.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Copy), copy.shortcut()),
                "paste":       self.createAction(paste.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Paste), paste.shortcut()),
                "back":        self.createAction(back.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Back), back.shortcut()),
                "forward":     self.createAction(forward.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Forward), forward.shortcut()),
                "reload":      self.createAction(reload.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Reload), reload.shortcut()),
            },
            "view": {
                "zoomin":      self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn),
                "zoomout":     self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut),
                "reset":       self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0),
                "fullscreen":  self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11),
                "hidemenu":    self.createAction("Toggle Menubar", self.toggleMenuBar, QtCore.Qt.Key_F12)
            },
            "help": {
                "help":       self.createAction("Help and Feedback", lambda : self.current().help(), QKeySequence.HelpContents),
                "center":     self.createAction("Slack Help Center", lambda : self.current().helpCenter()),
                "about":      self.createAction("About", lambda : self.current().about())
             }
        }
        menu = self.menuBar()
        fileMenu = menu.addMenu("&File")
        fileMenu.addAction(self.menus["file"]["preferences"])
        fileMenu.addAction(self.menus["file"]["systray"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["addTeam"])
        fileMenu.addAction(self.menus["file"]["signout"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["close"])
        fileMenu.addAction(self.menus["file"]["exit"])
        editMenu = menu.addMenu("&Edit")
        editMenu.addAction(self.menus["edit"]["undo"])
        editMenu.addAction(self.menus["edit"]["redo"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["cut"])
        editMenu.addAction(self.menus["edit"]["copy"])
        editMenu.addAction(self.menus["edit"]["paste"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["back"])
        editMenu.addAction(self.menus["edit"]["forward"])
        editMenu.addAction(self.menus["edit"]["reload"])
        viewMenu = menu.addMenu("&View")
        viewMenu.addAction(self.menus["view"]["zoomin"])
        viewMenu.addAction(self.menus["view"]["zoomout"])
        viewMenu.addAction(self.menus["view"]["reset"])
        viewMenu.addSeparator()
        viewMenu.addAction(self.menus["view"]["fullscreen"])
        viewMenu.addAction(self.menus["view"]["hidemenu"])
        helpMenu = menu.addMenu("&Help")
        helpMenu.addAction(self.menus["help"]["help"])
        helpMenu.addAction(self.menus["help"]["center"])
        helpMenu.addSeparator()
        helpMenu.addAction(self.menus["help"]["about"])
        self.enableMenus(False)
        showSystray = self.settings.value("Systray") == "True"
        self.menus["file"]["systray"].setChecked(showSystray)
        self.menus["file"]["close"].setEnabled(showSystray)
        # Restore menu visibility
        visible = self.settings.value("Menu")
        if visible is not None and visible == "False":
            menu.setVisible(False)

    def enableMenus(self, enabled):
        self.menus["file"]["preferences"].setEnabled(enabled == True)
        self.menus["file"]["addTeam"].setEnabled(enabled == True)
        self.menus["file"]["signout"].setEnabled(enabled == True)
        self.menus["help"]["help"].setEnabled(enabled == True)

    def createAction(self, text, slot, shortcut=None, checkable=False):
        action = QtGui.QAction(text, self)
        action.triggered.connect(slot)
        if shortcut is not None:
            action.setShortcut(shortcut)
            self.addAction(action)
        if checkable:
            action.setCheckable(True)
        return action

    def normalize(self, url):
        if url.endswith(".slack.com"):
            url+= "/"
        elif not url.endswith(".slack.com/"):
            url = "https://"+url+".slack.com/"
        return url

    def current(self):
        return self.stackedWidget.currentWidget()

    def teams(self, teams):
        if len(self.domains) == 0:
            self.domains.append(teams[0]['team_url'])
        team_list = [t['team_url'] for t in teams]
        for t in teams:
            for i in range(0, len(self.domains)):
                self.domains[i] = self.normalize(self.domains[i])
                # When team_icon is missing, the team already exists (Fixes #381, #391)
                if 'team_icon' in t:
                    if self.domains[i] in team_list:
                        add = next(item for item in teams if item['team_url'] == self.domains[i])
                        if 'team_icon' in add:
                            self.leftPane.addTeam(add['id'], add['team_name'], add['team_url'], add['team_icon']['image_44'], add == teams[0])
                            # Adding new teams and saving loading positions
                            if t['team_url'] not in self.domains:
                                self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], t['team_icon']['image_44'], t == teams[0])
                                self.domains.append(t['team_url'])
                                self.settings.setValue("Domain", self.domains)
        if len(teams) > 1:
            self.leftPane.show()

    def switchTo(self, url):
        exists = False
        for i in range(0, self.stackedWidget.count()):
            if self.stackedWidget.widget(i).url().toString().startswith(url):
                self.stackedWidget.setCurrentIndex(i)
                self.quicklist(self.current().listChannels())
                self.current().setFocus()
                self.leftPane.click(i)
                exists = True
                break
        if not exists:
            self.addWrapper(url)

    def eventFilter(self, obj, event):
        if event.type() == QtCore.QEvent.ActivationChange and self.isActiveWindow():
            self.focusInEvent(event)
        if event.type() == QtCore.QEvent.KeyPress:
            # Ctrl + <n>
            modifiers = QtGui.QApplication.keyboardModifiers()
            if modifiers == QtCore.Qt.ControlModifier:
                if event.key() == QtCore.Qt.Key_1:   self.leftPane.click(0)
                elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1)
                elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2)
                elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3)
                elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4)
                elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5)
                elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6)
                elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7)
                elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8)
                # Ctrl + Tab
                elif event.key() == QtCore.Qt.Key_Tab: self.leftPane.clickNext(1)
            # Ctrl + BackTab
            if (modifiers & QtCore.Qt.ControlModifier) and (modifiers & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_Backtab: self.leftPane.clickNext(-1)
            # Ctrl + Shift + <key>
            if (modifiers & QtCore.Qt.ShiftModifier) and (modifiers & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_V: self.current().createSnippet()
        return QtGui.QMainWindow.eventFilter(self, obj, event);

    def focusInEvent(self, event):
        self.launcher.set_property("urgent", False)
        self.tray.stopAlert()
        # Let's tickle all teams on window focus, but only if tickle was not fired in last 30 minutes
        if DBusQtMainLoop is None and not self.tickler.isActive():
            self.sendTickle()
            self.tickler.start()

    def titleChanged(self):
        self.setWindowTitle(self.current().title())

    def setForceClose(self):
        self.forceClose = True

    def closeEvent(self, event):
        if not self.forceClose and self.settings.value("Systray") == "True":
            self.hide()
            event.ignore()
        else:
            self.cookiesjar.save()
            self.settings.setValue("geometry", self.saveGeometry())
            self.settings.setValue("windowState", self.saveState())
        self.forceClose = False

    def show(self):
        self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
        self.activateWindow()
        self.setVisible(True)

    def exit(self):
        self.setForceClose()
        self.close()

    def quicklist(self, channels):
        if Dbusmenu is not None:
            if channels is not None:
                ql = Dbusmenu.Menuitem.new()
                self.launcher.set_property("quicklist", ql)
                for c in channels:
                    if c['is_member']:
                        item = Dbusmenu.Menuitem.new ()
                        item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, "#"+c['name'])
                        item.property_set ("id", c['name'])
                        item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True)
                        item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel)
                        ql.child_append(item)
                self.launcher.set_property("quicklist", ql)

    def notify(self, title, message, icon):
        if self.debug: print("Notification: title [{}] message [{}] icon [{}]".format(title, message, icon))
        self.notifier.notify(title, message, icon)
        self.alert()

    def alert(self):
        if not self.isActiveWindow():
            self.launcher.set_property("urgent", True)
            self.tray.alert()

    def count(self):
        total = 0
        unreads = 0
        for i in range(0, self.stackedWidget.count()):
            widget = self.stackedWidget.widget(i)
            highlights = widget.highlights
            unreads+= widget.unreads
            total+=highlights
        if total > self.messages:
            self.alert()
        if 0 == total:
            self.launcher.set_property("count_visible", False)
            self.tray.setCounter(0)
            if unreads > 0:
                self.setWindowTitle("*{}".format(self.title))
            else:
                self.setWindowTitle(self.title)
        else:
            self.tray.setCounter(total)
            self.launcher.set_property("count", total)
            self.launcher.set_property("count_visible", True)
            self.setWindowTitle("[{}]{}".format(str(total), self.title))
        self.messages = total
示例#41
0
 def setupTimer(self):
     timer = QTimer(self)
     timer.timeout.connect(self.loadFinished)
     # Hope each 10 minutes will not be produce high CPU usage
     timer.setInterval(600000)
     timer.start()
示例#42
0
 def setupTimer(self):
     timer = QTimer(self)
     timer.timeout.connect(self.loadFinished)
     # Hope each 10 minutes will not be produce high CPU usage
     timer.setInterval(600000)
     timer.start()
示例#43
0
class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
        TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin,
        SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin,
        EbookDownloadMixin
        ):
    'The main GUI'

    proceed_requested = pyqtSignal(object, object)

    def __init__(self, opts, parent=None, gui_debug=None):
        global _gui
        MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True)
        self.jobs_pointer = Pointer(self)
        self.proceed_requested.connect(self.do_proceed,
                type=Qt.QueuedConnection)
        self.proceed_question = ProceedQuestion(self)
        self.job_error_dialog = JobError(self)
        self.keyboard = Manager(self)
        _gui = self
        self.opts = opts
        self.device_connected = None
        self.gui_debug = gui_debug
        self.iactions = OrderedDict()
        # Actions
        for action in interface_actions():
            if opts.ignore_plugins and action.plugin_path is not None:
                continue
            try:
                ac = self.init_iaction(action)
            except:
                # Ignore errors in loading user supplied plugins
                import traceback
                traceback.print_exc()
                if action.plugin_path is None:
                    raise
                continue
            ac.plugin_path = action.plugin_path
            ac.interface_action_base_plugin = action
            self.add_iaction(ac)
        self.load_store_plugins()

    def init_iaction(self, action):
        ac = action.load_actual_plugin(self)
        ac.plugin_path = action.plugin_path
        ac.interface_action_base_plugin = action
        action.actual_iaction_plugin_loaded = True
        return ac

    def add_iaction(self, ac):
        acmap = self.iactions
        if ac.name in acmap:
            if ac.priority >= acmap[ac.name].priority:
                acmap[ac.name] = ac
        else:
            acmap[ac.name] = ac

    def load_store_plugins(self):
        from calibre.gui2.store.loader import Stores
        self.istores = Stores()
        for store in available_store_plugins():
            if self.opts.ignore_plugins and store.plugin_path is not None:
                continue
            try:
                st = self.init_istore(store)
                self.add_istore(st)
            except:
                # Ignore errors in loading user supplied plugins
                import traceback
                traceback.print_exc()
                if store.plugin_path is None:
                    raise
                continue
        self.istores.builtins_loaded()

    def init_istore(self, store):
        st = store.load_actual_plugin(self)
        st.plugin_path = store.plugin_path
        st.base_plugin = store
        store.actual_istore_plugin_loaded = True
        return st

    def add_istore(self, st):
        stmap = self.istores
        if st.name in stmap:
            if st.priority >= stmap[st.name].priority:
                stmap[st.name] = st
        else:
            stmap[st.name] = st


    def initialize(self, library_path, db, listener, actions, show_gui=True):
        opts = self.opts
        self.preferences_action, self.quit_action = actions
        self.library_path = library_path
        self.content_server = None
        self.spare_servers = []
        self.must_restart_before_config = False
        self.listener = Listener(listener)
        self.check_messages_timer = QTimer()
        self.connect(self.check_messages_timer, SIGNAL('timeout()'),
                self.another_instance_wants_to_talk)
        self.check_messages_timer.start(1000)

        for ac in self.iactions.values():
            ac.do_genesis()
        self.donate_action = QAction(QIcon(I('donate.png')),
                _('&Donate to support calibre'), self)
        for st in self.istores.values():
            st.do_genesis()
        MainWindowMixin.__init__(self, db)

        # Jobs Button {{{
        self.job_manager = JobManager()
        self.jobs_dialog = JobsDialog(self, self.job_manager)
        self.jobs_button = JobsButton(horizontal=True, parent=self)
        self.jobs_button.initialize(self.jobs_dialog, self.job_manager)
        # }}}

        LayoutMixin.__init__(self)
        EmailMixin.__init__(self)
        EbookDownloadMixin.__init__(self)
        DeviceMixin.__init__(self)

        self.progress_indicator = ProgressIndicator(self)
        self.progress_indicator.pos = (0, 20)
        self.verbose = opts.verbose
        self.get_metadata = GetMetadata()
        self.upload_memory = {}
        self.metadata_dialogs = []
        self.default_thumbnail = None
        self.tb_wrapper = textwrap.TextWrapper(width=40)
        self.viewers = collections.deque()
        self.system_tray_icon = SystemTrayIcon(QIcon(I('lt.png')), self)
        self.system_tray_icon.setToolTip('calibre')
        self.system_tray_icon.tooltip_requested.connect(
                self.job_manager.show_tooltip)
        if not config['systray_icon']:
            self.system_tray_icon.hide()
        else:
            self.system_tray_icon.show()
        self.system_tray_menu = QMenu(self)
        self.restore_action = self.system_tray_menu.addAction(
                QIcon(I('page.png')), _('&Restore'))
        self.system_tray_menu.addAction(self.donate_action)
        self.donate_button.setDefaultAction(self.donate_action)
        self.donate_button.setStatusTip(self.donate_button.toolTip())
        self.eject_action = self.system_tray_menu.addAction(
                QIcon(I('eject.png')), _('&Eject connected device'))
        self.eject_action.setEnabled(False)
        self.addAction(self.quit_action)
        self.system_tray_menu.addAction(self.quit_action)
        self.keyboard.register_shortcut('quit calibre', _('Quit calibre'),
                default_keys=('Ctrl+Q',), action=self.quit_action)
        self.system_tray_icon.setContextMenu(self.system_tray_menu)
        self.connect(self.quit_action, SIGNAL('triggered(bool)'), self.quit)
        self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate)
        self.connect(self.restore_action, SIGNAL('triggered()'),
                        self.show_windows)
        self.system_tray_icon.activated.connect(
            self.system_tray_icon_activated)

        self.esc_action = QAction(self)
        self.addAction(self.esc_action)
        self.keyboard.register_shortcut('clear current search',
                _('Clear the current search'), default_keys=('Esc',),
                action=self.esc_action)
        self.esc_action.triggered.connect(self.esc)

        ####################### Start spare job server ########################
        QTimer.singleShot(1000, self.add_spare_server)

        ####################### Location Manager ########################
        self.location_manager.location_selected.connect(self.location_selected)
        self.location_manager.unmount_device.connect(self.device_manager.umount_device)
        self.location_manager.configure_device.connect(self.configure_connected_device)
        self.eject_action.triggered.connect(self.device_manager.umount_device)

        #################### Update notification ###################
        UpdateMixin.__init__(self, opts)

        ####################### Search boxes ########################
        SavedSearchBoxMixin.__init__(self)
        SearchBoxMixin.__init__(self)

        ####################### Library view ########################
        LibraryViewMixin.__init__(self, db)

        if show_gui:
            self.show()

        if self.system_tray_icon.isVisible() and opts.start_in_tray:
            self.hide_windows()
        self.library_view.model().count_changed_signal.connect(
                self.iactions['Choose Library'].count_changed)
        if not gprefs.get('quick_start_guide_added', False):
            from calibre.ebooks.metadata.meta import get_metadata
            mi = get_metadata(open(P('quick_start.epub'), 'rb'), 'epub')
            self.library_view.model().add_books([P('quick_start.epub')], ['epub'],
                    [mi])
            gprefs['quick_start_guide_added'] = True
            self.library_view.model().books_added(1)
            if hasattr(self, 'db_images'):
                self.db_images.reset()
            if self.library_view.model().rowCount(None) < 3:
                self.library_view.resizeColumnsToContents()

        self.library_view.model().count_changed()
        self.bars_manager.database_changed(self.library_view.model().db)
        self.library_view.model().database_changed.connect(self.bars_manager.database_changed,
                type=Qt.QueuedConnection)

        ########################### Tags Browser ##############################
        TagBrowserMixin.__init__(self, db)

        ######################### Search Restriction ##########################
        SearchRestrictionMixin.__init__(self)
        if db.prefs['gui_restriction']:
            self.apply_named_search_restriction(db.prefs['gui_restriction'])

        ########################### Cover Flow ################################

        CoverFlowMixin.__init__(self)

        self._calculated_available_height = min(max_available_height()-15,
                self.height())
        self.resize(self.width(), self._calculated_available_height)

        self.build_context_menus()

        for ac in self.iactions.values():
            try:
                ac.gui_layout_complete()
            except:
                import traceback
                traceback.print_exc()
                if ac.plugin_path is None:
                    raise

        if config['autolaunch_server']:
            self.start_content_server()


        self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)

        self.read_settings()
        self.finalize_layout()
        if self.bars_manager.showing_donate:
            self.donate_button.start_animation()
        self.set_window_title()

        for ac in self.iactions.values():
            try:
                ac.initialization_complete()
            except:
                import traceback
                traceback.print_exc()
                if ac.plugin_path is None:
                    raise
        self.device_manager.set_current_library_uuid(db.library_id)

        self.keyboard.finalize()
        self.auto_adder = AutoAdder(gprefs['auto_add_path'], self)

        self.save_layout_state()

        # Collect cycles now
        gc.collect()

        if show_gui and self.gui_debug is not None:
            info_dialog(self, _('Debug mode'), '<p>' +
                    _('You have started calibre in debug mode. After you '
                        'quit calibre, the debug log will be available in '
                        'the file: %s<p>The '
                        'log will be displayed automatically.')%self.gui_debug, show=True)

        self.iactions['Connect Share'].check_smartdevice_menus()
        QTimer.singleShot(1, self.start_smartdevice)

    def esc(self, *args):
        self.clear_button.click()

    def start_smartdevice(self):
        message = None
        if self.device_manager.get_option('smartdevice', 'autostart'):
            try:
                message = self.device_manager.start_plugin('smartdevice')
            except:
                message = 'start smartdevice unknown exception'
                prints(message)
                import traceback
                traceback.print_exc()
        if message:
            if not self.device_manager.is_running('Wireless Devices'):
                    error_dialog(self, _('Problem starting the wireless device'),
                                 _('The wireless device driver did not start. '
                                   'It said "%s"')%message,  show=True)
        self.iactions['Connect Share'].set_smartdevice_action_state()

    def start_content_server(self, check_started=True):
        from calibre.library.server.main import start_threaded_server
        from calibre.library.server import server_config
        self.content_server = start_threaded_server(
                self.library_view.model().db, server_config().parse())
        self.content_server.state_callback = Dispatcher(
                self.iactions['Connect Share'].content_server_state_changed)
        if check_started:
            self.content_server.start_failure_callback = \
                Dispatcher(self.content_server_start_failed)

    def content_server_start_failed(self, msg):
        error_dialog(self, _('Failed to start Content Server'),
                _('Could not start the content server. Error:\n\n%s')%msg,
                show=True)

    def resizeEvent(self, ev):
        MainWindow.resizeEvent(self, ev)
        self.search.setMaximumWidth(self.width()-150)

    def add_spare_server(self, *args):
        self.spare_servers.append(Server(limit=int(config['worker_limit']/2.0)))

    @property
    def spare_server(self):
        # Because of the use of the property decorator, we're called one
        # extra time. Ignore.
        if not hasattr(self, '__spare_server_property_limiter'):
            self.__spare_server_property_limiter = True
            return None
        try:
            QTimer.singleShot(1000, self.add_spare_server)
            return self.spare_servers.pop()
        except:
            pass

    def do_proceed(self, func, payload):
        if callable(func):
            func(payload)

    def no_op(self, *args):
        pass

    def system_tray_icon_activated(self, r):
        if r == QSystemTrayIcon.Trigger:
            if self.isVisible():
                self.hide_windows()
            else:
                self.show_windows()

    @property
    def is_minimized_to_tray(self):
        return getattr(self, '__systray_minimized', False)

    def ask_a_yes_no_question(self, title, msg, det_msg='',
            show_copy_button=False, ans_when_user_unavailable=True,
            skip_dialog_name=None, skipped_value=True):
        if self.is_minimized_to_tray:
            return ans_when_user_unavailable
        return question_dialog(self, title, msg, det_msg=det_msg,
                show_copy_button=show_copy_button,
                skip_dialog_name=skip_dialog_name,
                skip_dialog_skipped_value=skipped_value)

    def hide_windows(self):
        for window in QApplication.topLevelWidgets():
            if isinstance(window, (MainWindow, QDialog)) and \
                    window.isVisible():
                window.hide()
                setattr(window, '__systray_minimized', True)

    def show_windows(self):
        for window in QApplication.topLevelWidgets():
            if getattr(window, '__systray_minimized', False):
                window.show()
                setattr(window, '__systray_minimized', False)

    def test_server(self, *args):
        if self.content_server is not None and \
                self.content_server.exception is not None:
            error_dialog(self, _('Failed to start content server'),
                         unicode(self.content_server.exception)).exec_()

    @property
    def current_db(self):
        return self.library_view.model().db

    def another_instance_wants_to_talk(self):
        try:
            msg = self.listener.queue.get_nowait()
        except Empty:
            return
        if msg.startswith('launched:'):
            argv = eval(msg[len('launched:'):])
            if len(argv) > 1:
                path = os.path.abspath(argv[1])
                if os.access(path, os.R_OK):
                    self.iactions['Add Books'].add_filesystem_book(path)
            self.setWindowState(self.windowState() & \
                    ~Qt.WindowMinimized|Qt.WindowActive)
            self.show_windows()
            self.raise_()
            self.activateWindow()
        elif msg.startswith('refreshdb:'):
            self.library_view.model().refresh()
            self.library_view.model().research()
            self.tags_view.recount()
            self.library_view.model().db.refresh_format_cache()
        elif msg.startswith('shutdown:'):
            self.quit(confirm_quit=False)
        else:
            print msg

    def current_view(self):
        '''Convenience method that returns the currently visible view '''
        idx = self.stack.currentIndex()
        if idx == 0:
            return self.library_view
        if idx == 1:
            return self.memory_view
        if idx == 2:
            return self.card_a_view
        if idx == 3:
            return self.card_b_view

    def booklists(self):
        return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db

    def library_moved(self, newloc, copy_structure=False, call_close=True,
            allow_rebuild=False):
        if newloc is None: return
        default_prefs = None
        try:
            olddb = self.library_view.model().db
            if copy_structure:
                default_prefs = olddb.prefs
        except:
            olddb = None
        try:
            db = LibraryDatabase2(newloc, default_prefs=default_prefs)
        except (DatabaseException, sqlite.Error):
            if not allow_rebuild: raise
            import traceback
            repair = question_dialog(self, _('Corrupted database'),
                    _('The library database at %s appears to be corrupted. Do '
                    'you want calibre to try and rebuild it automatically? '
                    'The rebuild may not be completely successful.')
                    % force_unicode(newloc, filesystem_encoding),
                    det_msg=traceback.format_exc()
                    )
            if repair:
                from calibre.gui2.dialogs.restore_library import repair_library_at
                if repair_library_at(newloc, parent=self):
                    db = LibraryDatabase2(newloc, default_prefs=default_prefs)
                else:
                    return
            else:
                return
        if self.content_server is not None:
            self.content_server.set_database(db)
        self.library_path = newloc
        prefs['library_path'] = self.library_path
        self.book_on_device(None, reset=True)
        db.set_book_on_device_func(self.book_on_device)
        self.library_view.set_database(db)
        self.tags_view.set_database(db, self.alter_tb)
        self.library_view.model().set_book_on_device_func(self.book_on_device)
        self.status_bar.clear_message()
        self.search.clear()
        self.saved_search.clear()
        self.book_details.reset_info()
        #self.library_view.model().count_changed()
        db = self.library_view.model().db
        self.iactions['Choose Library'].count_changed(db.count())
        self.set_window_title()
        self.apply_named_search_restriction('') # reset restriction to null
        self.saved_searches_changed(recount=False) # reload the search restrictions combo box
        self.apply_named_search_restriction(db.prefs['gui_restriction'])
        for action in self.iactions.values():
            action.library_changed(db)
        if olddb is not None:
            try:
                if call_close:
                    olddb.conn.close()
            except:
                import traceback
                traceback.print_exc()
            olddb.break_cycles()
        if self.device_connected:
            self.set_books_in_library(self.booklists(), reset=True)
            self.refresh_ondevice()
            self.memory_view.reset()
            self.card_a_view.reset()
            self.card_b_view.reset()
        self.device_manager.set_current_library_uuid(db.library_id)
        self.library_view.set_current_row(0)
        # Run a garbage collection now so that it does not freeze the
        # interface later
        gc.collect()


    def set_window_title(self):
        self.setWindowTitle(__appname__ + u' - || %s ||'%self.iactions['Choose Library'].library_name())

    def location_selected(self, location):
        '''
        Called when a location icon is clicked (e.g. Library)
        '''
        page = 0 if location == 'library' else 1 if location == 'main' else 2 if location == 'carda' else 3
        self.stack.setCurrentIndex(page)
        self.book_details.reset_info()
        for x in ('tb', 'cb'):
            splitter = getattr(self, x+'_splitter')
            splitter.button.setEnabled(location == 'library')
        for action in self.iactions.values():
            action.location_selected(location)
        if location == 'library':
            self.search_restriction.setEnabled(True)
            self.highlight_only_button.setEnabled(True)
        else:
            self.search_restriction.setEnabled(False)
            self.highlight_only_button.setEnabled(False)
            # Reset the view in case something changed while it was invisible
            self.current_view().reset()
        self.set_number_of_books_shown()



    def job_exception(self, job, dialog_title=_('Conversion Error')):
        if not hasattr(self, '_modeless_dialogs'):
            self._modeless_dialogs = []
        minz = self.is_minimized_to_tray
        if self.isVisible():
            for x in list(self._modeless_dialogs):
                if not x.isVisible():
                    self._modeless_dialogs.remove(x)
        try:
            if 'calibre.ebooks.DRMError' in job.details:
                if not minz:
                    from calibre.gui2.dialogs.drm_error import DRMErrorMessage
                    d = DRMErrorMessage(self, _('Cannot convert') + ' ' +
                        job.description.split(':')[-1].partition('(')[-1][:-1])
                    d.setModal(False)
                    d.show()
                    self._modeless_dialogs.append(d)
                return

            if 'calibre.ebooks.oeb.transforms.split.SplitError' in job.details:
                title = job.description.split(':')[-1].partition('(')[-1][:-1]
                msg = _('<p><b>Failed to convert: %s')%title
                msg += '<p>'+_('''
                Many older ebook reader devices are incapable of displaying
                EPUB files that have internal components over a certain size.
                Therefore, when converting to EPUB, calibre automatically tries
                to split up the EPUB into smaller sized pieces.  For some
                files that are large undifferentiated blocks of text, this
                splitting fails.
                <p>You can <b>work around the problem</b> by either increasing the
                maximum split size under EPUB Output in the conversion dialog,
                or by turning on Heuristic Processing, also in the conversion
                dialog. Note that if you make the maximum split size too large,
                your ebook reader may have trouble with the EPUB.
                        ''')
                if not minz:
                    d = error_dialog(self, _('Conversion Failed'), msg,
                            det_msg=job.details)
                    d.setModal(False)
                    d.show()
                    self._modeless_dialogs.append(d)
                return

            if 'calibre.web.feeds.input.RecipeDisabled' in job.details:
                if not minz:
                    msg = job.details
                    msg = msg[msg.find('calibre.web.feeds.input.RecipeDisabled:'):]
                    msg = msg.partition(':')[-1]
                    d = error_dialog(self, _('Recipe Disabled'),
                        '<p>%s</p>'%msg)
                    d.setModal(False)
                    d.show()
                    self._modeless_dialogs.append(d)
                return

            if 'calibre.ebooks.conversion.ConversionUserFeedBack:' in job.details:
                if not minz:
                    import json
                    payload = job.details.rpartition(
                        'calibre.ebooks.conversion.ConversionUserFeedBack:')[-1]
                    payload = json.loads('{' + payload.partition('{')[-1])
                    d = {'info':info_dialog, 'warn':warning_dialog,
                            'error':error_dialog}.get(payload['level'],
                                    error_dialog)
                    d = d(self, payload['title'],
                            '<p>%s</p>'%payload['msg'],
                            det_msg=payload['det_msg'])
                    d.setModal(False)
                    d.show()
                    self._modeless_dialogs.append(d)
                return
        except:
            pass
        if job.killed:
            return
        try:
            prints(job.details, file=sys.stderr)
        except:
            pass
        if not minz:
            self.job_error_dialog.show_error(dialog_title,
                    _('<b>Failed</b>')+': '+unicode(job.description),
                    det_msg=job.details)

    def read_settings(self):
        geometry = config['main_window_geometry']
        if geometry is not None:
            self.restoreGeometry(geometry)
        self.read_layout_settings()

    def write_settings(self):
        with gprefs: # Only write to gprefs once
            config.set('main_window_geometry', self.saveGeometry())
            dynamic.set('sort_history', self.library_view.model().sort_history)
            self.save_layout_state()

    def quit(self, checked=True, restart=False, debug_on_restart=False,
            confirm_quit=True):
        if confirm_quit and not self.confirm_quit():
            return
        try:
            self.shutdown()
        except:
            pass
        self.restart_after_quit = restart
        self.debug_on_restart = debug_on_restart
        QApplication.instance().quit()

    def donate(self, *args):
        open_url(QUrl('http://calibre-ebook.com/donate'))

    def confirm_quit(self):
        if self.job_manager.has_jobs():
            msg = _('There are active jobs. Are you sure you want to quit?')
            if self.job_manager.has_device_jobs():
                msg = '<p>'+__appname__ + \
                      _(''' is communicating with the device!<br>
                      Quitting may cause corruption on the device.<br>
                      Are you sure you want to quit?''')+'</p>'

            if not question_dialog(self, _('Active jobs'), msg):
                return False
        return True


    def shutdown(self, write_settings=True):
        try:
            db = self.library_view.model().db
            cf = db.clean
        except:
            pass
        else:
            cf()
            # Save the current field_metadata for applications like calibre2opds
            # Goes here, because if cf is valid, db is valid.
            db.prefs['field_metadata'] = db.field_metadata.all_metadata()
            db.commit_dirty_cache()
            db.prefs.write_serialized(prefs['library_path'])
        for action in self.iactions.values():
            if not action.shutting_down():
                return
        if write_settings:
            self.write_settings()
        self.check_messages_timer.stop()
        self.update_checker.terminate()
        self.listener.close()
        self.job_manager.server.close()
        self.job_manager.threaded_server.close()
        while self.spare_servers:
            self.spare_servers.pop().close()
        self.device_manager.keep_going = False
        self.auto_adder.stop()
        mb = self.library_view.model().metadata_backup
        if mb is not None:
            mb.stop()

        self.hide_windows()
        try:
            try:
                if self.content_server is not None:
                    s = self.content_server
                    self.content_server = None
                    s.exit()
            except:
                pass
        except KeyboardInterrupt:
            pass
        time.sleep(2)
        self.istores.join()
        self.hide_windows()
        # Do not report any errors that happen after the shutdown
        sys.excepthook = sys.__excepthook__
        return True

    def run_wizard(self, *args):
        if self.confirm_quit():
            self.run_wizard_b4_shutdown = True
            self.restart_after_quit = True
            try:
                self.shutdown(write_settings=False)
            except:
                pass
            QApplication.instance().quit()



    def closeEvent(self, e):
        self.write_settings()
        if self.system_tray_icon.isVisible():
            if not dynamic['systray_msg'] and not isosx:
                info_dialog(self, 'calibre', 'calibre '+ \
                        _('will keep running in the system tray. To close it, '
                        'choose <b>Quit</b> in the context menu of the '
                        'system tray.'), show_copy_button=False).exec_()
                dynamic['systray_msg'] = True
            self.hide_windows()
            e.ignore()
        else:
            if self.confirm_quit():
                try:
                    self.shutdown(write_settings=False)
                except:
                    pass
                e.accept()
            else:
                e.ignore()
示例#44
0
        ##Wed Dec 31 16:00:08 PST 1969
        ##exitcode was 0

        #self._test("date", ["-rrr", "8"] )
        ##date: illegal time format
        ##usage: date [-nu] [-r seconds] [+format]
        ##       date [[[[[cc]yy]mm]dd]hh]mm[.ss]
        ##exitcode was 1

        #self._test("ls", ["-f"], "/tmp" )
        ##501
        ##mcx_compositor
        ##printers
        ##exitcode was 0
        global app
        app.quit()


if __name__ == '__main__':
    from PyQt4.Qt import QApplication, QTimer
    import sys
    test = ProcessTest()
    app = QApplication(sys.argv, False)
    timer = QTimer()
    timer.connect(timer, SIGNAL("timeout()"), test._all_tests)
    timer.setSingleShot(True)
    timer.start(10)
    app.exec_()

# end
示例#45
0
class TagBrowserWidget(QWidget):  # {{{

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.parent = parent
        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self._layout.setContentsMargins(0,0,0,0)

        # Set up the find box & button
        search_layout = QHBoxLayout()
        self._layout.addLayout(search_layout)
        self.item_search = HistoryLineEdit(parent)
        self.item_search.setMinimumContentsLength(5)
        self.item_search.setSizeAdjustPolicy(self.item_search.AdjustToMinimumContentsLengthWithIcon)
        try:
            self.item_search.lineEdit().setPlaceholderText(
                                                _('Find item in tag browser'))
        except:
            pass             # Using Qt < 4.7
        self.item_search.setToolTip(_(
        'Search for items. This is a "contains" search; items containing the\n'
        'text anywhere in the name will be found. You can limit the search\n'
        'to particular categories using syntax similar to search. For example,\n'
        'tags:foo will find foo in any tag, but not in authors etc. Entering\n'
        '*foo will filter all categories at once, showing only those items\n'
        'containing the text "foo"'))
        search_layout.addWidget(self.item_search)
        # Not sure if the shortcut should be translatable ...
        sc = QShortcut(QKeySequence(_('ALT+f')), parent)
        sc.activated.connect(self.set_focus_to_find_box)

        self.search_button = QToolButton()
        self.search_button.setText(_('F&ind'))
        self.search_button.setToolTip(_('Find the first/next matching item'))
        search_layout.addWidget(self.search_button)

        self.expand_button = QToolButton()
        self.expand_button.setText('-')
        self.expand_button.setToolTip(_('Collapse all categories'))
        search_layout.addWidget(self.expand_button)
        search_layout.setStretch(0, 10)
        search_layout.setStretch(1, 1)
        search_layout.setStretch(2, 1)

        self.current_find_position = None
        self.search_button.clicked.connect(self.find)
        self.item_search.initialize('tag_browser_search')
        self.item_search.lineEdit().returnPressed.connect(self.do_find)
        self.item_search.lineEdit().textEdited.connect(self.find_text_changed)
        self.item_search.activated[QString].connect(self.do_find)
        self.item_search.completer().setCaseSensitivity(Qt.CaseSensitive)

        parent.tags_view = TagsView(parent)
        self.tags_view = parent.tags_view
        self.expand_button.clicked.connect(self.tags_view.collapseAll)
        self._layout.addWidget(parent.tags_view)

        # Now the floating 'not found' box
        l = QLabel(self.tags_view)
        self.not_found_label = l
        l.setFrameStyle(QFrame.StyledPanel)
        l.setAutoFillBackground(True)
        l.setText('<p><b>'+_('No More Matches.</b><p> Click Find again to go to first match'))
        l.setAlignment(Qt.AlignVCenter)
        l.setWordWrap(True)
        l.resize(l.sizeHint())
        l.move(10,20)
        l.setVisible(False)
        self.not_found_label_timer = QTimer()
        self.not_found_label_timer.setSingleShot(True)
        self.not_found_label_timer.timeout.connect(self.not_found_label_timer_event,
                                                   type=Qt.QueuedConnection)

        parent.alter_tb = l = QPushButton(parent)
        l.setText(_('Alter Tag Browser'))
        l.setIcon(QIcon(I('tags.png')))
        l.m = QMenu()
        l.setMenu(l.m)
        self._layout.addWidget(l)

        sb = l.m.addAction(_('Sort by'))
        sb.m = l.sort_menu = QMenu(l.m)
        sb.setMenu(sb.m)
        sb.bg = QActionGroup(sb)

        # Must be in the same order as db2.CATEGORY_SORTS
        for i, x in enumerate((_('Sort by name'), _('Sort by number of books'),
                  _('Sort by average rating'))):
            a = sb.m.addAction(x)
            sb.bg.addAction(a)
            a.setCheckable(True)
            if i == 0:
                a.setChecked(True)
        sb.setToolTip(
                _('Set the sort order for entries in the Tag Browser'))
        sb.setStatusTip(sb.toolTip())

        ma = l.m.addAction(_('Search type when selecting multiple items'))
        ma.m = l.match_menu = QMenu(l.m)
        ma.setMenu(ma.m)
        ma.ag = QActionGroup(ma)

        # Must be in the same order as db2.MATCH_TYPE
        for i, x in enumerate((_('Match any of the items'), _('Match all of the items'))):
            a = ma.m.addAction(x)
            ma.ag.addAction(a)
            a.setCheckable(True)
            if i == 0:
                a.setChecked(True)
        ma.setToolTip(
                _('When selecting multiple entries in the Tag Browser '
                    'match any or all of them'))
        ma.setStatusTip(ma.toolTip())

        mt = l.m.addAction(_('Manage authors, tags, etc'))
        mt.setToolTip(_('All of these category_managers are available by right-clicking '
                       'on items in the tag browser above'))
        mt.m = l.manage_menu = QMenu(l.m)
        mt.setMenu(mt.m)

        # self.leak_test_timer = QTimer(self)
        # self.leak_test_timer.timeout.connect(self.test_for_leak)
        # self.leak_test_timer.start(5000)

    def set_pane_is_visible(self, to_what):
        self.tags_view.set_pane_is_visible(to_what)

    def find_text_changed(self, str):
        self.current_find_position = None

    def set_focus_to_find_box(self):
        self.item_search.setFocus()
        self.item_search.lineEdit().selectAll()

    def do_find(self, str=None):
        self.current_find_position = None
        self.find()

    def find(self):
        model = self.tags_view.model()
        model.clear_boxed()
        txt = unicode(self.item_search.currentText()).strip()

        if txt.startswith('*'):
            model.set_categories_filter(txt[1:])
            self.tags_view.recount()
            self.current_find_position = None
            return
        if model.get_categories_filter():
            model.set_categories_filter(None)
            self.tags_view.recount()
            self.current_find_position = None

        if not txt:
            return

        self.item_search.lineEdit().blockSignals(True)
        self.search_button.setFocus(True)
        self.item_search.lineEdit().blockSignals(False)

        key = None
        colon = txt.rfind(':') if len(txt) > 2 else 0
        if colon > 0:
            key = self.parent.library_view.model().db.\
                        field_metadata.search_term_to_field_key(txt[:colon])
            txt = txt[colon+1:]

        self.current_find_position = \
            model.find_item_node(key, txt, self.current_find_position)

        if self.current_find_position:
            self.tags_view.show_item_at_path(self.current_find_position, box=True)
        elif self.item_search.text():
            self.not_found_label.setVisible(True)
            if self.tags_view.verticalScrollBar().isVisible():
                sbw = self.tags_view.verticalScrollBar().width()
            else:
                sbw = 0
            width = self.width() - 8 - sbw
            height = self.not_found_label.heightForWidth(width) + 20
            self.not_found_label.resize(width, height)
            self.not_found_label.move(4, 10)
            self.not_found_label_timer.start(2000)

    def not_found_label_timer_event(self):
        self.not_found_label.setVisible(False)
示例#46
0
 def setupTimer(self):
     timer = QTimer(self)
     timer.timeout.connect(self.overrideNotifications)
     # Hope each 10 minutes will not be produce high CPU usage
     timer.setInterval(600000)
     timer.start()
示例#47
0
class SearchBox2(QComboBox):  # {{{

    '''
    To use this class:

        * Call initialize()
        * Connect to the search() and cleared() signals from this widget.
        * Connect to the changed() signal to know when the box content changes
        * Connect to focus_to_library() signal to be told to manually change focus
        * Call search_done() after every search is complete
        * Call set_search_string() to perform a search programmatically
        * You can use the current_text property to get the current search text
          Be aware that if you are using it in a slot connected to the
          changed() signal, if the connection is not queued it will not be
          accurate.
    '''

    INTERVAL = 1500  #: Time to wait before emitting search signal
    MAX_COUNT = 25

    search  = pyqtSignal(object)
    cleared = pyqtSignal()
    changed = pyqtSignal()
    focus_to_library = pyqtSignal()

    def __init__(self, parent=None):
        QComboBox.__init__(self, parent)
        self.normal_background = 'rgb(255, 255, 255, 0%)'
        self.line_edit = SearchLineEdit(self)
        self.setLineEdit(self.line_edit)

        c = self.line_edit.completer()
        c.setCompletionMode(c.PopupCompletion)
        c.highlighted[QString].connect(self.completer_used)
        c.activated[QString].connect(self.history_selected)

        self.line_edit.key_pressed.connect(self.key_pressed, type=Qt.DirectConnection)
        self.activated.connect(self.history_selected)
        self.setEditable(True)
        self.as_you_type = True
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.timer_event, type=Qt.QueuedConnection)
        self.setInsertPolicy(self.NoInsert)
        self.setMaxCount(self.MAX_COUNT)
        self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
        self.setMinimumContentsLength(25)
        self._in_a_search = False
        self.tool_tip_text = self.toolTip()

    def initialize(self, opt_name, colorize=False, help_text=_('Search')):
        self.as_you_type = config['search_as_you_type']
        self.opt_name = opt_name
        items = []
        for item in config[opt_name]:
            if item not in items:
                items.append(item)
        self.addItems(QStringList(items))
        try:
            self.line_edit.setPlaceholderText(help_text)
        except:
            # Using Qt < 4.7
            pass
        self.colorize = colorize
        self.clear()

    def hide_completer_popup(self):
        try:
            self.lineEdit().completer().popup().setVisible(False)
        except:
            pass

    def normalize_state(self):
        self.setToolTip(self.tool_tip_text)
        self.line_edit.setStyleSheet(
            'QLineEdit{color:none;background-color:%s;}' % self.normal_background)

    def text(self):
        return self.currentText()

    def clear_history(self, *args):
        QComboBox.clear(self)

    def clear(self, emit_search=True):
        self.normalize_state()
        self.setEditText('')
        if emit_search:
            self.search.emit('')
        self._in_a_search = False
        self.cleared.emit()

    def clear_clicked(self, *args):
        self.clear()

    def search_done(self, ok):
        if isinstance(ok, basestring):
            self.setToolTip(ok)
            ok = False
        if not unicode(self.currentText()).strip():
            self.clear(emit_search=False)
            return
        self._in_a_search = ok
        col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
        if not self.colorize:
            col = self.normal_background
        self.line_edit.setStyleSheet('QLineEdit{color:black;background-color:%s;}' % col)

    # Comes from the lineEdit control
    def key_pressed(self, event):
        k = event.key()
        if k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down,
                Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown,
                Qt.Key_unknown):
            return
        self.normalize_state()
        if self._in_a_search:
            self.changed.emit()
            self._in_a_search = False
        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.do_search()
            self.focus_to_library.emit()
        elif self.as_you_type and unicode(event.text()):
            self.timer.start(1500)

    # Comes from the combobox itself
    def keyPressEvent(self, event):
        k = event.key()
        if k in (Qt.Key_Enter, Qt.Key_Return):
            return self.do_search()
        if k not in (Qt.Key_Up, Qt.Key_Down):
            QComboBox.keyPressEvent(self, event)
        else:
            self.blockSignals(True)
            self.normalize_state()
            QComboBox.keyPressEvent(self, event)
            self.blockSignals(False)

    def completer_used(self, text):
        self.timer.stop()
        self.normalize_state()

    def timer_event(self):
        self.do_search()

    def history_selected(self, text):
        self.changed.emit()
        self.do_search()

    def _do_search(self, store_in_history=True):
        self.hide_completer_popup()
        text = unicode(self.currentText()).strip()
        if not text:
            return self.clear()
        self.search.emit(text)

        if store_in_history:
            idx = self.findText(text, Qt.MatchFixedString|Qt.MatchCaseSensitive)
            self.block_signals(True)
            if idx < 0:
                self.insertItem(0, text)
            else:
                t = self.itemText(idx)
                self.removeItem(idx)
                self.insertItem(0, t)
            self.setCurrentIndex(0)
            self.block_signals(False)
            history = [unicode(self.itemText(i)) for i in
                    range(self.count())]
            config[self.opt_name] = history

    def do_search(self, *args):
        self._do_search()

    def block_signals(self, yes):
        self.blockSignals(yes)
        self.line_edit.blockSignals(yes)

    def set_search_string(self, txt, store_in_history=False, emit_changed=True):
        if not store_in_history:
            self.activated.disconnect()
        try:
            self.setFocus(Qt.OtherFocusReason)
            if not txt:
                self.clear()
            else:
                self.normalize_state()
                # must turn on case sensitivity here so that tag browser strings
                # are not case-insensitively replaced from history
                self.line_edit.completer().setCaseSensitivity(Qt.CaseSensitive)
                self.setEditText(txt)
                self.line_edit.end(False)
                if emit_changed:
                    self.changed.emit()
                self._do_search(store_in_history=store_in_history)
                self.line_edit.completer().setCaseSensitivity(Qt.CaseInsensitive)
            self.focus_to_library.emit()
        finally:
            if not store_in_history:
                self.activated.connect(self.history_selected)

    def search_as_you_type(self, enabled):
        self.as_you_type = enabled

    def in_a_search(self):
        return self._in_a_search

    @property
    def current_text(self):
        return unicode(self.lineEdit().text())
示例#48
0
class GamessJob(SimJob):
    """
    A Gamess job is a setup used to run Gamess simulation.
    Two ways exist to create a Gamess Job:
    (1). Create a Gamess Jig.
    (2). A gamess job coming from a set of existing files
    in a particular location. 
    """
    def __init__(self, job_parms, **job_prop):
        """
        To support the 2 ways of gamess job creation.
        """
        name = "Gamess Job 1"
        [self.job_batfile,
         self.job_outputfile] = job_prop.get('job_from_file', [None, None])
        if self.job_outputfile:
            self.job_outputfile = self.job_outputfile.strip('"')
        self.gamessJig = job_prop.get('jig', None)

        if self.job_batfile:
            server_id = job_parms['Server_id']
            self.server = ServerManager().getServerById(int(server_id))
            if not self.server:
                raise ValueError, "The server of %d can't be found." % server_id

        SimJob.__init__(self, name, job_parms)

        self.edit_cntl = GamessProp()

        #Huaicai 7/6/05: try to fix the problem when run a gamess jig coming from mmp file
        #and without opening the jig property windows and save it.
        if not self.__dict__.has_key('server'):
            sManager = ServerManager()
            self.server = sManager.getServers()[0]
        return

    def edit(self):
        self.edit_cntl.showDialog(self)
        return

    def launch(
        self
    ):  # this method should probably exist in the superclass API too [bruce 071216 comment]
        """
        Launch GAMESS job.
        Returns: 0 = Success
                 1 = Cancelled
                 2 = Failed
        """
        # Get a unique Job Id and the Job Id directory for this run.
        from analysis.GAMESS.JobManager import get_job_manager_job_id_and_dir
        ### this causes an import cycle.
        ### FIX - pass in an obj providing this func? or just move this func into SimJob.py?
        # [bruce 071216 comment]
        job_id, job_id_dir = get_job_manager_job_id_and_dir()
        self.Job_id = job_id
        self.Status = 'Queued'

        basename = self.gamessJig.name + "-" + self.gamessJig.gms_parms_info(
            '_')

        # GAMESS Job INP, OUT and BAT files.

        self.job_inputfile = os.path.join(job_id_dir, "%s.inp" % basename)

        self.job_outputfile = os.path.join(job_id_dir, "%s.out" % basename)
        self.job_batfile = os.path.join(job_id_dir, "%s.bat" % basename)

        # Write INP file (in ~/Nanorex/JobManager/Job Id subdirectory)
        writegms_inpfile(self.job_inputfile, self.gamessJig)

        # Create BAT file (in ~/Nanorex/JobManager/Job Id subdirectory)
        ## writegms_batfile(self.job_batfile, self)

        # Open INP file in editor if user checked checkbox in GAMESS jig properties UI.
        #        if self.edit_cntl.edit_input_file_cbox.isChecked():
        #            open_file_in_editor(self.job_inputfile)

        self.starttime = time.time()  # Save the start time for this job.

        # Validate Gamess Program
        r = self._validate_gamess_program()

        if r:  # Gamess program was not valid.
            return 1  # Job Cancelled.

        try:
            if sys.platform == 'win32':  # Windows
                return self._launch_pcgamess()
            else:  # Linux or MacOS
                return self._launch_gamess()
        except:
            print_compact_traceback("Exception happened when run gamess")
        return

    def _validate_gamess_program(self):
        """
        Private method:
        Checks that the GAMESS program path exists in the user pref database
        and that the file it points to exists.  If the GAMESS path does not exist, the
        user will be prompted with the file chooser to select the GAMESS executable.
        This function does not check whether the GAMESS path is actually GAMESS 
        or if it is the correct version of GAMESS for this platform (i.e. PC GAMESS for Windows).
        Returns:  0 = Valid
                  1 = Invalid
        """
        # Get GAMESS executable path from the user preferences
        prefs = preferences.prefs_context()
        self.server.program = prefs.get(gmspath_prefs_key)

        if not self.server.program:
            msg = "The GAMESS executable path is not set.\n"
        elif os.path.exists(self.server.program):
            return 0
        else:
            msg = self.server.program + " does not exist.\n"

        # GAMESS Prop Dialog is the parent for messagebox and file chooser.
        parent = self.edit_cntl

        ret = QMessageBox.warning(
            parent, "GAMESS Executable Path", msg +
            "Please select OK to set the location of GAMESS for this computer.",
            "&OK", "Cancel", "", 0, 1)

        if ret == 0:  # OK
            self.server.program = \
                get_filename_and_save_in_prefs(parent, gmspath_prefs_key, "Choose GAMESS Executable")
            if not self.server.program:
                return 1  # Cancelled from file chooser.

            # Enable GAMESS Plug-in. Mark 060112.
            env.prefs[gamess_enabled_prefs_key] = True

        elif ret == 1:  # Cancel
            return 1

        return 0

    def _readFromStdout(self):
        """
        Slot method to read stdout of the gamess process
        """
        #while self.process.canReadLineStdout():
        #    lineStr = str(self.process.readLineStdout()) + '\n'
        #    self.outputFile.write(lineStr)
        bytes = self.process.readStdout()
        self.outputFile.writeBlock(bytes)
        return

    def startFileWriting(self):
        self.fwThread.start()
        return

    def readOutData(self):
        bytes = self.process.readStdout()
        if bytes:
            self.fwThread.putData(bytes)
        return

    def processTimeout(self):
        if self.process.isRunning():
            if not self.progressDialog.isShown(
            ) or self.progressDialog.Rejected:
                return

            msgLabel = self.progressDialog.getMsgLabel()
            duration = time.time() - self.stime
            elapmsg = "Elapsed Time: " + hhmmss_str(int(duration))
            msgLabel.setText(elapmsg)

            ####bytes = self.process.readStdout()
            ####if bytes:
            ####    self.fwThread.putData(bytes)
        return

    def processDone(self):
        #self.fwThread.stop()
        self.jobTimer.stop()

        if self.process.normalExit():
            print "The process is done!"
            QDialog.accept(self.progressDialog)
        else:
            print "The process is cancelled!"
            QDialog.reject(self.progressDialog)
        return

    def _launch_gamess(self):
        oldir = os.getcwd()  # Save current directory

        ####self.outputFile = QFile(self.job_outputfile)
        ####self.outputFile.open( IO_WriteOnly | IO_Append )

        jobInputfile = os.path.basename(self.job_inputfile)
        jobInputFile = jobInputfile[:-4]
        jobOutputfile = self.job_outputfile  #os.path.basename(self.job_outputfile)

        ### Notes: The following way to get the 'bin' works by assuming user didn't change the
        ### working directory after the atom runs, otherwise it will get problem.
        filePath = os.path.dirname(os.path.abspath(sys.argv[0]))
        program = os.path.normpath(filePath + '/../bin/rungms')

        jobDir = os.path.dirname(self.job_batfile)
        os.chdir(jobDir)  # Change directory to the GAMESS temp directory.
        #        print "Current directory is: ", jobDir

        executableFile = self.server.program

        args = [program, jobInputFile, jobOutputfile,
                executableFile]  #, '01', '1']

        self.process = QProcess()
        for s in args:
            self.process.addArgument(s)
        ####self.process.setCommunication(QProcess.Stdout)
        print "The params for the QProcess run are: ", args

        ####self.fwThread = _FileWriting(self.outputFile)
        self.jobTimer = QTimer(self)

        self.progressDialog = JobProgressDialog(self.process, self.Calculation)
        self.connect(self.process, SIGNAL('processExited ()'),
                     self.processDone)

        #self.connect(self.process, SIGNAL('readyReadStdout()'), self.readOutData)
        ####self.fwThread.start()

        if not self.process.start():
            print "The process can't be started."
            return 2

        self.connect(self.jobTimer, SIGNAL('timeout()'), self.processTimeout)
        self.stime = time.time()
        self.jobTimer.start(1)

        ret = self.progressDialog.exec_()
        if ret == QDialog.Accepted:
            retValue = 0
        else:
            retValue = 1

        ####self.fwThread.wait()
        ####bytes = self.process.readStdout()
        ####if bytes:
        #####self.outputFile.writeBlock(bytes)
        #####self.outputFile.flush()

        self.process = None
        ####self.outputFile.close()

        os.chdir(oldir)
        self.gamessJig.outputfile = self.job_outputfile

        return retValue

    def _launch_pcgamess(self):
        """
        Run PC GAMESS (Windows only).
        PC GAMESS creates 2 output files:
          - the DAT file, called "PUNCH", is written to the directory from which
            PC GAMESS is started.  This is why we chdir to the Gamess temp directory
            before we run PC GAMESS.
          - the OUT file (aka the log file), which we name jigname.out.
        Returns: 0 = Success
                       1 = Cancelled
                       2 = Failed
        """
        oldir = os.getcwd()  # Save current directory

        jobDir = os.path.dirname(self.job_batfile)
        os.chdir(jobDir)  # Change directory to the GAMESS temp directory.
        ##        print "Current directory is: ", jobDir

        DATfile = os.path.join(jobDir, "PUNCH")
        if os.path.exists(DATfile):  # Remove any previous DAT (PUNCH) file.
            print "run_pcgamess: Removing DAT file: ", DATfile
            os.remove(DATfile)

        # Hours wasted testing this undocumented tripe.  Here's the deal: When using spawnv
        # on Windows, any args that might have spaces must be delimited by double quotes.
        # Mark 050530.

        #program = "\"" + self.job_batfile + "\""
        #args = [program, ]

        # Here's the infuriating part.  The 2nd arg to spawnv cannot have double quotes, but the
        # first arg in args (the program name) must have the double quotes if there is a space in
        # self.gms_program.

        #print  "program = ", program
        #print  "Spawnv args are %r" % (args,) # this %r remains (see above)
        #os.spawnv(os.P_WAIT, self.job_batfile, args)

        arg_list = ['-i', self.job_inputfile, '-o', self.job_outputfile]
        args = QStringList()
        for s in arg_list:
            args.append(str(s))

        process = QProcess()
        process.start(self.server.program, args)
        # Blocks for n millisconds until the process has started and started()
        # signal is emitted. Returns true if the process was started successfullly.
        if not process.waitForStarted(2000):
            print "The process can't be started."
            return 2
        progressDialog = self.showProgress()
        progressDialog.show()
        i = 55
        pInc = True
        while process.state() == QProcess.Running:
            env.call_qApp_processEvents(
            )  #bruce 050908 replaced qApp.processEvents()
            if progressDialog.wasCanceled():
                process.kill()
                os.chdir(oldir)
                return 1  # Job cancelled.

            progressDialog.setValue(i)
            if pInc:
                if i < 75: i += 1
                else: pInc = False
            else:
                if i > 55: i -= 1
                else: pInc = True
            # Do sth here
            time.sleep(0.05)
            if not process.state() == QProcess.Running:
                break

        progressDialog.setValue(100)
        progressDialog.accept()

        os.chdir(oldir)
        self.gamessJig.outputfile = self.job_outputfile

        return 0  # Success

    def showProgress(self, modal=True):
        """
        Open the progress dialog to show the current job progress.
        """
        #Replace "self.edit_cntl.win" with "None"
        #---Huaicai 7/7/05: To fix bug 751, the "win" may be none.
        #Feel awkward for the design of our code.
        simProgressDialog = QProgressDialog()
        simProgressDialog.setModal(True)
        simProgressDialog.setObjectName("progressDialog")
        simProgressDialog.setWindowIcon(geticon('ui/border/MainWindow'))
        if self.Calculation == 'Energy':
            simProgressDialog.setWindowTitle(
                "Calculating Energy ...Please Wait")
        else:
            simProgressDialog.setWindowTitle("Optimizing ...Please Wait")

        progBar = QProgressBar(simProgressDialog)
        progBar.setMaximum(0)
        progBar.setMinimum(0)
        progBar.setValue(0)
        progBar.setTextVisible(False)
        simProgressDialog.setBar(progBar)
        simProgressDialog.setAutoReset(False)
        simProgressDialog.setAutoClose(False)

        return simProgressDialog

    # Added by Mark 050919 for bug #915.  Not used yet.
    def _job_cancelation_confirmed(self):
        ret = QMessageBox.warning(
            None,
            "Confirm",
            "Please confirm you want to abort the GAMESS simulation.\n",
            "Confirm",
            "Cancel",
            "",
            1,  # The "default" button, when user presses Enter or Return (1 = Cancel)
            1)  # Escape (1= Cancel)

        if ret == 0:  # Confirmed
            print "CONFIRMED"
            return True
        else:
            print "CANCELLED"
            return False

    pass  # end of class GamessJob
示例#49
0
class RotateMode_GM( TemporaryCommand_Overdrawing.GraphicsMode_class ):
    """
    Custom GraphicsMode for use as a component of RotateMode.
    """
    
    def __init__(self, glpane):
        TemporaryCommand_Overdrawing.GraphicsMode_class.__init__(self, glpane)

        self.auto_rotate = False # set to True when user presses "A" key while
        self.animationTimer = None # time used to animate view
        self.last_quat = None # last quaternion to be used for incremental rotation 
    
    def leftDown(self, event):
        ## global clicked
        ## clicked = True
        self.glpane.SaveMouse(event)
        self.glpane.trackball.start(self.glpane.MousePos[0],
                                    self.glpane.MousePos[1])
        
        # piotr 080807: The most recent quaternion to be used for "auto-rotate" 
        # animation, initially set to None, so the animation stops when
        # user pushes down mouse button.
        self.last_quat = None
        
        self.picking = False
        return
        
    def leftDrag(self, event):
        ## global clicked
        ## if clicked:
        ##     set_enabled_for_profile_single_call(True)
        ##     clicked = False

        self.glpane.SaveMouse(event)
        q = self.glpane.trackball.update(self.glpane.MousePos[0],
                                         self.glpane.MousePos[1])
        self.glpane.quat += q 
        
        # piotr 080807: Remember the most recent quaternion to be used
        # in 'auto_rotate' mode. Do it only if 'auto_rotate' class attribute
        # is True, i.e. when user pressed an "A" key while dragging the mouse.
        if self.auto_rotate:
            self.last_quat = q
                
        self.glpane.gl_update()
        self.picking = False
        return
        
    def leftUp(self, event):
        if self.last_quat:
            # Create and enable animation timer.
            if self.animationTimer is None:
                self.animationTimer  =  QTimer(self.glpane)
                self.win.connect(self.animationTimer, 
                                 SIGNAL('timeout()'), 
                                 self._animationTimerTimeout)
            self.animationTimer.start(20) # use 50 fps for smooth animation
        else:
            # Stop animation if mouse was not dragged.
            if self.animationTimer:
                self.animationTimer.stop()
    
    def _animationTimerTimeout(self):
        if self.last_quat:
            self.glpane.quat += self.last_quat
            self.glpane.gl_update()
    
    def update_cursor_for_no_MB(self): # Fixes bug 1638. Mark 3/12/2006
        """
        Update the cursor for 'Rotate' mode.
        """
        self.glpane.setCursor(self.win.RotateViewCursor)
        return

    def keyPress(self, key):
        if key == Qt.Key_A:
            self.auto_rotate = True
            
        _superclass.keyPress(self, key)
        return
        
    def keyRelease(self, key):
        if key == Qt.Key_A:
            self.auto_rotate = False
            
        _superclass.keyRelease(self, key)
        return
    pass
示例#50
0
 def setupTimer(self):
     timer = QTimer(self)
     timer.timeout.connect(self.count)
     timer.setInterval(2000)
     timer.start()
示例#51
0
class SearchDialog(QDialog, Ui_Dialog):

    def __init__(self, gui, parent=None, query=''):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.config = JSONConfig('store/search')
        self.search_title.initialize('store_search_search_title')
        self.search_author.initialize('store_search_search_author')
        self.search_edit.initialize('store_search_search')

        # Loads variables that store various settings.
        # This needs to be called soon in __init__ because
        # the variables it sets up are used later.
        self.load_settings()

        self.gui = gui

        # Setup our worker threads.
        self.search_pool = SearchThreadPool(self.search_thread_count)
        self.cache_pool = CacheUpdateThreadPool(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(self.details_thread_count)
        self.results_view.setCursor(Qt.PointingHandCursor)

        # Check for results and hung threads.
        self.checker = QTimer()
        self.progress_checker = QTimer()
        self.hang_check = 0

        # Update store caches silently.
        for p in self.gui.istores.values():
            self.cache_pool.add_task(p, self.timeout)

        self.store_checks = {}
        self.setup_store_checks()

        # Set the search query
        if isinstance(query, (str, unicode)):
            self.search_edit.setText(query)
        elif isinstance(query, dict):
            if 'author' in query:
                self.search_author.setText(query['author'])
            if 'title' in query:
                self.search_title.setText(query['title'])
        # Title
        self.search_title.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.search_title.setMinimumContentsLength(25)
        # Author
        self.search_author.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.search_author.setMinimumContentsLength(25)
        # Keyword
        self.search_edit.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.search_edit.setMinimumContentsLength(25)

        # Create and add the progress indicator
        self.pi = ProgressIndicator(self, 24)
        self.button_layout.takeAt(0)
        self.button_layout.setAlignment(Qt.AlignCenter)
        self.button_layout.insertWidget(0, self.pi, 0, Qt.AlignCenter)

        self.adv_search_button.setIcon(QIcon(I('search.png')))
        self.configure.setIcon(QIcon(I('config.png')))

        self.adv_search_button.clicked.connect(self.build_adv_search)
        self.search.clicked.connect(self.do_search)
        self.checker.timeout.connect(self.get_results)
        self.progress_checker.timeout.connect(self.check_progress)
        self.results_view.activated.connect(self.result_item_activated)
        self.results_view.download_requested.connect(self.download_book)
        self.results_view.open_requested.connect(self.open_store)
        self.results_view.model().total_changed.connect(self.update_book_total)
        self.select_all_stores.clicked.connect(self.stores_select_all)
        self.select_invert_stores.clicked.connect(self.stores_select_invert)
        self.select_none_stores.clicked.connect(self.stores_select_none)
        self.configure.clicked.connect(self.do_config)
        self.finished.connect(self.dialog_closed)

        self.progress_checker.start(100)

        self.restore_state()

    def setup_store_checks(self):
        first_run = self.config.get('first_run', True)

        # Add check boxes for each store so the user
        # can disable searching specific stores on a
        # per search basis.
        existing = {}
        for n in self.store_checks:
            existing[n] = self.store_checks[n].isChecked()

        self.store_checks = {}

        stores_check_widget = QWidget()
        store_list_layout = QGridLayout()
        stores_check_widget.setLayout(store_list_layout)

        icon = QIcon(I('donate.png'))
        for i, x in enumerate(sorted(self.gui.istores.keys(), key=lambda x: x.lower())):
            cbox = QCheckBox(x)
            cbox.setChecked(existing.get(x, first_run))
            store_list_layout.addWidget(cbox, i, 0, 1, 1)
            if self.gui.istores[x].base_plugin.affiliate:
                iw = QLabel(self)
                iw.setToolTip('<p>' + _('Buying from this store supports the calibre developer: %s</p>') % self.gui.istores[x].base_plugin.author + '</p>')
                iw.setPixmap(icon.pixmap(16, 16))
                store_list_layout.addWidget(iw, i, 1, 1, 1)
            self.store_checks[x] = cbox
        store_list_layout.setRowStretch(store_list_layout.rowCount(), 10)
        self.store_list.setWidget(stores_check_widget)

        self.config['first_run'] = False

    def build_adv_search(self):
        adv = AdvSearchBuilderDialog(self)
        if adv.exec_() == QDialog.Accepted:
            self.search_edit.setText(adv.search_string())

    def resize_columns(self):
        total = 600
        # Cover
        self.results_view.setColumnWidth(0, 85)
        total = total - 85
        # Title / Author
        self.results_view.setColumnWidth(1,int(total*.40))
        # Price
        self.results_view.setColumnWidth(2,int(total*.12))
        # DRM
        self.results_view.setColumnWidth(3, int(total*.15))
        # Store / Formats
        self.results_view.setColumnWidth(4, int(total*.25))
        # Download
        self.results_view.setColumnWidth(5, 20)
        # Affiliate
        self.results_view.setColumnWidth(6, 20)

    def do_search(self):
        # Stop all running threads.
        self.checker.stop()
        self.search_pool.abort()
        # Clear the visible results.
        self.results_view.model().clear_results()

        # Don't start a search if there is nothing to search for.
        query = []
        if self.search_title.text():
            query.append(u'title2:"~%s"' % unicode(self.search_title.text()).replace('"', ' '))
        if self.search_author.text():
            query.append(u'author2:"%s"' % unicode(self.search_author.text()).replace('"', ' '))
        if self.search_edit.text():
            query.append(unicode(self.search_edit.text()))
        query = " ".join(query)
        if not query.strip():
            error_dialog(self, _('No query'),
                        _('You must enter a title, author or keyword to'
                          ' search for.'), show=True)
            return
        # Give the query to the results model so it can do
        # futher filtering.
        self.results_view.model().set_query(query)

        # Plugins are in random order that does not change.
        # Randomize the ord of the plugin names every time
        # there is a search. This way plugins closer
        # to a don't have an unfair advantage over
        # plugins further from a.
        store_names = self.store_checks.keys()
        if not store_names:
            return
        # Remove all of our internal filtering logic from the query.
        query = self.clean_query(query)
        shuffle(store_names)
        # Add plugins that the user has checked to the search pool's work queue.
        self.gui.istores.join(4.0) # Wait for updated plugins to load
        for n in store_names:
            if self.store_checks[n].isChecked():
                self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout)
        self.hang_check = 0
        self.checker.start(100)
        self.pi.startAnimation()

    def clean_query(self, query):
        query = query.lower()
        # Remove control modifiers.
        query = query.replace('\\', '')
        query = query.replace('!', '')
        query = query.replace('=', '')
        query = query.replace('~', '')
        query = query.replace('>', '')
        query = query.replace('<', '')
        # Remove the prefix.
        for loc in ('all', 'author', 'author2', 'authors', 'title', 'title2'):
            query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query)
            query = query.replace('%s:' % loc, '')
        # Remove the prefix and search text.
        for loc in ('cover', 'download', 'downloads', 'drm', 'format', 'formats', 'price', 'store'):
            query = re.sub(r'%s:"[^"]"' % loc, '', query)
            query = re.sub(r'%s:[^\s]*' % loc, '', query)
        # Remove logic.
        query = re.sub(r'(^|\s)(and|not|or|a|the|is|of)(\s|$)', ' ', query)
        # Remove "
        query = query.replace('"', '')
        # Remove excess whitespace.
        query = re.sub(r'\s{2,}', ' ', query)
        query = query.strip()
        return query.encode('utf-8')

    def save_state(self):
        self.config['geometry'] = bytearray(self.saveGeometry())
        self.config['store_splitter_state'] = bytearray(self.store_splitter.saveState())
        self.config['results_view_column_width'] = [self.results_view.columnWidth(i) for i in range(self.results_view.model().columnCount())]
        self.config['sort_col'] = self.results_view.model().sort_col
        self.config['sort_order'] = self.results_view.model().sort_order
        self.config['open_external'] = self.open_external.isChecked()

        store_check = {}
        for k, v in self.store_checks.items():
            store_check[k] = v.isChecked()
        self.config['store_checked'] = store_check

    def restore_state(self):
        geometry = self.config.get('geometry', None)
        if geometry:
            self.restoreGeometry(geometry)

        splitter_state = self.config.get('store_splitter_state', None)
        if splitter_state:
            self.store_splitter.restoreState(splitter_state)

        results_cwidth = self.config.get('results_view_column_width', None)
        if results_cwidth:
            for i, x in enumerate(results_cwidth):
                if i >= self.results_view.model().columnCount():
                    break
                self.results_view.setColumnWidth(i, x)
        else:
            self.resize_columns()

        self.open_external.setChecked(self.should_open_external)

        store_check = self.config.get('store_checked', None)
        if store_check:
            for n in store_check:
                if n in self.store_checks:
                    self.store_checks[n].setChecked(store_check[n])

        self.results_view.model().sort_col = self.config.get('sort_col', 2)
        self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder)
        self.results_view.header().setSortIndicator(self.results_view.model().sort_col, self.results_view.model().sort_order)

    def load_settings(self):
        # Seconds
        self.timeout = self.config.get('timeout', 75)
        # Milliseconds
        self.hang_time = self.config.get('hang_time', 75) * 1000

        self.max_results = self.config.get('max_results', 15)
        self.should_open_external = self.config.get('open_external', True)

        # Number of threads to run for each type of operation
        self.search_thread_count = self.config.get('search_thread_count', 4)
        self.cache_thread_count = self.config.get('cache_thread_count', 2)
        self.cover_thread_count = self.config.get('cover_thread_count', 2)
        self.details_thread_count = self.config.get('details_thread_count', 4)

    def do_config(self):
        # Save values that need to be synced between the dialog and the
        # search widget.
        self.config['open_external'] = self.open_external.isChecked()

        # Create the config dialog. It's going to put two config widgets
        # into a QTabWidget for displaying all of the settings.
        d = QDialog(self)
        button_box = QDialogButtonBox(QDialogButtonBox.Close)
        v = QVBoxLayout(d)
        button_box.accepted.connect(d.accept)
        button_box.rejected.connect(d.reject)
        d.setWindowTitle(_('Customize get books search'))

        tab_widget = QTabWidget(d)
        v.addWidget(tab_widget)
        v.addWidget(button_box)

        chooser_config_widget = StoreChooserWidget()
        search_config_widget = StoreConfigWidget(self.config)

        tab_widget.addTab(chooser_config_widget, _('Choose stores'))
        tab_widget.addTab(search_config_widget, _('Configure search'))

        # Restore dialog state.
        geometry = self.config.get('config_dialog_geometry', None)
        if geometry:
            d.restoreGeometry(geometry)
        else:
            d.resize(800, 600)
        tab_index = self.config.get('config_dialog_tab_index', 0)
        tab_index = min(tab_index, tab_widget.count() - 1)
        tab_widget.setCurrentIndex(tab_index)

        d.exec_()

        # Save dialog state.
        self.config['config_dialog_geometry'] = bytearray(d.saveGeometry())
        self.config['config_dialog_tab_index'] = tab_widget.currentIndex()

        search_config_widget.save_settings()
        self.config_changed()
        self.gui.load_store_plugins()
        self.setup_store_checks()

    def config_changed(self):
        self.load_settings()

        self.open_external.setChecked(self.should_open_external)
        self.search_pool.set_thread_count(self.search_thread_count)
        self.cache_pool.set_thread_count(self.cache_thread_count)
        self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count)
        self.results_view.model().details_pool.set_thread_count(self.details_thread_count)

    def get_results(self):
        # We only want the search plugins to run
        # a maximum set amount of time before giving up.
        self.hang_check += 1
        if self.hang_check >= self.hang_time:
            self.search_pool.abort()
            self.checker.stop()
        else:
            # Stop the checker if not threads are running.
            if not self.search_pool.threads_running() and not self.search_pool.has_tasks():
                self.checker.stop()

        while self.search_pool.has_results():
            res, store_plugin = self.search_pool.get_result()
            if res:
                self.results_view.model().add_result(res, store_plugin)

        if not self.search_pool.threads_running() and not self.results_view.model().has_results():
            info_dialog(self, _('No matches'), _('Couldn\'t find any books matching your query.'), show=True, show_copy_button=False)

    def update_book_total(self, total):
        self.total.setText('%s' % total)

    def result_item_activated(self, index):
        result = self.results_view.model().get_result(index)

        if result.downloads:
            self.download_book(result)
        else:
            self.open_store(result)

    def download_book(self, result):
        d = ChooseFormatDialog(self, _('Choose format to download to your library.'), result.downloads.keys())
        if d.exec_() == d.Accepted:
            ext = d.format()
            fname = result.title[:60] + '.' + ext.lower()
            fname = ascii_filename(fname)
            self.gui.download_ebook(result.downloads[ext], filename=fname)

    def open_store(self, result):
        self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked())

    def check_progress(self):
        if not self.search_pool.threads_running() and not self.results_view.model().cover_pool.threads_running() and not self.results_view.model().details_pool.threads_running():
            self.pi.stopAnimation()
        else:
            if not self.pi.isAnimated():
                self.pi.startAnimation()

    def stores_select_all(self):
        for check in self.store_checks.values():
            check.setChecked(True)

    def stores_select_invert(self):
        for check in self.store_checks.values():
            check.setChecked(not check.isChecked())

    def stores_select_none(self):
        for check in self.store_checks.values():
            check.setChecked(False)

    def dialog_closed(self, result):
        self.results_view.model().closing()
        self.search_pool.abort()
        self.cache_pool.abort()
        self.save_state()

    def exec_(self):
        if unicode(self.search_edit.text()).strip() or unicode(self.search_title.text()).strip() or unicode(self.search_author.text()).strip():
            self.do_search()
        return QDialog.exec_(self)
示例#52
0
class Preview(QWidget):

    sync_requested = pyqtSignal(object, object)
    split_requested = pyqtSignal(object, object)
    split_start_requested = pyqtSignal()

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout()
        self.setLayout(l)
        l.setContentsMargins(0, 0, 0, 0)
        self.view = WebView(self)
        self.view.page().sync_requested.connect(self.request_sync)
        self.view.page().split_requested.connect(self.request_split)
        self.view.page().loadFinished.connect(self.load_finished)
        self.inspector = self.view.inspector
        self.inspector.setPage(self.view.page())
        l.addWidget(self.view)
        self.bar = QToolBar(self)
        l.addWidget(self.bar)

        ac = actions['auto-reload-preview']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.auto_reload_toggled)
        self.auto_reload_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['sync-preview-to-editor']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.sync_toggled)
        self.sync_toggled(ac.isChecked())
        self.bar.addAction(ac)

        self.bar.addSeparator()

        ac = actions['split-in-preview']
        ac.setCheckable(True)
        ac.setChecked(False)
        ac.toggled.connect(self.split_toggled)
        self.split_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['reload-preview']
        ac.triggered.connect(self.refresh)
        self.bar.addAction(ac)

        actions['preview-dock'].toggled.connect(self.visibility_changed)

        self.current_name = None
        self.last_sync_request = None
        self.refresh_timer = QTimer(self)
        self.refresh_timer.timeout.connect(self.refresh)
        parse_worker.start()
        self.current_sync_request = None

        self.search = HistoryLineEdit2(self)
        self.search.initialize('tweak_book_preview_search')
        self.search.setPlaceholderText(_('Search in preview'))
        self.search.returnPressed.connect(partial(self.find, 'next'))
        self.bar.addSeparator()
        self.bar.addWidget(self.search)
        for d in ('next', 'prev'):
            ac = actions['find-%s-preview' % d]
            ac.triggered.connect(partial(self.find, d))
            self.bar.addAction(ac)

    def find(self, direction):
        text = unicode(self.search.text())
        self.view.findText(text, QWebPage.FindWrapsAroundDocument | (
            QWebPage.FindBackward if direction == 'prev' else QWebPage.FindFlags(0)))

    def request_sync(self, lnum):
        if self.current_name:
            self.sync_requested.emit(self.current_name, lnum)

    def request_split(self, loc):
        if self.current_name:
            self.split_requested.emit(self.current_name, loc)

    def sync_to_editor(self, name, lnum):
        self.current_sync_request = (name, lnum)
        QTimer.singleShot(100, self._sync_to_editor)

    def _sync_to_editor(self):
        if not actions['sync-preview-to-editor'].isChecked():
            return
        try:
            if self.refresh_timer.isActive() or self.current_sync_request[0] != self.current_name:
                return QTimer.singleShot(100, self._sync_to_editor)
        except TypeError:
            return  # Happens if current_sync_request is None
        lnum = self.current_sync_request[1]
        self.current_sync_request = None
        self.view.page().go_to_line(lnum)

    def show(self, name):
        if name != self.current_name:
            self.refresh_timer.stop()
            self.current_name = name
            parse_worker.add_request(name)
            self.view.setUrl(QUrl.fromLocalFile(current_container().name_to_abspath(name)))

    def refresh(self):
        if self.current_name:
            self.refresh_timer.stop()
            # This will check if the current html has changed in its editor,
            # and re-parse it if so
            parse_worker.add_request(self.current_name)
            # Tell webkit to reload all html and associated resources
            current_url = QUrl.fromLocalFile(current_container().name_to_abspath(self.current_name))
            if current_url != self.view.url():
                # The container was changed
                self.view.setUrl(current_url)
            else:
                self.view.refresh()

    def clear(self):
        self.view.clear()

    @property
    def is_visible(self):
        return actions['preview-dock'].isChecked()

    def start_refresh_timer(self):
        if self.is_visible and actions['auto-reload-preview'].isChecked():
            self.refresh_timer.start(tprefs['preview_refresh_time'] * 1000)

    def stop_refresh_timer(self):
        self.refresh_timer.stop()

    def auto_reload_toggled(self, checked):
        actions['auto-reload-preview'].setToolTip(_(
            'Auto reload preview when text changes in editor') if not checked else _(
                'Disable auto reload of preview'))

    def sync_toggled(self, checked):
        actions['sync-preview-to-editor'].setToolTip(_(
            'Disable syncing of preview position to editor position') if checked else _(
                'Enable syncing of preview position to editor position'))

    def visibility_changed(self, is_visible):
        if is_visible:
            self.refresh()

    def split_toggled(self, checked):
        actions['split-in-preview'].setToolTip(textwrap.fill(_(
            'Abort file split') if checked else _(
                'Split this file at a specified location.\n\nAfter clicking this button, click'
                ' inside the preview panel above at the location you want the file to be split.')))
        if checked:
            self.split_start_requested.emit()
        else:
            self.view.page().split_mode(False)

    def do_start_split(self):
        self.view.page().split_mode(True)

    def stop_split(self):
        actions['split-in-preview'].setChecked(False)

    def load_finished(self, ok):
        if actions['split-in-preview'].isChecked():
            if ok:
                self.do_start_split()
            else:
                self.stop_split()
示例#53
0
class Widget(QWidget, ScreenWidget):
    name = "summary"

    def __init__(self):
        QWidget.__init__(self)
        self.ui = Ui_SummaryWidget()
        self.ui.setupUi(self)

        self.ui.content.setText("")
        self.timer = QTimer()
        self.start_time = 0

        try:
            self.connect(self.timer, SIGNAL("timeout()"), self.updateCounter)
        except:
            pass

    def slotReboot(self):
        reply = QuestionDialog(_("Restart"),
                               _('''<b><p>Are you sure you want to restart the computer?</p></b>'''))
        if reply == "yes":
            yali.util.reboot()

    def startBombCounter(self):
        self.start_time = int(time.time())
        self.timer.start(1000)

    def backCheck(self):
        self.timer.stop()
        ctx.interface.informationWindow.hide()
        if ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT:
            ctx.mainScreen.ui.buttonNext.setText(_("Next"))

        if (ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT) \
           and not ctx.flags.collection:
            ctx.mainScreen.step_increment = 2
        return True

    def updateCounter(self):
        remain = 20 - (int(time.time()) - self.start_time)
        ctx.interface.informationWindow.update(_("Installation starts in <b>%s</b> seconds") % remain)
        if remain <= 0:
            self.timer.stop()
            ctx.mainScreen.slotNext()

    def shown(self):
        #ctx.mainScreen.disableNext()
        if ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT:
            ctx.mainScreen.ui.buttonNext.setText(_("Start Installation"))
        if ctx.flags.install_type == ctx.STEP_FIRST_BOOT:
            ctx.mainScreen.ui.buttonNext.setText(_("Apply Settings"))

        if ctx.installData.isKahyaUsed:
            self.startBombCounter()

        self.fillContent()

    def fillContent(self):
        subject = "<p><li><b>%s</b></li><ul>"
        item    = "<li>%s</li>"
        end     = "</ul></p>"
        content = QString("")

        content.append("""<html><body><ul>""")

        # Keyboard Layout
        if ctx.installData.keyData:
            content.append(subject % _("Keyboard Settings"))
            content.append(item %
                           _("Selected keyboard layout is <b>%s</b>") %
                           ctx.installData.keyData["name"])
            content.append(end)

        # TimeZone
        if ctx.installData.timezone:
            content.append(subject % _("Date/Time Settings"))
            content.append(item %
                           _("Selected TimeZone is <b>%s</b>") %
                           ctx.installData.timezone)
            content.append(end)

        # Users
        if len(yali.users.PENDING_USERS) > 0:
            content.append(subject % _("User Settings"))
            for user in yali.users.PENDING_USERS:
                state = _("User %(username)s (<b>%(realname)s</b>) added.")
                if "wheel" in user.groups:
                    state = _("User %(username)s (<b>%(realname)s</b>) added with <u>administrator privileges</u>.")
                content.append(item % state % {"username":user.realname, "realname":user.username})
            content.append(end)

        # HostName
        if ctx.installData.hostName:
            content.append(subject % _("Hostname Settings"))
            content.append(item %
                           _("Hostname is set as <b>%s</b>") %
                           ctx.installData.hostName)
            content.append(end)

        # Partition
        if ctx.storage.clearPartType is not None:
            content.append(subject % _("Partition Settings"))
            devices = ""
            for disk in ctx.storage.clearPartDisks:
                device = ctx.storage.devicetree.getDeviceByName(disk)
                devices += "(%s on %s)" % (device.model, device.name)

            if ctx.storage.doAutoPart:
                content.append(item % _("Automatic Partitioning selected."))
                if ctx.storage.clearPartType == CLEARPART_TYPE_ALL:
                    content.append(item % _("Use All Space"))
                    content.append(item % _("Removes all partitions on the selected "\
                                            "%s device(s). <b><u>This includes partitions "\
                                            "created by other operating systems.</u></b>") % devices)
                elif ctx.storage.clearPartType == CLEARPART_TYPE_LINUX:
                    content.append(item % _("Replace Existing Linux System(s)"))
                    content.append(item % _("Removes all Linux partitions on the selected" \
                                            "%s device(s). This does not remove "\
                                            "other partitions you may have on your " \
                                            "storage device(s) (such as VFAT or FAT32)") % devices)
                elif ctx.storage.clearPartType == CLEARPART_TYPE_NONE:
                    content.append(item % _("Use Free Space"))
                    content.append(item % _("Retains your current data and partitions" \
                                            " and uses only the unpartitioned space on " \
                                            "the selected %s device(s), assuming you have "\
                                            "enough free space available.") % devices)

            else:
                content.append(item % _("Manual Partitioning selected."))
                for operation in ctx.storage.devicetree.operations:
                    content.append(item % operation)

            content.append(end)

        # Bootloader
        if ctx.bootloader.stage1Device:
            content.append(subject % _("Bootloader Settings"))
            grubstr = _("GRUB will be installed to <b>%s</b>.")
            if ctx.bootloader.bootType == BOOT_TYPE_NONE:
                content.append(item % _("GRUB will not be installed."))
            else:
                content.append(item % grubstr % ctx.bootloader.stage1Device)

            content.append(end)

        if ctx.flags.collection and ctx.installData.autoCollection:
            content.append(subject % _("Package Installation Settings"))
            content.append(item % _("Collection <b>%s</b> selected") %
                           ctx.installData.autoCollection.title)

            content.append(end)

        content.append("""</ul></body></html>""")

        self.ui.content.setHtml(content)

    def execute(self):
        self.timer.stop()

        if ctx.flags.dryRun:
            ctx.logger.debug("dryRun activated Yali stopped")
            ctx.mainScreen.enableBack()
            return False

        if ctx.flags.install_type == ctx.STEP_BASE or ctx.flags.install_type == ctx.STEP_DEFAULT:
            self.createPackageList()

            rc = ctx.interface.messageWindow(_("Confirm"),
                                        _("The partitioning options you have selected "
                                          "will now be written to disk. Any "
                                          "data on deleted or reformatted partitions "
                                          "will be lost."),
                                          type = "custom", customIcon="question",
                                          customButtons=[_("Write Changes to Disk"), _("Go Back")],
                                          default=1)

            ctx.mainScreen.processEvents()

            if not rc:
                if yali.storage.complete(ctx.storage, ctx.interface):
                    ctx.storage.turnOnSwap()
                    ctx.storage.mountFilesystems(readOnly=False, skipRoot=False)
                    ctx.mainScreen.step_increment = 1
                    ctx.mainScreen.ui.buttonNext.setText(_("Next"))
                    return True

            ctx.mainScreen.enableBack()
            return False

        elif ctx.flags.install_type == ctx.STEP_FIRST_BOOT:
            ctx.mainScreen.ui.buttonNext.setText(_("Next"))
            return True


        # Auto Partitioning
        #if not ctx.storage.storageset.swapDevices:
        #    size = 0
        #    if yali.util.memInstalled() > 512:
        #        size = 300
        #    else:
        #        size = 600
        #    ctx.storage.storageset.createSwapFile(ctx.storage.storageset.rootDevice, ctx.consts.target_dir, size)

    def createPackageList(self):
        ctx.logger.debug("Generating package list...")

        if ctx.flags.collection:
            # Get only collection packages with collection Name
            packages = yali.pisiiface.getAllPackagesWithPaths(
                        collectionIndex=ctx.installData.autoCollection.index)
        else:
            # Check for just installing system.base packages
            if ctx.flags.baseonly:
                packages = yali.pisiiface.getBasePackages()
            else:
                packages = yali.pisiiface.getAllPackagesWithPaths()

        # Check for extra languages
        packages = list(set(packages) - set(yali.pisiiface.getNotNeededLanguagePackages()))
        ctx.logger.debug("Not needed lang packages will not be installing...")

        packages = self.filterDriverPacks(packages)
        packages.sort()

        # Place baselayout package on the top of package list
        baselayout = None
        for path in packages:
            if "/baselayout-" in path:
                baselayout = packages.index(path)
                break

        if baselayout:
            packages.insert(0, packages.pop(baselayout))

        ctx.packagesToInstall = packages

    def filterDriverPacks(self, paths):
        try:
            from panda import Panda
        except ImportError:
            ctx.logger.debug("Installing all driver packages since panda module is not installed.")
            return paths

        panda = Panda()

        # filter all driver packages
        foundDriverPackages = set(yali.pisiiface.getPathsByPackageName(panda.get_all_driver_packages()))
        ctx.logger.debug("Found driver packages: %s" % foundDriverPackages)

        allPackages = set(paths)
        packages = allPackages - foundDriverPackages

        # detect hardware
        neededDriverPackages = set(yali.pisiiface.getPathsByPackageName(panda.get_needed_driver_packages()))
        ctx.logger.debug("Known driver packages for this hardware: %s" % neededDriverPackages)

        # if alternatives are available ask to user, otherwise return
        if neededDriverPackages and neededDriverPackages.issubset(allPackages):
            answer = ctx.interface.messageWindow(
                    _("Proprietary Hardware Drivers"),
                    _("<qt>Proprietary drivers are available which may be required "
                      "to utilize the full capabilities of your video card. "
                      "These drivers are developed by the hardware manufacturer "
                      "and not supported by Pardus developers since their "
                      "source code is not publicly available."
                      "<br><br>"
                      "<b>Do you want to install and use these proprietary drivers "
                      "instead of the default drivers?</b></qt>"),
                      type="custom", customIcon="question",
                      customButtons=[_("Yes"), _("No")])

            if answer == 0:
                packages.update(neededDriverPackages)
                ctx.blacklistedKernelModules.append(panda.get_blacklisted_module())
                ctx.logger.debug("These driver packages will be installed: %s" % neededDriverPackages)

        return list(packages)
示例#54
0
class VoivoiShow(Slide):
    '''
    VoiVoi Slideshow class for displaying images from voivoi database
    '''
    imgList = list()
    eventID = 0
    mostRecent = datetime.fromtimestamp(0)
    host = "127.0.0.1"
    user = "******"
    passwd = "secret"
    db = "voivoi"
    
    def __init__(self, parent = None):
        '''
        Constructor
        '''
        super(VoivoiShow, self).__init__(parent)
        #self.setStyleSheet("background: #000;")
        
        self.splashscreen = QImage("res/splashscreen.jpg")
        #print(self.splashscreen.isNull())
        if not self.splashscreen.isNull():
            self.imgList.append(self.splashscreen)
                                      
        self.imageLabel = QLabel()
        self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.imageLabel.setParent(self)

        self.httpPool = urllib3.PoolManager()
        
        self.slideIterator = 0
        self.timer.timeout.connect(self.nextImage)
        self.updateTimer = QTimer()
        self.updateTimer.timeout.connect(self.updateImages)
        self.mostRecent = datetime.fromtimestamp(0)
        
        self.layout = QHBoxLayout()
        self.layout.setMargin(0)
        self.layout.addWidget(self.imageLabel)
        self.layout.setAlignment(self.imageLabel, Qt.AlignHCenter)
        self.setLayout(self.layout)
        self.voivoifont = QFont("Helvetica", 48)
        self.voivoifont.setBold(True)
        
    def setup(self, host, user, passwd, event_id, database="voivoi", slideInterval=10, updateInterval=60):
        ''' setup the slide for it's display routine '''
        
        self.host = host
        print(self.host)
        self.user = user
        self.passwd = passwd
        self.interval = slideInterval
        self.updateInterval = updateInterval
        self.db=database
        self.eventID = event_id
        self.updateImages()
        
    def updateImages(self, buffer_limit=20):
        try:
            db_connection = pymysql.connect(self.host, self.user, self.passwd, self.db)
        except: 
            print("Database connection not available")
            return
        
        sqlquery = "SELECT `categoryOrder`, `userID`, `dayID`, `comment`, `timestamp`, `eventID` FROM `pictures` WHERE `eventID`>=" + str(self.eventID) + " AND `timestamp`>'" + str(self.mostRecent) + "' ORDER BY `timestamp` ASC"
        #print(sqlquery)
        try:
            with db_connection.cursor() as cursor:
                cursor.execute(sqlquery)
                
                result = cursor.fetchall()
                
                for entry in result:
                    print(entry[4])
                    #print(self.mostRecent)
                    if entry[4] > self.mostRecent:
                        if entry[0] == 0:
                            print("BREAKING")
                            continue
                        self.mostRecent = entry[4]
                        image_url = "http://voivoi.eventfive.de/events/" + str(entry[5]) + "/uploads/" + str(entry[0]) + "_" + str(entry[1]) + "_" + str(entry[2]) + ".jpg"
                        req = self.httpPool.request('GET',image_url)
                        if req.status == 200:
                            data = req.data
                            print("Newer file downloaded")
                                
                            might_be_image = QImage()
                            if might_be_image.loadFromData(data) :
                                #print("URL is valid")
                                self.drawOverlay(might_be_image, entry)
                            
                                if len(self.imgList) > buffer_limit-1: #TODO: change behaviour to iteration over fixed list, to prevent jumps
                                    self.imgList.pop(1)
                                self.imgList.append(might_be_image)
                               
        finally: 
            print("Update done, closing connection")
            db_connection.close()
    
    def drawOverlay(self, image, entry):
        
        #establish painter
        painter = QPainter()
        
        #set adjustment factor
        corner_fac = 0.037
        category_fac = 0.27
        text_fac = 0.03
        
        #load images
        category = QImage("res/" + str(entry[0]) + ".png")
        upperLeft = QImage("res/upperLeft.png")
        upperRight = QImage("res/upperRight.png")
        lowerLeft = QImage("res/lowerLeft.png")
        lowerRight = QImage("res/lowerRight.png")
        
        #adjust overlays to image size
        category = category.scaledToHeight(category_fac*image.height(), Qt.SmoothTransformation)
        upperLeft = upperLeft.scaledToHeight(corner_fac*image.height(), Qt.SmoothTransformation)
        upperRight = upperRight.scaledToHeight(corner_fac*image.height(), Qt.SmoothTransformation)
        lowerLeft = lowerLeft.scaledToHeight(corner_fac*image.height(), Qt.SmoothTransformation)
        lowerRight = lowerRight.scaledToHeight(corner_fac*image.height(), Qt.SmoothTransformation)
        self.voivoifont.setPixelSize(text_fac*image.height())
        
        # create size calculator for font
        size_calculator = QFontMetrics(self.voivoifont)
        text_width = size_calculator.boundingRect(entry[3]).width()
        text_height = size_calculator.height()

        #define text-boundary
        margin_hor = 0.01*image.width()
        max_text_bound = QRect(margin_hor,image.height()-image.height()/3, image.width()-image.width()/3, image.height()/3)
        
        #format text for display
        #text_elided = size_calculator.elidedText(entry[3].upper(), Qt.ElideRight, max_text_bound.width(), Qt.TextWordWrap)
        text_upper = entry[3].upper()
        text_bounds = size_calculator.boundingRect(max_text_bound, Qt.TextWordWrap, text_upper)
        text_width = text_bounds.width()
        text_height = text_bounds.height()
        
        #calculate positions
        margin_ver = 0.018*image.height()
        #margin_hor = 0.01*image.width()
        lower_bound = image.height()-margin_ver
        upper_bound = lower_bound-lowerRight.height()-text_height-upperLeft.height()
        
        #begin painting on image
        painter.begin(image)
        
        #first paint category
        painter.drawImage(image.width()-category.width()-margin_hor, margin_ver, category)
        
        # now background rectangle and corners + comment
        if len(text_upper) > 0:
            painter.fillRect(margin_hor, upper_bound , lowerLeft.width()+text_width+lowerRight.width(), lowerLeft.height()+text_height+upperLeft.height(), QColor(qRgb(255,255,255)))
            painter.drawImage(margin_hor, lower_bound-lowerLeft.height(), lowerLeft)
            painter.drawImage(margin_hor, upper_bound, upperLeft)
            painter.drawImage(margin_hor+lowerLeft.width()+text_width, upper_bound, upperRight)
            painter.drawImage(margin_hor+lowerLeft.width()+text_width,lower_bound-lowerRight.height(), lowerRight)
            
            # write text to prepared rectangle
            painter.setPen(QColor(qRgb(17,195,159)))
            painter.setFont(self.voivoifont)
            #print(text_upper)
            painter.drawText(margin_hor+lowerLeft.width(),image.height()-lowerRight.height()-margin_ver-text_height, text_width, text_height, Qt.TextWordWrap, text_upper)                    
                                
        painter.end()
    
    
    def run(self):
        ''' run the widgets display routine '''
        
        self.timer.start(self.interval*1000)
        self.updateTimer.start(self.updateInterval*1000)
        self.nextImage()
        
    def nextImage(self):
        ''' take image from list and scale it 
        to height of parent widget
        (which should be QMainWindow). 
        Then generate a pixmap from the scaled 
        image and display it on a QLabel'''
        
        if len(self.imgList) > 0: 
            image = self.imgList[self.slideIterator%len(self.imgList)]
        else:
            return
        #print("IMAGE SIZE AFTER: " + str(image.size()))    
        currImage = QPixmap.fromImage(image.scaledToHeight(self.parentWidget().height(), Qt.SmoothTransformation))
        self.imageLabel.setPixmap(currImage)
        self.slideIterator += 1
        
    def stop(self):
        Slide.stop(self)
        self.timer.stop()

    def shutdown(self):
        qWarning("Shutting down Slideshow-Module, this will purge all loaded images! \nTo pause, use stop() instead")
        Slide.shutdown(self)
        self.timer.stop()
        self.imgList.clear()
示例#55
0
class CoverDelegate(QStyledItemDelegate): # {{{

    needs_redraw = pyqtSignal()

    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)

        self.angle = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.frame_changed)
        self.color = parent.palette().color(QPalette.WindowText)
        self.spinner_width = 64

    def frame_changed(self, *args):
        self.angle = (self.angle+30)%360
        self.needs_redraw.emit()

    def start_animation(self):
        self.angle = 0
        self.timer.start(200)

    def stop_animation(self):
        self.timer.stop()

    def draw_spinner(self, painter, rect):
        width = rect.width()

        outer_radius = (width-1)*0.5
        inner_radius = (width-1)*0.5*0.38

        capsule_height = outer_radius - inner_radius
        capsule_width  = int(capsule_height * (0.23 if width > 32 else 0.35))
        capsule_radius = capsule_width//2

        painter.save()
        painter.setRenderHint(painter.Antialiasing)

        for i in xrange(12):
            color = QColor(self.color)
            color.setAlphaF(1.0 - (i/12.0))
            painter.setPen(Qt.NoPen)
            painter.setBrush(color)
            painter.save()
            painter.translate(rect.center())
            painter.rotate(self.angle - i*30.0)
            painter.drawRoundedRect(-capsule_width*0.5,
                    -(inner_radius+capsule_height), capsule_width,
                    capsule_height, capsule_radius, capsule_radius)
            painter.restore()
        painter.restore()

    def paint(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, index)
        style = QApplication.style()
        waiting = self.timer.isActive() and index.data(Qt.UserRole).toBool()
        if waiting:
            rect = QRect(0, 0, self.spinner_width, self.spinner_width)
            rect.moveCenter(option.rect.center())
            self.draw_spinner(painter, rect)
        else:
            # Ensure the cover is rendered over any selection rect
            style.drawItemPixmap(painter, option.rect, Qt.AlignTop|Qt.AlignHCenter,
                QPixmap(index.data(Qt.DecorationRole)))
示例#56
0
文件: jobs.py 项目: 089git/calibre
class JobManager(QAbstractTableModel, SearchQueryParser):  # {{{

    job_added = pyqtSignal(int)
    job_done  = pyqtSignal(int)

    def __init__(self):
        QAbstractTableModel.__init__(self)
        SearchQueryParser.__init__(self, ['all'])

        self.wait_icon     = QVariant(QIcon(I('jobs.png')))
        self.running_icon  = QVariant(QIcon(I('exec.png')))
        self.error_icon    = QVariant(QIcon(I('dialog_error.png')))
        self.done_icon     = QVariant(QIcon(I('ok.png')))

        self.jobs          = []
        self.add_job       = Dispatcher(self._add_job)
        self.server        = Server(limit=int(config['worker_limit']/2.0),
                                enforce_cpu_limit=config['enforce_cpu_limit'])
        self.threaded_server = ThreadedJobServer()
        self.changed_queue = Queue()

        self.timer         = QTimer(self)
        self.timer.timeout.connect(self.update, type=Qt.QueuedConnection)
        self.timer.start(1000)

    def columnCount(self, parent=QModelIndex()):
        return 5

    def rowCount(self, parent=QModelIndex()):
        return len(self.jobs)

    def headerData(self, section, orientation, role):
        if role != Qt.DisplayRole:
            return NONE
        if orientation == Qt.Horizontal:
            return QVariant({
              0: _('Job'),
              1: _('Status'),
              2: _('Progress'),
              3: _('Running time'),
              4: _('Start time'),
            }.get(section, ''))
        else:
            return QVariant(section+1)

    def show_tooltip(self, arg):
        widget, pos = arg
        QToolTip.showText(pos, self.get_tooltip())

    def get_tooltip(self):
        running_jobs = [j for j in self.jobs if j.run_state == j.RUNNING]
        waiting_jobs = [j for j in self.jobs if j.run_state == j.WAITING]
        lines = [_('There are %d running jobs:')%len(running_jobs)]
        for job in running_jobs:
            desc = job.description
            if not desc:
                desc = _('Unknown job')
            p = 100. if job.is_finished else job.percent
            lines.append('%s:  %.0f%% done'%(desc, p))
        lines.extend(['', _('There are %d waiting jobs:')%len(waiting_jobs)])
        for job in waiting_jobs:
            desc = job.description
            if not desc:
                desc = _('Unknown job')
            lines.append(desc)
        return '\n'.join(['calibre', '']+ lines)

    def data(self, index, role):
        try:
            if role not in (Qt.DisplayRole, Qt.DecorationRole):
                return NONE
            row, col = index.row(), index.column()
            job = self.jobs[row]

            if role == Qt.DisplayRole:
                if col == 0:
                    desc = job.description
                    if not desc:
                        desc = _('Unknown job')
                    return QVariant(desc)
                if col == 1:
                    return QVariant(job.status_text)
                if col == 2:
                    p = 100. if job.is_finished else job.percent
                    return QVariant(p)
                if col == 3:
                    rtime = job.running_time
                    if rtime is None:
                        return NONE
                    return QVariant('%dm %ds'%(int(rtime)//60, int(rtime)%60))
                if col == 4 and job.start_time is not None:
                    return QVariant(time.strftime('%H:%M -- %d %b', time.localtime(job.start_time)))
            if role == Qt.DecorationRole and col == 0:
                state = job.run_state
                if state == job.WAITING:
                    return self.wait_icon
                if state == job.RUNNING:
                    return self.running_icon
                if job.killed or job.failed:
                    return self.error_icon
                return self.done_icon
        except:
            import traceback
            traceback.print_exc()
        return NONE

    def update(self):
        try:
            self._update()
        except BaseException:
            import traceback
            traceback.print_exc()

    def _update(self):
        # Update running time
        for i, j in enumerate(self.jobs):
            if j.run_state == j.RUNNING:
                idx = self.index(i, 3)
                self.dataChanged.emit(idx, idx)

        # Update parallel jobs
        jobs = set([])
        while True:
            try:
                jobs.add(self.server.changed_jobs_queue.get_nowait())
            except Empty:
                break

        # Update device jobs
        while True:
            try:
                jobs.add(self.changed_queue.get_nowait())
            except Empty:
                break

        # Update threaded jobs
        while True:
            try:
                jobs.add(self.threaded_server.changed_jobs.get_nowait())
            except Empty:
                break

        if jobs:
            needs_reset = False
            for job in jobs:
                orig_state = job.run_state
                job.update()
                if orig_state != job.run_state:
                    needs_reset = True
                    if job.is_finished:
                        self.job_done.emit(len(self.unfinished_jobs()))
            if needs_reset:
                self.layoutAboutToBeChanged.emit()
                self.jobs.sort()
                self.layoutChanged.emit()
            else:
                for job in jobs:
                    idx = self.jobs.index(job)
                    self.dataChanged.emit(
                        self.index(idx, 0), self.index(idx, 3))

        # Kill parallel jobs that have gone on too long
        try:
            wmax_time = gprefs['worker_max_time'] * 60
        except:
            wmax_time = 0

        if wmax_time > 0:
            for job in self.jobs:
                if isinstance(job, ParallelJob):
                    rtime = job.running_time
                    if (rtime is not None and rtime > wmax_time and
                            job.duration is None):
                        job.timed_out = True
                        self.server.kill_job(job)

    def _add_job(self, job):
        self.layoutAboutToBeChanged.emit()
        self.jobs.append(job)
        self.jobs.sort()
        self.job_added.emit(len(self.unfinished_jobs()))
        self.layoutChanged.emit()

    def done_jobs(self):
        return [j for j in self.jobs if j.is_finished]

    def unfinished_jobs(self):
        return [j for j in self.jobs if not j.is_finished]

    def row_to_job(self, row):
        return self.jobs[row]

    def has_device_jobs(self, queued_also=False):
        for job in self.jobs:
            if isinstance(job, DeviceJob):
                if job.duration is None:  # Running or waiting
                    if (job.is_running or queued_also):
                        return True
        return False

    def has_jobs(self):
        for job in self.jobs:
            if job.is_running:
                return True
        return False

    def run_job(self, done, name, args=[], kwargs={},
                           description='', core_usage=1):
        job = ParallelJob(name, description, done, args=args, kwargs=kwargs)
        job.core_usage = core_usage
        self.add_job(job)
        self.server.add_job(job)
        return job

    def run_threaded_job(self, job):
        self.add_job(job)
        self.threaded_server.add_job(job)

    def launch_gui_app(self, name, args=[], kwargs={}, description=''):
        job = ParallelJob(name, description, lambda x: x,
                args=args, kwargs=kwargs)
        self.server.run_job(job, gui=True, redirect_output=False)

    def _kill_job(self, job):
        if isinstance(job, ParallelJob):
            self.server.kill_job(job)
        elif isinstance(job, ThreadedJob):
            self.threaded_server.kill_job(job)
        else:
            job.kill_on_start = True

    def hide_jobs(self, rows):
        for r in rows:
            self.jobs[r].hidden_in_gui = True
        for r in rows:
            self.dataChanged.emit(self.index(r, 0), self.index(r, 0))

    def show_hidden_jobs(self):
        for j in self.jobs:
            j.hidden_in_gui = False
        for r in xrange(len(self.jobs)):
            self.dataChanged.emit(self.index(r, 0), self.index(r, 0))

    def kill_job(self, row, view):
        job = self.jobs[row]
        if isinstance(job, DeviceJob):
            return error_dialog(view, _('Cannot kill job'),
                         _('Cannot kill jobs that communicate with the device')).exec_()
        if job.duration is not None:
            return error_dialog(view, _('Cannot kill job'),
                         _('Job has already run')).exec_()
        if not getattr(job, 'killable', True):
            return error_dialog(view, _('Cannot kill job'),
                    _('This job cannot be stopped'), show=True)
        self._kill_job(job)

    def kill_multiple_jobs(self, rows, view):
        jobs = [self.jobs[row] for row in rows]
        devjobs = [j for j in jobs if isinstance(j, DeviceJob)]
        if devjobs:
            error_dialog(view, _('Cannot kill job'),
                         _('Cannot kill jobs that communicate with the device')).exec_()
            jobs = [j for j in jobs if not isinstance(j, DeviceJob)]
        jobs = [j for j in jobs if j.duration is None]
        unkillable = [j for j in jobs if not getattr(j, 'killable', True)]
        if unkillable:
            names = u'\n'.join(as_unicode(j.description) for j in unkillable)
            error_dialog(view, _('Cannot kill job'),
                    _('Some of the jobs cannot be stopped. Click Show details'
                        ' to see the list of unstoppable jobs.'), det_msg=names,
                    show=True)
            jobs = [j for j in jobs if getattr(j, 'killable', True)]
        jobs = [j for j in jobs if j.duration is None]
        for j in jobs:
            self._kill_job(j)

    def kill_all_jobs(self):
        for job in self.jobs:
            if (isinstance(job, DeviceJob) or job.duration is not None or
                    not getattr(job, 'killable', True)):
                continue
            self._kill_job(job)

    def terminate_all_jobs(self):
        self.server.killall()
        for job in self.jobs:
            if (isinstance(job, DeviceJob) or job.duration is not None or
                    not getattr(job, 'killable', True)):
                continue
            if not isinstance(job, ParallelJob):
                self._kill_job(job)

    def universal_set(self):
        return set([i for i, j in enumerate(self.jobs) if not getattr(j,
            'hidden_in_gui', False)])

    def get_matches(self, location, query, candidates=None):
        if candidates is None:
            candidates = self.universal_set()
        ans = set()
        if not query:
            return ans
        query = lower(query)
        for j in candidates:
            job = self.jobs[j]
            if job.description and query in lower(job.description):
                ans.add(j)
        return ans

    def find(self, query):
        query = query.strip()
        rows = self.parse(query)
        return rows