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')
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'))
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)
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'))
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)
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)
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)
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))
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
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
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
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_()
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
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_()
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)
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()
def setupTimer(self): timer = QTimer(self) timer.timeout.connect(self.count) timer.setInterval(2000) timer.start()
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
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()
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)
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
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)
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)
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'])
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>…')%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)
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
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)
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)
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())
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()
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
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
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
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)))
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
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
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()
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()
##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
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)
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()
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())
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
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
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)
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()
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)
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()
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)))
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