def qapplication(translate=True, test_time=3): """ Return QApplication instance Creates it if it doesn't already exist test_time: Time to maintain open the application when testing. It's given in seconds """ if running_in_mac_app(): SpyderApplication = MacApplication else: SpyderApplication = QApplication app = SpyderApplication.instance() if app is None: # Set Application name for Gnome 3 # https://groups.google.com/forum/#!topic/pyside/24qxvwfrRDs app = SpyderApplication(['Spyder']) # Set application name for KDE (See issue 2207) app.setApplicationName('Spyder') if translate: install_translator(app) test_travis = os.environ.get('TEST_CI_WIDGETS', None) if test_travis is not None: timer_shutdown = QTimer(app) timer_shutdown.timeout.connect(app.quit) timer_shutdown.start(test_time*1000) return app
class MatplotlibDataViewer(MatplotlibViewerMixin, DataViewer): _state_cls = MatplotlibDataViewerState tools = ['mpl:home', 'mpl:pan', 'mpl:zoom'] subtools = {'save': ['mpl:save']} def __init__(self, session, parent=None, wcs=None, state=None): super(MatplotlibDataViewer, self).__init__(session, parent=parent, state=state) # Use MplWidget to set up a Matplotlib canvas inside the Qt window self.mpl_widget = MplWidget() self.setCentralWidget(self.mpl_widget) # TODO: shouldn't have to do this self.central_widget = self.mpl_widget self.figure, self.axes = init_mpl(self.mpl_widget.canvas.fig, wcs=wcs) MatplotlibViewerMixin.setup_callbacks(self) self.central_widget.resize(600, 400) self.resize(self.central_widget.size()) self._monitor_computation = QTimer() self._monitor_computation.setInterval(500) self._monitor_computation.timeout.connect(self._update_computation) def _update_computation(self, message=None): # If we get a ComputationStartedMessage and the timer isn't currently # active, then we start the timer but we then return straight away. # This is to avoid showing the 'Computing' message straight away in the # case of reasonably fast operations. if isinstance(message, ComputationStartedMessage): if not self._monitor_computation.isActive(): self._monitor_computation.start() return for layer_artist in self.layers: if layer_artist.is_computing: self.loading_rectangle.set_visible(True) text = self.loading_text.get_text() if text.count('.') > 2: text = 'Computing' else: text += '.' self.loading_text.set_text(text) self.loading_text.set_visible(True) self.redraw() return self.loading_rectangle.set_visible(False) self.loading_text.set_visible(False) self.redraw() # If we get here, the computation has stopped so we can stop the timer self._monitor_computation.stop()
def start_video(self): timer = QTimer() self.timer = timer timer.timeout.connect(self._wait_for_frame) self.cam.start_live_video(**self.settings) timer.start(0) # Run full throttle self.is_live = True self.needs_resize = True self.videoStarted.emit()
class BaseTimerStatus(StatusBarWidget): """Status bar widget base for widgets that update based on timers.""" def __init__(self, parent, statusbar): """Status bar widget base for widgets that update based on timers.""" self.timer = None # Needs to come before parent call super(BaseTimerStatus, self).__init__(parent, statusbar) self._interval = 2000 # Widget setup fm = self.label_value.fontMetrics() self.label_value.setMinimumWidth(fm.width('000%')) # Setup if self.is_supported(): self.timer = QTimer() self.timer.timeout.connect(self.update_status) self.timer.start(self._interval) else: self.hide() def setVisible(self, value): """Override Qt method to stops timers if widget is not visible.""" if self.timer is not None: if value: self.timer.start(self._interval) else: self.timer.stop() super(BaseTimerStatus, self).setVisible(value) def set_interval(self, interval): """Set timer interval (ms).""" self._interval = interval if self.timer is not None: self.timer.setInterval(interval) def import_test(self): """Raise ImportError if feature is not supported.""" raise NotImplementedError def is_supported(self): """Return True if feature is supported.""" try: self.import_test() return True except ImportError: return False def get_value(self): """Return formatted text value.""" raise NotImplementedError def update_status(self): """Update status label widget, if widget is visible.""" if self.isVisible(): self.label_value.setText(self.get_value())
def embed(aQObject): tag = "__eventletEmbededTimer__" timer = QTimer() timer.setSingleShot(True) timer.setInterval(0.1) timer.timeout.connect(functools.partial(_timerOnTimeout, timer)) timer.start() aQObject.setProperty(tag, timer)
class DelayJobRunner(object): """ Utility class for running job after a certain delay. If a new request is made during this delay, the previous request is dropped and the timer is restarted for the new request. We use this to implement a cooldown effect that prevents jobs from being executed while the IDE is not idle. A job is a simple callable. """ def __init__(self, delay=500): """ :param delay: Delay to wait before running the job. This delay applies to all requests and cannot be changed afterwards. """ self._timer = QTimer() self.delay = delay self._timer.timeout.connect(self._exec_requested_job) self._args = [] self._kwargs = {} self._job = lambda x: None def request_job(self, job, *args, **kwargs): """ Request a job execution. The job will be executed after the delay specified in the DelayJobRunner contructor elapsed if no other job is requested until then. :param job: job. :type job: callable :param args: job's position arguments :param kwargs: job's keyworded arguments """ self.cancel_requests() self._job = job self._args = args self._kwargs = kwargs self._timer.start(self.delay) def cancel_requests(self): """Cancels pending requests.""" self._timer.stop() self._job = None self._args = None self._kwargs = None def _exec_requested_job(self): """Execute the requested job after the timer has timeout.""" self._timer.stop() self._job(*self._args, **self._kwargs)
def open_in_window(widget_name, script): """ Displays a widget in a window. :param widget_name: A qualified name of a widget, ie mantidqt.mywidget.MyWidget :param script: A qualified name of a test function that can be run after the widget is created. The test function must have the signature: def test(widget): ... where argument widget is an instance of the tested widget. The test function can yield from time to time after which the widget can update itself. This will make the test non-blocking and changes can be viewed as the script runs. If the test yields an integer it is interpreted as the number of seconds to wait until the next step. """ raw_input('Please attach the Debugger now if required. Press any key to continue') setup_library_paths() app = QApplication([""]) w = create_widget(widget_name) w.setWindowTitle(widget_name) w.show() if script is not None: try: # If script is a generator script_iter allows non-blocking # test execution script_iter = iter(run_script(script, w)) pause_timer = QTimer() pause_timer.setSingleShot(True) def idle(): if not pause_timer.isActive(): try: # Run test script until the next 'yield' pause_sec = script_iter.next() if pause_sec is not None: # Start non-blocking pause in seconds pause_timer.start(int(pause_sec * 1000)) except StopIteration: pass except: traceback.print_exc() timer = QTimer() # Zero-timeout timer runs idle() between Qt events timer.timeout.connect(idle) timer.start() except: pass sys.exit(app.exec_())
def setup(self, icon_painter, painter, rect): if self.parent_widget not in self.info: timer = QTimer() timer.timeout.connect(lambda: self._update(self.parent_widget)) self.info[self.parent_widget] = [timer, 0, self.step] timer.start(self.interval) else: timer, angle, self.step = self.info[self.parent_widget] x_center = rect.width() * 0.5 y_center = rect.height() * 0.5 painter.translate(x_center, y_center) painter.rotate(angle) painter.translate(-x_center, -y_center)
class ConnectionInspector(QWidget): def __init__(self, parent=None): super(ConnectionInspector, self).__init__(parent, Qt.Window) connections = self.fetch_data() self.table_view = ConnectionTableView(connections, self) self.setLayout(QVBoxLayout(self)) self.layout().addWidget(self.table_view) button_layout = QHBoxLayout() self.layout().addItem(button_layout) self.save_status_label = QLabel(self) button_layout.addWidget(self.save_status_label) button_layout.addStretch() self.save_button = QPushButton(self) self.save_button.setText("Save list to file...") self.save_button.clicked.connect(self.save_list_to_file) button_layout.addWidget(self.save_button) self.update_timer = QTimer(parent=self) self.update_timer.setInterval(1500) self.update_timer.timeout.connect(self.update_data) self.update_timer.start() def update_data(self): self.table_view.model().connections = self.fetch_data() def fetch_data(self): plugins = data_plugins.plugin_modules return [connection for p in plugins.values() for connection in p.connections.values() ] @Slot() def save_list_to_file(self): filename, filters = QFileDialog.getSaveFileName(self, "Save connection list", "", "Text Files (*.txt)") try: with open(filename, "w") as f: for conn in self.table_view.model().connections: f.write( "{p}://{a}\n".format(p=conn.protocol, a=conn.address)) self.save_status_label.setText("File saved to {}".format(filename)) except Exception as e: msgBox = QMessageBox() msgBox.setText("Couldn't save connection list to file.") msgBox.setInformativeText("Error: {}".format(str(e))) msgBox.setStandardButtons(QMessageBox.Ok) msgBox.exec_()
def setup(): qapp = QApplication(sys.argv) # qapp.setGraphicsSystem('native') qapp.setWindowIcon(QIcon(os.path.join(ICON_PATH, 'application', 'icon.png'))) #http://stackoverflow.com/questions/4938723/what-is-the-correct-way-to-make-my-pyqt-application-quit-when-killed-from-the-co timer = QTimer() timer.start(500) # You may change this if you wish. timer.timeout.connect(lambda: None) # Let the interpreter run each 500 ms. app = App(sys.argv) app.viewer.main_window.show() return qapp, app
def qapplication(translate=True, test_time=3): """Return QApplication instance Creates it if it doesn't already exist""" app = QApplication.instance() if app is None: app = QApplication(['Conda-Manager']) app.setApplicationName('Conda-Manager') if translate: install_translator(app) test_travis = os.environ.get('TEST_CI', None) if test_travis is not None: timer_shutdown = QTimer(app) timer_shutdown.timeout.connect(app.quit) timer_shutdown.start(test_time*1000) return app
class BaseTimerStatus(StatusBarWidget): TITLE = None TIP = None def __init__(self, parent, statusbar): StatusBarWidget.__init__(self, parent, statusbar) self.setToolTip(self.TIP) layout = self.layout() layout.addWidget(QLabel(self.TITLE)) self.label = QLabel() self.label.setFont(self.label_font) layout.addWidget(self.label) layout.addSpacing(20) if self.is_supported(): self.timer = QTimer() self.timer.timeout.connect(self.update_label) self.timer.start(2000) else: self.timer = None self.hide() def set_interval(self, interval): """Set timer interval (ms)""" if self.timer is not None: self.timer.setInterval(interval) def import_test(self): """Raise ImportError if feature is not supported""" raise NotImplementedError def is_supported(self): """Return True if feature is supported""" try: self.import_test() return True except ImportError: return False def get_value(self): """Return value (e.g. CPU or memory usage)""" raise NotImplementedError def update_label(self): """Update status label widget, if widget is visible""" if self.isVisible(): self.label.setText('%d %%' % self.get_value())
def test_invalid_directories(qtbot, pathmanager): """Check [site/dist]-packages are invalid paths.""" if os.name == 'nt': paths = ['/lib/site-packages/foo', '/lib/dist-packages/foo'] else: paths = [ '/lib/python3.6/site-packages/foo', '/lib/python3.6/dist-packages/foo' ] def interact_message_box(): child = pathmanager.findChild(QMessageBox) qtbot.keyPress(child, Qt.Key_Enter) for path in paths: timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(interact_message_box) timer.start(300) assert not pathmanager.check_path(path) pathmanager.add_path(path)
class FPSLabel(QLabel): """This QLabel can show FPS of main canvas in status bar.""" def __init__(self, parent: QWidget): super(FPSLabel, self).__init__(parent) self.__t0 = process_time() self.__frame_timer = QTimer() self.__frame_timer.timeout.connect(self.__update_text) self.__frame_timer.start(1000) @Slot() def __update_text(self) -> None: """Update FPS with timer.""" t1 = process_time() - self.__t0 fps = 1 / t1 if t1 else 1 self.setText(f"FPS: {fps:6.02f}") @Slot() def update_text(self) -> None: """Update FPS with timer.""" self.__update_text() self.__t0 = process_time()
def test_remove_item_and_reply_no(qtbot, pathmanager): """Check that the item is not removed after answering 'No'.""" pathmanager.show() count = pathmanager.count() def interact_message_box(): messagebox = pathmanager.findChild(QMessageBox) buttons = messagebox.findChildren(QPushButton) for button in buttons: if 'no' in button.text().lower(): qtbot.mouseClick(button, Qt.LeftButton) break timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(interact_message_box) timer.start(100) qtbot.mouseClick(pathmanager.remove_button, Qt.LeftButton) # Back to main thread assert pathmanager.count() == count
class Connection(PyDMConnection): def __init__(self, widget, address, protocol=None, parent=None): super(Connection, self).__init__(widget, address, protocol, parent) self.add_listener(widget) self.value = address self.rand = 0 self.timer = QTimer(self) self.timer.timeout.connect(self.send_new_value) self.timer.start(1000) self.connected = True def send_new_value(self): val_to_send = "{0}-{1}".format(self.value, random.randint(0, 9)) self.new_value_signal[str].emit(str(val_to_send)) def send_connection_state(self, conn): self.connection_state_signal.emit(conn) def add_listener(self, widget): super(Connection, self).add_listener(widget) self.send_connection_state(True)
def create_app(datafiles=[], interactive=True): app = get_qapp() if interactive: # Splash screen splash = get_splash() splash.image = QtGui.QPixmap(MOSVIZ_SPLASH_PATH) splash.show() else: splash = None # Start off by loading plugins. We need to do this before restoring # the session or loading the configuration since these may use existing # plugins. load_plugins(splash=splash) # # Show the splash screen for 2 seconds if interactive: timer = QTimer() timer.setInterval(2000) timer.setSingleShot(True) timer.timeout.connect(splash.close) timer.start() data_collection = glue.core.DataCollection() hub = data_collection.hub if interactive: splash.set_progress(100) ga = _create_glue_app(data_collection, hub) ga.run_startup_action('mosviz') # Load the data files. if datafiles: datasets = load_data_files(datafiles) ga.add_datasets(data_collection, datasets, auto_merge=False) return ga
class BarGraph(PlotWidget): """Bar Graph.""" def __init__(self, channels=list(), xLabels=list(), yLabel='', title=''): """Init.""" super().__init__() self._channels = list() for chn in channels: self._channels.append(_ConnSignal(chn)) self._xLabels = xLabels self._yLabel = yLabel self.showGrid(x=True, y=True) self.setBackground('w') self.setXRange(min=-0.5, max=len(xLabels)-0.5) self.setTitle(title) self.setLabel('left', text=self._yLabel) self.getAxis('left').setStyle( autoExpandTextSpace=False, tickTextWidth=30) self.getAxis('bottom').setTicks( [[(i, l) for i, l in enumerate(self._xLabels)]]) self._baritems = dict() for idx, lbl in enumerate(self._xLabels): baritem = BarGraphItem( x=[idx, ], width=1, height=0, brush='b') self.addItem(baritem) self._baritems[idx] = baritem self._timer = QTimer() self._timer.timeout.connect(self._update_graph) self._timer.setInterval(500) # ms self._timer.start() def _update_graph(self): wave = list() for idx, chn in enumerate(self._channels): value = chn.value if chn.value is not None else 0 wave.append(value) self._baritems[idx].setOpts(height=value)
class ApplicationBackend(BaseApplicationBackend): _app: QApplication def _mgui_get_backend_name(self): return "qt" def _mgui_process_events(self): app = self._mgui_get_native_app() app.flush() app.processEvents() def _mgui_run(self): app = self._mgui_get_native_app() # only start the event loop if magicgui created it if app.applicationName() == APPLICATION_NAME: return app.exec_() def _mgui_quit(self): return self._mgui_get_native_app().quit() def _mgui_get_native_app(self): # Get native app self._app = QApplication.instance() if not self._app: QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) self._app = QApplication(sys.argv) self._app.setApplicationName(APPLICATION_NAME) return self._app def _mgui_start_timer(self, interval=0, on_timeout=None, single=False): self._timer = QTimer() if on_timeout: self._timer.timeout.connect(on_timeout) self._timer.setSingleShot(single) self._timer.setInterval(interval) self._timer.start() def _mgui_stop_timer(self): if getattr(self, "_timer", None): self._timer.stop()
class GEventProcessing: """Interoperability class between Qt/gevent that allows processing gevent tasks during Qt idle periods.""" def __init__(self, idle_period=0.010): # Limit the IDLE handler's frequency while still allow for gevent # to trigger a microthread anytime self._idle_period = idle_period # IDLE timer: on_idle is called whenever no Qt events left for # processing self._timer = QTimer() self._timer.timeout.connect(self.process_events) self._timer.start(0) def __enter__(self): pass def __exit__(self, *exc_info): self._timer.stop() def process_events(self): # Cooperative yield, allow gevent to monitor file handles via libevent gevent.sleep(self._idle_period)
def __init__(self): """Initialise the whole program.""" super().__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.positioners.addItems(positioners.keys()) self.used_positioners = [] self.connect_views() # Make a dummy meristem with random buds in different colours self.meristem = BudFronter() # set the OpenGL canvas up with the meristem self.ui.mainCanvas.objects = self.meristem self.ui.flatStem.objects = self.meristem self.ui.flatStem.show() # Set a timer to refresh the OpenGL screen every 20ms (50fps) timer = QTimer(self) timer.timeout.connect(self.ui.mainCanvas.update) timer.start(20)
def handle_lsp_down(self, language): """ Handle automatic restart of client/server on failure. """ if (not self.clients_restarting.get(language, False) and not running_under_pytest()): try: self.clients_hearbeat[language].stop() except KeyError: pass logger.info("Automatic restart for {}...".format(language)) timer = QTimer(self) timer.setSingleShot(False) timer.setInterval(self.TIME_BETWEEN_RESTARTS) timer.timeout.connect(lambda: self.restart_lsp(language)) self.set_status(language, _('restarting...')) self.clients_restarting[language] = True self.clients_restart_count[language] = self.MAX_RESTART_ATTEMPTS self.clients_restart_timers[language] = timer timer.start()
class ScalarWidget(QLCDNumber): def __init__(self, name, topic, addr, parent=None): super(__class__, self).__init__(parent) self.name = name self.topic = topic self.timer = QTimer() self.setGeometry(QRect(320, 180, 191, 81)) self.setDigitCount(10) self.setObjectName(topic) self.comm_handler = AsyncGraphCommHandler(addr.name, addr.comm) self.timer.timeout.connect(self.get_scalar) self.timer.start(1000) @asyncqt.asyncSlot() async def get_scalar(self): reply = await self.comm_handler.fetch(self.topic) if reply is not None: self.scalar_updated(reply) else: logger.warn("failed to fetch %s from manager!", self.topic) def scalar_updated(self, data): self.display(data)
def start_symbol_services(self, language): """Show symbols for all `language` files.""" # Save all languages that can send info to this pane. self._languages.append(language) # Update all files associated to `language` through a timer # that allows to wait a bit between updates. That doesn't block # the interface at startup. timer = QTimer(self) timer.setSingleShot(True) timer.setInterval(700) timer.timeout.connect(lambda: self.update_editors(language)) self.update_timers[language] = timer # Select editors to update per language to_update = [] for editor in self.editor_ids.keys(): if editor.get_language().lower() == language: to_update.append(editor) self.editors_to_update[language] = to_update # Start timer timer.start()
class TimerThread(QObject): signal = Signal(str) def __init__(self, callback, milis): super().__init__() self.milis = milis self.signal.connect(callback) self.thread = QThread() self.timer = QTimer() #self.timer.timeout.connect(self.timeout) #self.timer.start(milis) self.thread.started.connect(self.init) def Start(self): self.thread.start() @Slot() def init(self): self.timer.timeout.connect(self.timeout) self.timer.start(self.milis) def timeout(self): self.signal.emit("tick")
class QZmqNode(QObject, zmqNode): def __init__(self, **kwargs): super().__init__(**kwargs) def listen_all(self): for connection in list(self.connections.keys()): self.listen(name=connection) if self.listening: self.timer_listen = QTimer() self.timer_listen.setInterval(int(self.heart_beat * 1000)) self.timer_listen.setSingleShot(True) self.timer_listen.timeout.connect(self.listen_all, Qt.QueuedConnection) self.timer_listen.start() elif self.timer_listen: self.timer_listen.stop() self.timer_listen = None def listen(self, name=''): super(QZmqNode, self).listen(name) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) def disconnect(self, name=''): zmqNode.disconnect(self, name)
def check_updates(self, startup=False): """Check for spyder updates on github releases using a QThread.""" # Disable check_updates_action while the thread is working self.check_updates_action.setDisabled(True) if self.thread_updates is not None: self.thread_updates.terminate() self.thread_updates = QThread(self) self.worker_updates = WorkerUpdates(self, startup=startup) self.worker_updates.sig_ready.connect(self._check_updates_ready) self.worker_updates.sig_ready.connect(self.thread_updates.quit) self.worker_updates.moveToThread(self.thread_updates) self.thread_updates.started.connect(self.worker_updates.start) # Delay starting this check to avoid blocking the main window # while loading. # Fixes spyder-ide/spyder#15839 updates_timer = QTimer(self) updates_timer.setInterval(3000) updates_timer.setSingleShot(True) updates_timer.timeout.connect(self.thread_updates.start) updates_timer.start()
def qapplication(translate=True, test_time=3): """ Return QApplication instance Creates it if it doesn't already exist test_time: Time to maintain open the application when testing. It's given in seconds """ if sys.platform == "darwin": SpyderApplication = MacApplication else: SpyderApplication = QApplication app = SpyderApplication.instance() if app is None: # Set Application name for Gnome 3 # https://groups.google.com/forum/#!topic/pyside/24qxvwfrRDs app = SpyderApplication(['Spyder']) # Set application name for KDE. See spyder-ide/spyder#2207. app.setApplicationName('Spyder') if (sys.platform == "darwin" and not running_in_mac_app() and CONF.get('main', 'mac_open_file', False)): # Register app if setting is set register_app_launchservices() if translate: install_translator(app) test_ci = os.environ.get('TEST_CI_WIDGETS', None) if test_ci is not None: timer_shutdown = QTimer(app) timer_shutdown.timeout.connect(app.quit) timer_shutdown.start(test_time * 1000) return app
def hs_napari(self, dataset): with napari.gui_qt() as app: viewer = napari.Viewer() self.viewer = viewer self.app = app self.update_viewer(dataset) start = time.time() # timer for exiting napari timer = QTimer() timer.timeout.connect(self.quit) timer.start(1000*1) @viewer.bind_key('x') def crop(viewer): if 'Shapes' in viewer.layers: bound_box = np.array(viewer.layers['Shapes'].data).squeeze() else: bound_box = np.array(False) if bound_box.shape[0] == 4: #crop full dataset self.crop_section(bound_box) #save current selection selection = {} for d in self.im.dims: if d not in ['row', 'col']: if d in dataset.dims: selection[d] = dataset[d] else: selection[d] = dataset.coords[d].values # update viewer cropped = self.im.sel(selection) self.update_viewer(cropped)
class StartDialog(QDialog): def __init__(self, parent): super(StartDialog, self).__init__() # Call the inherited classes __init__ method #super().__init__(parent) uic.loadUi(_ST_DLG, self) self.hideText() self.index = 0 self.labels = [ self.label01, self.label02, self.label03, self.label04, self.label05, self.label06 ] self.timer = QTimer() self.timer.timeout.connect(self.serialText) self.timer.start(1060) self.setWindowModality(Qt.ApplicationModal) self.exec_() @Slot() def on_ok_clicked(self): self.timer.stop() self.close() def hideText(self): self.label01.hide() self.label02.hide() self.label03.hide() self.label04.hide() self.label05.hide() self.label06.hide() def serialText(self): self.labels[self.index].show() if self.index < 5: self.index += 1 else: self.timer.stop()
def _main(args, exec=True): global mainwindow, splash_proc, show_check_timer # import pydm # app = QApplication([]) # app = pydm.PyDMApplication() app = QApplication.instance() or QApplication([]) signal.signal(signal.SIGINT, signal.SIG_DFL) from xicam.gui.windows import splash from xicam.core import msg if getattr(args, 'verbose', False): QErrorMessage.qtHandler() # start splash in subprocess splash_proc = QProcess() # splash_proc.started.connect(lambda: print('started splash')) # splash_proc.finished.connect(lambda: print('finished splashing')) log_file = msg.file_handler.baseFilename initial_length = os.path.getsize(log_file) splash_proc.start(sys.executable, [splash.__file__, log_file, str(initial_length)]) show_check_timer = QTimer() show_check_timer.timeout.connect(check_show_mainwindow) show_check_timer.start(100) from xicam.gui.windows.mainwindow import XicamMainWindow mainwindow = XicamMainWindow() if exec: return app.exec_() else: return mainwindow
def _highlight(self): num = self.component_selector.value labels = self.labels_layer.value bound_info = self.roi_info.bound_info.get(num, None) if bound_info is None: self._stop() return slices = bound_info.get_slices() component_mark = self.roi_info.roi[tuple(slices)] == num translate_grid = labels.translate + bound_info.lower * labels.scale if ".Highlight" in self.napari_viewer.layers: self.napari_viewer.layers[".Highlight"].data = component_mark else: layer = self.napari_viewer.add_labels( component_mark, name=".Highlight", scale=labels.scale, blending="translucent", color={0: (0, 0, 0, 0), 1: "white"}, opacity=0.7, ) def flash_fun(layer_=layer): opacity = layer_.opacity + 0.1 if opacity > 1: opacity = 0.1 layer_.opacity = opacity timer = QTimer() timer.setInterval(100) timer.timeout.connect(flash_fun) timer.start() layer.metadata["timer"] = timer self.napari_viewer.layers[".Highlight"].translate = translate_grid self._shift_if_need(labels, bound_info)
def __init__(self, callback=default_message_callback): super(vmpwMidiWidget, self).__init__() self.setWindowTitle('vmpw Midi Reader') self.setLayout(QVBoxLayout(self)) self.refreshButton = QPushButton("List Midi In Ports") self.refreshButton.clicked.connect(self.listMidiIns) self.layout().addWidget(self.refreshButton) self.readButton = QPushButton("Read Midi Messages") self.layout().addWidget(self.readButton) self.ins = midiutil.rtmidi.MidiIn( rtmidi.midiutil.get_api_from_environment()) self.ins.set_error_callback(midi_error) self.inport = None self.inputButtons = [] _i = 0 for port in self.listMidiIns(): button = QPushButton(str(port)) button.clicked.connect(lambda: self.set_input(1)) _i = _i + 1 self.layout().addWidget(button) self.inputButtons.append(button) self.i = None #Monitor self.messageBox = QLabel('') self.layout().addWidget(self.messageBox) timer = QTimer(self) timer.setInterval(1) timer.timeout.connect(self.getmessages) timerID = timer.start(1000 // 60) self.timer = timer self.callback = callback self.show()
class _CondaAPI(QObject): """ """ ROOT_PREFIX = None ENCODING = 'ascii' UTF8 = 'utf-8' DEFAULT_CHANNELS = ['https://repo.continuum.io/pkgs/pro', 'https://repo.continuum.io/pkgs/free'] def __init__(self, parent=None): super(_CondaAPI, self).__init__() self._parent = parent self._queue = deque() self._timer = QTimer() self._current_worker = None self._workers = [] self._timer.setInterval(1000) self._timer.timeout.connect(self._clean) self.set_root_prefix() def _clean(self): """ Periodically check for inactive workers and remove their references. """ if self._workers: for w in self._workers: if w.is_finished(): self._workers.remove(w) else: self._current_worker = None self._timer.stop() def _start(self): """ """ if len(self._queue) == 1: self._current_worker = self._queue.popleft() self._workers.append(self._current_worker) self._current_worker.start() self._timer.start() def is_active(self): """ Check if a worker is still active. """ return len(self._workers) == 0 def terminate_all_processes(self): """ Kill all working processes. """ for worker in self._workers: worker.close() # --- Conda api # ------------------------------------------------------------------------- def _call_conda(self, extra_args, abspath=True, parse=False, callback=None): """ Call conda with the list of extra arguments, and return the worker. The result can be force by calling worker.communicate(), which returns the tuple (stdout, stderr). """ if abspath: if sys.platform == 'win32': python = join(self.ROOT_PREFIX, 'python.exe') conda = join(self.ROOT_PREFIX, 'Scripts', 'conda-script.py') else: python = join(self.ROOT_PREFIX, 'bin/python') conda = join(self.ROOT_PREFIX, 'bin/conda') cmd_list = [python, conda] else: # Just use whatever conda is on the path cmd_list = ['conda'] cmd_list.extend(extra_args) process_worker = ProcessWorker(cmd_list, parse=parse, callback=callback) process_worker.sig_finished.connect(self._start) self._queue.append(process_worker) self._start() return process_worker def _call_and_parse(self, extra_args, abspath=True, callback=None): """ """ return self._call_conda(extra_args, abspath=abspath, parse=True, callback=callback) def _setup_install_commands_from_kwargs(self, kwargs, keys=tuple()): cmd_list = [] if kwargs.get('override_channels', False) and 'channel' not in kwargs: raise TypeError('conda search: override_channels requires channel') if 'env' in kwargs: cmd_list.extend(['--name', kwargs.pop('env')]) if 'prefix' in kwargs: cmd_list.extend(['--prefix', kwargs.pop('prefix')]) if 'channel' in kwargs: channel = kwargs.pop('channel') if isinstance(channel, str): cmd_list.extend(['--channel', channel]) else: cmd_list.append('--channel') cmd_list.extend(channel) for key in keys: if key in kwargs and kwargs[key]: cmd_list.append('--' + key.replace('_', '-')) return cmd_list def set_root_prefix(self, prefix=None): """ Set the prefix to the root environment (default is /opt/anaconda). This function should only be called once (right after importing conda_api). """ if prefix: self.ROOT_PREFIX = prefix else: # Find some conda instance, and then use info to get 'root_prefix' worker = self._call_and_parse(['info', '--json'], abspath=False) info = worker.communicate()[0] self.ROOT_PREFIX = info['root_prefix'] def get_conda_version(self): """ Return the version of conda being used (invoked) as a string. """ return self._call_conda(['--version'], callback=self._get_conda_version) def _get_conda_version(self, stdout, stderr): # argparse outputs version to stderr in Python < 3.4. # http://bugs.python.org/issue18920 pat = re.compile(r'conda:?\s+(\d+\.\d\S+|unknown)') m = pat.match(stderr.decode().strip()) if m is None: m = pat.match(stdout.decode().strip()) if m is None: raise Exception('output did not match: {0}'.format(stderr)) return m.group(1) def get_envs(self): """ Return all of the (named) environment (this does not include the root environment), as a list of absolute path to their prefixes. """ logger.debug('') # return self._call_and_parse(['info', '--json'], # callback=lambda o, e: o['envs']) envs = os.listdir(os.sep.join([self.ROOT_PREFIX, 'envs'])) envs = [os.sep.join([self.ROOT_PREFIX, 'envs', i]) for i in envs] valid_envs = [e for e in envs if os.path.isdir(e) and self.environment_exists(prefix=e)] return valid_envs def get_prefix_envname(self, name): """ Given the name of an environment return its full prefix path, or None if it cannot be found. """ prefix = None if name == 'root': prefix = self.ROOT_PREFIX # envs, error = self.get_envs().communicate() envs = self.get_envs() for p in envs: if basename(p) == name: prefix = p return prefix def linked(self, prefix): """ Return the (set of canonical names) of linked packages in `prefix`. """ logger.debug(str(prefix)) if not isdir(prefix): raise Exception('no such directory: {0}'.format(prefix)) meta_dir = join(prefix, 'conda-meta') if not isdir(meta_dir): # We might have nothing in linked (and no conda-meta directory) return set() return set(fn[:-5] for fn in os.listdir(meta_dir) if fn.endswith('.json')) def split_canonical_name(self, cname): """ Split a canonical package name into (name, version, build) strings. """ return tuple(cname.rsplit('-', 2)) def info(self, abspath=True): """ Return a dictionary with configuration information. No guarantee is made about which keys exist. Therefore this function should only be used for testing and debugging. """ logger.debug(str('')) return self._call_and_parse(['info', '--json'], abspath=abspath) def package_info(self, package, abspath=True): """ Return a dictionary with package information. """ return self._call_and_parse(['info', package, '--json'], abspath=abspath) def search(self, regex=None, spec=None, **kwargs): """ Search for packages. """ cmd_list = ['search', '--json'] if regex and spec: raise TypeError('conda search: only one of regex or spec allowed') if regex: cmd_list.append(regex) if spec: cmd_list.extend(['--spec', spec]) if 'platform' in kwargs: cmd_list.extend(['--platform', kwargs.pop('platform')]) cmd_list.extend( self._setup_install_commands_from_kwargs( kwargs, ('canonical', 'unknown', 'use_index_cache', 'outdated', 'override_channels'))) return self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True)) def create(self, name=None, prefix=None, pkgs=None, channels=None): """ Create an environment either by name or path with a specified set of packages. """ logger.debug(str((prefix, pkgs, channels))) # TODO: Fix temporal hack if not pkgs or not isinstance(pkgs, (list, tuple, str)): raise TypeError('must specify a list of one or more packages to ' 'install into new environment') cmd_list = ['create', '--yes', '--quiet', '--json', '--mkdir'] if name: ref = name search = [os.path.join(d, name) for d in self.info().communicate()[0]['envs_dirs']] cmd_list.extend(['--name', name]) elif prefix: ref = prefix search = [prefix] cmd_list.extend(['--prefix', prefix]) else: raise TypeError('must specify either an environment name or a ' 'path for new environment') if any(os.path.exists(prefix) for prefix in search): raise CondaEnvExistsError('Conda environment {0} already ' 'exists'.format(ref)) # TODO: Fix temporal hack if isinstance(pkgs, (list, tuple)): cmd_list.extend(pkgs) elif isinstance(pkgs, str): cmd_list.extend(['--file', pkgs]) # TODO: Check if correct if channels: cmd_list.extend(['--override-channels']) for channel in channels: cmd_list.extend(['--channel']) cmd_list.extend([channel]) return self._call_and_parse(cmd_list) def parse_token_channel(self, channel, token): """ Adapt a channel to include the authentication token of the logged user. Ignore default channels """ if token and channel not in self.DEFAULT_CHANNELS: url_parts = channel.split('/') start = url_parts[:-1] middle = 't/{0}'.format(token) end = url_parts[-1] token_channel = '{0}/{1}/{2}'.format('/'.join(start), middle, end) return token_channel else: return channel def install(self, name=None, prefix=None, pkgs=None, dep=True, channels=None, token=None): """ Install packages into an environment either by name or path with a specified set of packages. If token is specified, the channels different from the defaults will get the token appended. """ logger.debug(str((prefix, pkgs, channels))) # TODO: Fix temporal hack if not pkgs or not isinstance(pkgs, (list, tuple, str)): raise TypeError('must specify a list of one or more packages to ' 'install into existing environment') cmd_list = ['install', '--yes', '--json', '--force-pscheck'] if name: cmd_list.extend(['--name', name]) elif prefix: cmd_list.extend(['--prefix', prefix]) else: # Just install into the current environment, whatever that is pass # TODO: Check if correct if channels: cmd_list.extend(['--override-channels']) for channel in channels: cmd_list.extend(['--channel']) channel = self.parse_token_channel(channel, token) cmd_list.extend([channel]) # TODO: Fix temporal hack if isinstance(pkgs, (list, tuple)): cmd_list.extend(pkgs) elif isinstance(pkgs, str): cmd_list.extend(['--file', pkgs]) if not dep: cmd_list.extend(['--no-deps']) return self._call_and_parse(cmd_list) def update(self, *pkgs, **kwargs): """ Update package(s) (in an environment) by name. """ cmd_list = ['update', '--json', '--quiet', '--yes'] if not pkgs and not kwargs.get('all'): raise TypeError("Must specify at least one package to update, or " "all=True.") cmd_list.extend( self._setup_install_commands_from_kwargs( kwargs, ('dry_run', 'no_deps', 'override_channels', 'no_pin', 'force', 'all', 'use_index_cache', 'use_local', 'alt_hint'))) cmd_list.extend(pkgs) return self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True)) def remove(self, name=None, prefix=None, pkgs=None, all_=False): """ Remove a package (from an environment) by name. Returns { success: bool, (this is always true), (other information) } """ logger.debug(str((prefix, pkgs))) cmd_list = ['remove', '--json', '--quiet', '--yes'] if not pkgs and not all_: raise TypeError("Must specify at least one package to remove, or " "all=True.") if name: cmd_list.extend(['--name', name]) elif prefix: cmd_list.extend(['--prefix', prefix]) else: raise TypeError('must specify either an environment name or a ' 'path for package removal') if all_: cmd_list.extend(['--all']) else: cmd_list.extend(pkgs) return self._call_and_parse(cmd_list) def remove_environment(self, name=None, path=None, **kwargs): """ Remove an environment entirely. See ``remove``. """ return self.remove(name=name, path=path, all=True, **kwargs) def clone_environment(self, clone, name=None, prefix=None, **kwargs): """ Clone the environment `clone` into `name` or `prefix`. """ cmd_list = ['create', '--json', '--quiet'] if (name and prefix) or not (name or prefix): raise TypeError("conda clone_environment: exactly one of `name` " "or `path` required") if name: cmd_list.extend(['--name', name]) if prefix: cmd_list.extend(['--prefix', prefix]) cmd_list.extend(['--clone', clone]) cmd_list.extend( self._setup_install_commands_from_kwargs( kwargs, ('dry_run', 'unknown', 'use_index_cache', 'use_local', 'no_pin', 'force', 'all', 'channel', 'override_channels', 'no_default_packages'))) return self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True)) # FIXME: def process(self, name=None, prefix=None, cmd=None): """ Create a Popen process for cmd using the specified args but in the conda environment specified by name or prefix. The returned object will need to be invoked with p.communicate() or similar. """ if bool(name) == bool(prefix): raise TypeError('exactly one of name or prefix must be specified') if not cmd: raise TypeError('cmd to execute must be specified') if not args: args = [] if name: prefix = self.get_prefix_envname(name) conda_env = dict(os.environ) sep = os.pathsep if sys.platform == 'win32': conda_env['PATH'] = join(prefix, 'Scripts') + sep + conda_env['PATH'] else: # Unix conda_env['PATH'] = join(prefix, 'bin') + sep + conda_env['PATH'] conda_env['PATH'] = prefix + os.pathsep + conda_env['PATH'] cmd_list = [cmd] cmd_list.extend(args) # = self.subprocess.process(cmd_list, env=conda_env, stdin=stdin, # stdout=stdout, stderr=stderr) def _setup_config_from_kwargs(self, kwargs): cmd_list = ['--json', '--force'] if 'file' in kwargs: cmd_list.extend(['--file', kwargs['file']]) if 'system' in kwargs: cmd_list.append('--system') return cmd_list def config_path(self, **kwargs): """ Get the path to the config file. """ cmd_list = ['config', '--get'] cmd_list.extend(self._setup_config_from_kwargs(kwargs)) return self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True), callback=lambda o, e: o['rc_path']) def config_get(self, *keys, **kwargs): """ Get the values of configuration keys. Returns a dictionary of values. Note, the key may not be in the dictionary if the key wasn't set in the configuration file. """ cmd_list = ['config', '--get'] cmd_list.extend(keys) cmd_list.extend(self._setup_config_from_kwargs(kwargs)) return self._call_and_parse(cmd_list, abspath=kwargs.get('abspath', True), callback=lambda o, e: o['get']) def config_set(self, key, value, **kwargs): """ Set a key to a (bool) value. Returns a list of warnings Conda may have emitted. """ cmd_list = ['config', '--set', key, str(value)] cmd_list.extend(self._setup_config_from_kwargs(kwargs)) return self._call_and_parse( cmd_list, abspath=kwargs.get('abspath', True), callback=lambda o, e: o.get('warnings', [])) def config_add(self, key, value, **kwargs): """ Add a value to a key. Returns a list of warnings Conda may have emitted. """ cmd_list = ['config', '--add', key, value] cmd_list.extend(self._setup_config_from_kwargs(kwargs)) return self._call_and_parse( cmd_list, abspath=kwargs.get('abspath', True), callback=lambda o, e: o.get('warnings', [])) def config_remove(self, key, value, **kwargs): """ Remove a value from a key. Returns a list of warnings Conda may have emitted. """ cmd_list = ['config', '--remove', key, value] cmd_list.extend(self._setup_config_from_kwargs(kwargs)) return self._call_and_parse( cmd_list, abspath=kwargs.get('abspath', True), callback=lambda o, e: o.get('warnings', [])) def config_delete(self, key, **kwargs): """ Remove a key entirely. Returns a list of warnings Conda may have emitted. """ cmd_list = ['config', '--remove-key', key] cmd_list.extend(self._setup_config_from_kwargs(kwargs)) return self._call_and_parse( cmd_list, abspath=kwargs.get('abspath', True), callback=lambda o, e: o.get('warnings', [])) def run(self, command, abspath=True): """ Launch the specified app by name or full package name. Returns a dictionary containing the key "fn", whose value is the full package (ending in ``.tar.bz2``) of the app. """ cmd_list = ['run', '--json', command] return self._call_and_parse(cmd_list, abspath=abspath) # --- Additional methods # ----------------------------------------------------------------------------- def dependencies(self, name=None, prefix=None, pkgs=None, channels=None, dep=True): """ Get dependenciy list for packages to be installed into an environment defined either by 'name' or 'prefix'. """ if not pkgs or not isinstance(pkgs, (list, tuple)): raise TypeError('must specify a list of one or more packages to ' 'install into existing environment') cmd_list = ['install', '--dry-run', '--json', '--force-pscheck'] if not dep: cmd_list.extend(['--no-deps']) if name: cmd_list.extend(['--name', name]) elif prefix: cmd_list.extend(['--prefix', prefix]) else: pass cmd_list.extend(pkgs) # TODO: Check if correct if channels: cmd_list.extend(['--override-channels']) for channel in channels: cmd_list.extend(['--channel']) cmd_list.extend([channel]) return self._call_and_parse(cmd_list) def environment_exists(self, name=None, prefix=None, abspath=True): """ Check if an environment exists by 'name' or by 'prefix'. If query is by 'name' only the default conda environments directory is searched. """ logger.debug(str((name, prefix))) if name and prefix: raise TypeError("Exactly one of 'name' or 'prefix' is required.") if name: prefix = self.get_prefix_envname(name) if prefix is None: prefix = self.ROOT_PREFIX return os.path.isdir(os.path.join(prefix, 'conda-meta')) def clear_lock(self, abspath=True): """ Clean any conda lock in the system. """ cmd_list = ['clean', '--lock', '--json'] return self._call_and_parse(cmd_list, abspath=abspath) def package_version(self, prefix=None, name=None, pkg=None): """ """ package_versions = {} if name and prefix: raise TypeError("Exactly one of 'name' or 'prefix' is required.") if name: prefix = self.get_prefix_envname(name) if self.environment_exists(prefix=prefix): for package in self.linked(prefix): if pkg in package: n, v, b = self.split_canonical_name(package) package_versions[n] = v return package_versions.get(pkg, None) def get_platform(self): """ Get platform of current system (system and bitness). """ _sys_map = {'linux2': 'linux', 'linux': 'linux', 'darwin': 'osx', 'win32': 'win', 'openbsd5': 'openbsd'} non_x86_linux_machines = {'armv6l', 'armv7l', 'ppc64le'} sys_platform = _sys_map.get(sys.platform, 'unknown') bits = 8 * tuple.__itemsize__ if (sys_platform == 'linux' and platform.machine() in non_x86_linux_machines): arch_name = platform.machine() subdir = 'linux-{0}'.format(arch_name) else: arch_name = {64: 'x86_64', 32: 'x86'}[bits] subdir = '{0}-{1}'.format(sys_platform, bits) return subdir def get_condarc_channels(self): """ Returns all the channel urls defined in .condarc using the defined `channel_alias`. If no condarc file is found, use the default channels. """ # First get the location of condarc file and parse it to get # the channel alias and the channels. default_channel_alias = 'https://conda.anaconda.org' default_urls = ['https://repo.continuum.io/pkgs/free', 'https://repo.continuum.io/pkgs/pro'] condarc_path = os.path.abspath(os.path.expanduser('~/.condarc')) channels = default_urls[:] if not os.path.isfile(condarc_path): condarc = None channel_alias = default_channel_alias else: with open(condarc_path, 'r') as f: data = f.read() condarc = yaml.load(data) channels += condarc.get('channels', []) channel_alias = condarc.get('channel_alias', default_channel_alias) if channel_alias[-1] == '/': template = "{0}{1}" else: template = "{0}/{1}" if 'defaults' in channels: channels.remove('defaults') channel_urls = [] for channel in channels: if not channel.startswith('http'): channel_url = template.format(channel_alias, channel) else: channel_url = channel channel_urls.append(channel_url) return channel_urls # --- Pip commands # ------------------------------------------------------------------------- def _call_pip(self, name=None, prefix=None, extra_args=None, callback=None): """ """ cmd_list = self._pip_cmd(name=name, prefix=prefix) cmd_list.extend(extra_args) process_worker = ProcessWorker(cmd_list, pip=True, callback=callback) process_worker.sig_finished.connect(self._start) self._queue.append(process_worker) self._start() return process_worker def _pip_cmd(self, name=None, prefix=None): """ Get pip location based on environment `name` or `prefix`. """ if (name and prefix) or not (name or prefix): raise TypeError("conda pip: exactly one of 'name' ""or 'prefix' " "required.") if name and self.environment_exists(name=name): prefix = self.get_prefix_envname(name) if sys.platform == 'win32': python = join(prefix, 'python.exe') # FIXME: pip = join(prefix, 'pip.exe') # FIXME: else: python = join(prefix, 'bin/python') pip = join(prefix, 'bin/pip') cmd_list = [python, pip] return cmd_list def pip_list(self, name=None, prefix=None, abspath=True): """ Get list of pip installed packages. """ if (name and prefix) or not (name or prefix): raise TypeError("conda pip: exactly one of 'name' ""or 'prefix' " "required.") if name: prefix = self.get_prefix_envname(name) pip_command = os.sep.join([prefix, 'bin', 'python']) cmd_list = [pip_command, PIP_LIST_SCRIPT] process_worker = ProcessWorker(cmd_list, pip=True, parse=True, callback=self._pip_list, extra_kwargs={'prefix': prefix}) process_worker.sig_finished.connect(self._start) self._queue.append(process_worker) self._start() return process_worker # if name: # cmd_list = ['list', '--name', name] # if prefix: # cmd_list = ['list', '--prefix', prefix] # return self._call_conda(cmd_list, abspath=abspath, # callback=self._pip_list) def _pip_list(self, stdout, stderr, prefix=None): """ """ result = stdout # A dict linked = self.linked(prefix) pip_only = [] linked_names = [self.split_canonical_name(l)[0] for l in linked] for pkg in result: name = self.split_canonical_name(pkg)[0] if name not in linked_names: pip_only.append(pkg) # FIXME: NEED A MORE ROBUST WAY! # if '<pip>' in line and '#' not in line: # temp = line.split()[:-1] + ['pip'] # temp = '-'.join(temp) # if '-(' in temp: # start = temp.find('-(') # end = temp.find(')') # substring = temp[start:end+1] # temp = temp.replace(substring, '') # result.append(temp) return pip_only def pip_remove(self, name=None, prefix=None, pkgs=None): """ Remove a pip package in given environment by `name` or `prefix`. """ logger.debug(str((prefix, pkgs))) if isinstance(pkgs, list) or isinstance(pkgs, tuple): pkg = ' '.join(pkgs) else: pkg = pkgs extra_args = ['uninstall', '--yes', pkg] return self._call_pip(name=name, prefix=prefix, extra_args=extra_args) def pip_search(self, search_string=None): """ Search for pip installable python packages in PyPI matching `search_string`. """ extra_args = ['search', search_string] return self._call_pip(name='root', extra_args=extra_args, callback=self._pip_search) # if stderr: # raise PipError(stderr) # You are using pip version 7.1.2, however version 8.0.2 is available. # You should consider upgrading via the 'pip install --upgrade pip' # command. def _pip_search(self, stdout, stderr): result = {} lines = to_text_string(stdout).split('\n') while '' in lines: lines.remove('') for line in lines: if ' - ' in line: parts = line.split(' - ') name = parts[0].strip() description = parts[1].strip() result[name] = description return result
class BasePlot(PlotWidget, PyDMPrimitiveWidget): crosshair_position_updated = Signal(float, float) def __init__(self, parent=None, background='default', axisItems=None): PlotWidget.__init__(self, parent=parent, background=background, axisItems=axisItems) PyDMPrimitiveWidget.__init__(self) self.plotItem = self.getPlotItem() self.plotItem.hideButtons() self._auto_range_x = None self.setAutoRangeX(True) self._auto_range_y = None self.setAutoRangeY(True) self._min_x = 0.0 self._max_x = 1.0 self._min_y = 0.0 self._max_y = 1.0 self._show_x_grid = None self.setShowXGrid(False) self._show_y_grid = None self.setShowYGrid(False) self._show_right_axis = False self.redraw_timer = QTimer(self) self.redraw_timer.timeout.connect(self.redrawPlot) self._redraw_rate = 30 # Redraw at 30 Hz by default. self.maxRedrawRate = self._redraw_rate self._curves = [] self._title = None self._show_legend = False self._legend = self.addLegend() self._legend.hide() # Drawing crosshair on the ViewBox self.vertical_crosshair_line = None self.horizontal_crosshair_line = None self.crosshair_movement_proxy = None def addCurve(self, plot_item, curve_color=None): if curve_color is None: curve_color = utilities.colors.default_colors[ len(self._curves) % len(utilities.colors.default_colors)] plot_item.color_string = curve_color self._curves.append(plot_item) self.addItem(plot_item) self.redraw_timer.start() # Connect channels for chan in plot_item.channels(): if chan: chan.connect() # self._legend.addItem(plot_item, plot_item.curve_name) def removeCurve(self, plot_item): self.removeItem(plot_item) self._curves.remove(plot_item) if len(self._curves) < 1: self.redraw_timer.stop() # Disconnect channels for chan in plot_item.channels(): if chan: chan.disconnect() def removeCurveWithName(self, name): for curve in self._curves: if curve.name() == name: self.removeCurve(curve) def removeCurveAtIndex(self, index): curve_to_remove = self._curves[index] self.removeCurve(curve_to_remove) def setCurveAtIndex(self, index, new_curve): old_curve = self._curves[index] self._curves[index] = new_curve # self._legend.addItem(new_curve, new_curve.name()) self.removeCurve(old_curve) def curveAtIndex(self, index): return self._curves[index] def curves(self): return self._curves def clear(self): legend_items = [label.text for (sample, label) in self._legend.items] for item in legend_items: self._legend.removeItem(item) self.plotItem.clear() self._curves = [] @Slot() def redrawPlot(self): pass def getShowXGrid(self): return self._show_x_grid def setShowXGrid(self, value, alpha=None): self._show_x_grid = value self.showGrid(x=self._show_x_grid, alpha=alpha) def resetShowXGrid(self): self.setShowXGrid(False) showXGrid = Property("bool", getShowXGrid, setShowXGrid, resetShowXGrid) def getShowYGrid(self): return self._show_y_grid def setShowYGrid(self, value, alpha=None): self._show_y_grid = value self.showGrid(y=self._show_y_grid, alpha=alpha) def resetShowYGrid(self): self.setShowYGrid(False) showYGrid = Property("bool", getShowYGrid, setShowYGrid, resetShowYGrid) def getBackgroundColor(self): return self.backgroundBrush().color() def setBackgroundColor(self, color): if self.backgroundBrush().color() != color: self.setBackgroundBrush(QBrush(color)) backgroundColor = Property(QColor, getBackgroundColor, setBackgroundColor) def getAxisColor(self): return self.getAxis('bottom')._pen.color() def setAxisColor(self, color): if self.getAxis('bottom')._pen.color() != color: self.getAxis('bottom').setPen(color) self.getAxis('left').setPen(color) self.getAxis('top').setPen(color) self.getAxis('right').setPen(color) axisColor = Property(QColor, getAxisColor, setAxisColor) def getBottomAxisLabel(self): return self.getAxis('bottom').labelText def getShowRightAxis(self): """ Provide whether the right y-axis is being shown. Returns : bool ------- True if the graph shows the right y-axis. False if not. """ return self._show_right_axis def setShowRightAxis(self, show): """ Set whether the graph should show the right y-axis. Parameters ---------- show : bool True for showing the right axis; False is for not showing. """ if show: self.showAxis("right") else: self.hideAxis("right") self._show_right_axis = show showRightAxis = Property("bool", getShowRightAxis, setShowRightAxis) def getPlotTitle(self): if self._title is None: return "" return str(self._title) def setPlotTitle(self, value): self._title = str(value) if len(self._title) < 1: self._title = None self.setTitle(self._title) def resetPlotTitle(self): self._title = None self.setTitle(self._title) title = Property(str, getPlotTitle, setPlotTitle, resetPlotTitle) def getShowLegend(self): """ Check if the legend is being shown. Returns : bool ------- True if the legend is displayed on the graph; False if not. """ return self._show_legend def setShowLegend(self, value): """ Set to display the legend on the graph. Parameters ---------- value : bool True to display the legend; False is not. """ self._show_legend = value if self._show_legend: if self._legend is None: self._legend = self.addLegend() else: self._legend.show() else: if self._legend is not None: self._legend.hide() def resetShowLegend(self): """ Reset the legend display status to hidden. """ self.setShowLegend(False) showLegend = Property(bool, getShowLegend, setShowLegend, resetShowLegend) def getAutoRangeX(self): return self._auto_range_x def setAutoRangeX(self, value): self._auto_range_x = value if self._auto_range_x: self.plotItem.enableAutoRange(ViewBox.XAxis, enable=self._auto_range_x) def resetAutoRangeX(self): self.setAutoRangeX(True) def getAutoRangeY(self): return self._auto_range_y def setAutoRangeY(self, value): self._auto_range_y = value if self._auto_range_y: self.plotItem.enableAutoRange(ViewBox.YAxis, enable=self._auto_range_y) def resetAutoRangeY(self): self.setAutoRangeY(True) def getMinXRange(self): """ Minimum X-axis value visible on the plot. Returns ------- float """ return self.plotItem.viewRange()[0][0] def setMinXRange(self, new_min_x_range): """ Set the minimum X-axis value visible on the plot. Parameters ------- new_min_x_range : float """ if self._auto_range_x: return self._min_x = new_min_x_range self.plotItem.setXRange(self._min_x, self._max_x, padding=0) def getMaxXRange(self): """ Maximum X-axis value visible on the plot. Returns ------- float """ return self.plotItem.viewRange()[0][1] def setMaxXRange(self, new_max_x_range): """ Set the Maximum X-axis value visible on the plot. Parameters ------- new_max_x_range : float """ if self._auto_range_x: return self._max_x = new_max_x_range self.plotItem.setXRange(self._min_x, self._max_x, padding=0) def getMinYRange(self): """ Minimum Y-axis value visible on the plot. Returns ------- float """ return self.plotItem.viewRange()[1][0] def setMinYRange(self, new_min_y_range): """ Set the minimum Y-axis value visible on the plot. Parameters ------- new_min_y_range : float """ if self._auto_range_y: return self._min_y = new_min_y_range self.plotItem.setYRange(self._min_y, self._max_y, padding=0) def getMaxYRange(self): """ Maximum Y-axis value visible on the plot. Returns ------- float """ return self.plotItem.viewRange()[1][1] def setMaxYRange(self, new_max_y_range): """ Set the maximum Y-axis value visible on the plot. Parameters ------- new_max_y_range : float """ if self._auto_range_y: return self._max_y = new_max_y_range self.plotItem.setYRange(self._min_y, self._max_y, padding=0) @Property(bool) def mouseEnabledX(self): """ Whether or not mouse interactions are enabled for the X-axis. Returns ------- bool """ return self.plotItem.getViewBox().state['mouseEnabled'][0] @mouseEnabledX.setter def mouseEnabledX(self, x_enabled): """ Whether or not mouse interactions are enabled for the X-axis. Parameters ------- x_enabled : bool """ self.plotItem.setMouseEnabled(x=x_enabled) @Property(bool) def mouseEnabledY(self): """ Whether or not mouse interactions are enabled for the Y-axis. Returns ------- bool """ return self.plotItem.getViewBox().state['mouseEnabled'][1] @mouseEnabledY.setter def mouseEnabledY(self, y_enabled): """ Whether or not mouse interactions are enabled for the Y-axis. Parameters ------- y_enabled : bool """ self.plotItem.setMouseEnabled(y=y_enabled) @Property(int) def maxRedrawRate(self): """ The maximum rate (in Hz) at which the plot will be redrawn. The plot will not be redrawn if there is not new data to draw. Returns ------- int """ return self._redraw_rate @maxRedrawRate.setter def maxRedrawRate(self, redraw_rate): """ The maximum rate (in Hz) at which the plot will be redrawn. The plot will not be redrawn if there is not new data to draw. Parameters ------- redraw_rate : int """ self._redraw_rate = redraw_rate self.redraw_timer.setInterval(int((1.0/self._redraw_rate)*1000)) def pausePlotting(self): self.redraw_timer.stop() if self.redraw_timer.isActive() else self.redraw_timer.start() return self.redraw_timer.isActive() def mouseMoved(self, evt): """ A handler for the crosshair feature. Every time the mouse move, the mouse coordinates are updated, and the horizontal and vertical hairlines will be redrawn at the new coordinate. If a PyDMDisplay object is available, that display will also have the x- and y- values to update on the UI. Parameters ------- evt: MouseEvent The mouse event type, from which the mouse coordinates are obtained. """ pos = evt[0] if self.sceneBoundingRect().contains(pos): mouse_point = self.getViewBox().mapSceneToView(pos) self.vertical_crosshair_line.setPos(mouse_point.x()) self.horizontal_crosshair_line.setPos(mouse_point.y()) self.crosshair_position_updated.emit(mouse_point.x(), mouse_point.y()) def enableCrosshair(self, is_enabled, starting_x_pos, starting_y_pos, vertical_angle=90, horizontal_angle=0, vertical_movable=False, horizontal_movable=False): """ Enable the crosshair to be drawn on the ViewBox. Parameters ---------- is_enabled : bool True is to draw the crosshair, False is to not draw. starting_x_pos : float The x coordinate where to start the vertical crosshair line. starting_y_pos : float The y coordinate where to start the horizontal crosshair line. vertical_angle : float The angle to tilt the vertical crosshair line. Default at 90 degrees. horizontal_angle The angle to tilt the horizontal crosshair line. Default at 0 degrees. vertical_movable : bool True if the vertical line can be moved by the user; False is not. horizontal_movable False if the horizontal line can be moved by the user; False is not. """ if is_enabled: self.vertical_crosshair_line = InfiniteLine(pos=starting_x_pos, angle=vertical_angle, movable=vertical_movable) self.horizontal_crosshair_line = InfiniteLine(pos=starting_y_pos, angle=horizontal_angle, movable=horizontal_movable) self.plotItem.addItem(self.vertical_crosshair_line) self.plotItem.addItem(self.horizontal_crosshair_line) self.crosshair_movement_proxy = SignalProxy(self.plotItem.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved) else: if self.vertical_crosshair_line: self.plotItem.removeItem(self.vertical_crosshair_line) if self.horizontal_crosshair_line: self.plotItem.removeItem(self.horizontal_crosshair_line) if self.crosshair_movement_proxy: self.crosshair_movement_proxy.disconnect()
class ProcessWorker(QObject): """ """ sig_finished = Signal(object, object, object) sig_partial = Signal(object, object, object) def __init__(self, cmd_list, parse=False, pip=False, callback=None, extra_kwargs={}): super(ProcessWorker, self).__init__() self._result = None self._cmd_list = cmd_list self._parse = parse self._pip = pip self._conda = not pip self._callback = callback self._fired = False self._communicate_first = False self._partial_stdout = None self._extra_kwargs = extra_kwargs self._timer = QTimer() self._process = QProcess() self._timer.setInterval(50) self._timer.timeout.connect(self._communicate) self._process.finished.connect(self._communicate) self._process.readyReadStandardOutput.connect(self._partial) def _partial(self): raw_stdout = self._process.readAllStandardOutput() stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8) json_stdout = stdout.replace('\n\x00', '') try: json_stdout = json.loads(json_stdout) except Exception: json_stdout = stdout if self._partial_stdout is None: self._partial_stdout = stdout else: self._partial_stdout += stdout self.sig_partial.emit(self, json_stdout, None) def _communicate(self): """ """ if not self._communicate_first: if self._process.state() == QProcess.NotRunning: self.communicate() elif self._fired: self._timer.stop() def communicate(self): """ """ self._communicate_first = True self._process.waitForFinished() if self._partial_stdout is None: raw_stdout = self._process.readAllStandardOutput() stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8) else: stdout = self._partial_stdout raw_stderr = self._process.readAllStandardError() stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8) result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)] # FIXME: Why does anaconda client print to stderr??? if PY2: stderr = stderr.decode() if 'using anaconda cloud api site' not in stderr.lower(): if stderr.strip() and self._conda: raise Exception('{0}:\n' 'STDERR:\n{1}\nEND' ''.format(' '.join(self._cmd_list), stderr)) # elif stderr.strip() and self._pip: # raise PipError(self._cmd_list) else: result[-1] = '' if self._parse and stdout: try: result = json.loads(stdout), result[-1] except ValueError as error: result = stdout, error if 'error' in result[0]: error = '{0}: {1}'.format(" ".join(self._cmd_list), result[0]['error']) result = result[0], error if self._callback: result = self._callback(result[0], result[-1], **self._extra_kwargs), result[-1] self._result = result self.sig_finished.emit(self, result[0], result[-1]) if result[-1]: logger.error(str(('error', result[-1]))) self._fired = True return result def close(self): """ """ self._process.close() def is_finished(self): """ """ return self._process.state() == QProcess.NotRunning and self._fired def start(self): """ """ logger.debug(str(' '.join(self._cmd_list))) if not self._fired: self._partial_ouput = None self._process.start(self._cmd_list[0], self._cmd_list[1:]) self._timer.start() else: raise CondaProcessWorker('A Conda ProcessWorker can only run once ' 'per method call.')
class ModalTester(object): """ Helper class for testing modal widgets (dialogs). """ def __init__(self, creator, tester): """ Initialise ModalTester. :param creator: Function without arguments that creates a modal widget. Can be a class name. A modal widget must have exec_() method. :param tester: A function taking a widget as its argument that does testing. """ self.app = QApplication.instance() if self.app is None: self.app = QApplication(['']) self.creator = creator self.tester = tester self.widget = None self.timer = None self.passed = False def __call__(self): """ Initialise testing. """ try: self.widget = self.creator() except: traceback.print_exc() self.app.exit(0) if self.widget is not None: self.widget.exec_() def _idle(self): """ This function runs every time in QApplication's event loop. Call the testing function. """ modal_widget = self.app.activeModalWidget() if modal_widget is not None: if self.widget is modal_widget: try: self.tester(self.widget) self.passed = True except: traceback.print_exc() if modal_widget.isVisible(): modal_widget.close() def start(self): """ Start this tester. """ self.timer = QTimer() # Connecting self.idle to a QTimer with 0 time delay # makes it called when there are no events to process self.timer.timeout.connect(self._idle) self.timer.start() # This calls __call__() method QTimer.singleShot(0, self) # Start the event loop self.app.exec_()
class BackgroundPlotter(QtInteractor): """Qt interactive plotter. Background plotter for pyvista that allows you to maintain an interactive plotting window without blocking the main python thread. Parameters ---------- show : Show the plotting window. If ``False``, show this window by running ``show()`` app : optional Creates a `QApplication` if left as `None`. window_size : Window size in pixels. Defaults to ``[1024, 768]`` off_screen : Renders off screen when True. Useful for automated screenshots or debug testing. allow_quit_keypress : Allow user to exit by pressing ``"q"``. toolbar : bool If True, display the default camera toolbar. Defaults to True. menu_bar : bool If True, display the default main menu. Defaults to True. editor: bool If True, display the VTK object editor. Defaults to True. update_app_icon : If True, update_app_icon will be called automatically to update the Qt app icon based on the current rendering output. If None, the logo of PyVista will be used. If False, no icon will be set. Defaults to None. title : str, optional Title of plotting window. multi_samples : int, optional The number of multi-samples used to mitigate aliasing. 4 is a good default but 8 will have better results with a potential impact on performance. line_smoothing : bool, optional If True, enable line smothing point_smoothing : bool, optional If True, enable point smothing polygon_smoothing : bool, optional If True, enable polygon smothing auto_update : float, bool, optional Automatic update rate in seconds. Useful for automatically updating the render window when actors are change without being automatically ``Modified``. If set to ``True``, update rate will be 1 second. Examples -------- >>> import pyvista as pv >>> from pyvistaqt import BackgroundPlotter >>> plotter = BackgroundPlotter() >>> _ = plotter.add_mesh(pv.Sphere()) """ # pylint: disable=too-many-ancestors # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-statements ICON_TIME_STEP = 5.0 # pylint: disable=too-many-arguments # pylint: disable=too-many-locals def __init__( self, show: bool = True, app: Optional[QApplication] = None, window_size: Optional[Tuple[int, int]] = None, off_screen: Optional[bool] = None, allow_quit_keypress: bool = True, toolbar: bool = True, menu_bar: bool = True, editor: bool = True, update_app_icon: Optional[bool] = None, **kwargs: Any, ) -> None: # pylint: disable=too-many-branches """Initialize the qt plotter.""" # avoid recursion of the close() function by setting # self._closed=True until the BasePlotter.__init__ # is called self._closed = True LOG.debug("BackgroundPlotter init start") _check_type(show, "show", [bool]) _check_type(app, "app", [QApplication, type(None)]) _check_type(window_size, "window_size", [tuple, type(None)]) _check_type(off_screen, "off_screen", [bool, type(None)]) _check_type(allow_quit_keypress, "allow_quit_keypress", [bool]) _check_type(toolbar, "toolbar", [bool]) _check_type(menu_bar, "menu_bar", [bool]) _check_type(editor, "editor", [bool]) _check_type(update_app_icon, "update_app_icon", [bool, type(None)]) # toolbar self._view_action: QAction = None self.default_camera_tool_bar: QToolBar = None self.saved_camera_positions: Optional[list] = None self.saved_cameras_tool_bar: QToolBar = None # menu bar self.main_menu: QMenuBar = None self._edl_action: QAction = None self._menu_close_action: QAction = None self._parallel_projection_action: QAction = None # editor self.editor: Optional[Editor] = None self._editor_action: QAction = None self.active = True self.counters: List[Counter] = [] self.allow_quit_keypress = allow_quit_keypress if window_size is None: window_size = global_theme.window_size # Remove notebook argument in case user passed it kwargs.pop("notebook", None) self.ipython = _setup_ipython() self.app = _setup_application(app) self.off_screen = _setup_off_screen(off_screen) self.app_window = MainWindow(title=kwargs.get("title", global_theme.title)) self.frame = QFrame(parent=self.app_window) self.frame.setFrameStyle(QFrame.NoFrame) vlayout = QVBoxLayout() super().__init__(parent=self.frame, off_screen=off_screen, **kwargs) assert not self._closed vlayout.addWidget(self) self.frame.setLayout(vlayout) self.app_window.setCentralWidget(self.frame) self.app_window.grabGesture(QtCore.Qt.PinchGesture) self.app_window.signal_gesture.connect(self.gesture_event) self.app_window.signal_close.connect(self._close) if menu_bar: self.add_menu_bar() if editor: self.add_editor() if toolbar: self.add_toolbars() if show and not self.off_screen: # pragma: no cover self.app_window.show() self.window_size = window_size self._last_update_time = -np.inf self._last_window_size = self.window_size self._last_camera_pos = self.camera_position if update_app_icon: self.add_callback(self.update_app_icon) elif update_app_icon is None: self.set_icon( os.path.join( os.path.dirname(__file__), "data", "pyvista_logo_square.png" ) ) else: assert update_app_icon is False # Keypress events if self.iren is not None: self.add_key_event("S", self._qt_screenshot) # shift + s LOG.debug("BackgroundPlotter init stop") def reset_key_events(self) -> None: """Reset all of the key press events to their defaults. Handles closing configuration for q-key. """ super().reset_key_events() if self.allow_quit_keypress: # pylint: disable=unnecessary-lambda self.add_key_event("q", lambda: self.close()) def scale_axes_dialog(self, show: bool = True) -> ScaleAxesDialog: """Open scale axes dialog.""" return ScaleAxesDialog(self.app_window, self, show=show) def close(self) -> None: """Close the plotter. This function closes the window which in turn will close the plotter through `signal_close`. """ if not self._closed: # Can get: # # RuntimeError: wrapped C/C++ object of type MainWindow has # been deleted # # So let's be safe and try/except this in case of a problem. try: self.app_window.close() except Exception: pass def _close(self) -> None: super().close() def update_app_icon(self) -> None: """Update the app icon if the user is not trying to resize the window.""" if os.name == "nt" or not hasattr( self, "_last_window_size" ): # pragma: no cover # DO NOT EVEN ATTEMPT TO UPDATE ICON ON WINDOWS return cur_time = time.time() if self._last_window_size != self.window_size: # pragma: no cover # Window size hasn't remained constant since last render. # This means the user is resizing it so ignore update. pass elif ( cur_time - self._last_update_time > BackgroundPlotter.ICON_TIME_STEP ) and self._last_camera_pos != self.camera_position: # its been a while since last update OR # the camera position has changed and its been at least one second # Update app icon as preview of the window self.set_icon(pad_image(self.image)) # Update trackers self._last_update_time = cur_time self._last_camera_pos = self.camera_position # Update trackers self._last_window_size = self.window_size def set_icon(self, img: Union[np.ndarray, str]) -> None: """Set the icon image. Parameters ---------- img : ndarray, shape (w, h, c) | str The image. Should be uint8 and square (w == h). Can have 3 or 4 color/alpha channels (``c``). Can also be a string path that QIcon can load. Notes ----- Currently string paths can silently fail, so make sure your path is something that produces a valid ``QIcon(img)``. """ if not ( isinstance(img, np.ndarray) and img.ndim == 3 and img.shape[0] == img.shape[1] and img.dtype == np.uint8 and img.shape[-1] in (3, 4) ) and not isinstance(img, str): raise ValueError( "img must be 3D uint8 ndarray with shape[1] == shape[2] and " "shape[2] == 3 or 4, or str" ) if isinstance(img, np.ndarray): fmt_str = "Format_RGB" fmt_str += ("A8" if img.shape[2] == 4 else "") + "888" fmt = getattr(QtGui.QImage, fmt_str) img = QtGui.QPixmap.fromImage( QtGui.QImage(img.copy(), img.shape[1], img.shape[0], fmt) ) # Currently no way to check if str/path is actually correct (want to # allow resource paths and the like so os.path.isfile is no good) # and icon.isNull() returns False even if the path is bogus. self.app.setWindowIcon(QtGui.QIcon(img)) def _qt_screenshot(self, show: bool = True) -> FileDialog: return FileDialog( self.app_window, filefilter=["Image File (*.png)", "JPEG (*.jpeg)"], show=show, directory=bool(os.getcwd()), callback=self.screenshot, ) def _qt_export_vtkjs(self, show: bool = True) -> FileDialog: """Spawn an save file dialog to export a vtkjs file.""" return FileDialog( self.app_window, filefilter=["VTK JS File(*.vtkjs)"], show=show, directory=bool(os.getcwd()), callback=self.export_vtkjs, ) def _toggle_edl(self) -> None: if hasattr(self.renderer, "edl_pass"): return self.renderer.disable_eye_dome_lighting() return self.renderer.enable_eye_dome_lighting() def _toggle_parallel_projection(self) -> None: if self.camera.GetParallelProjection(): return self.disable_parallel_projection() return self.enable_parallel_projection() @property def window_size(self) -> Tuple[int, int]: """Return render window size.""" the_size = self.app_window.baseSize() return the_size.width(), the_size.height() @window_size.setter def window_size(self, window_size: QSize) -> None: """Set the render window size.""" self.app_window.setBaseSize(*window_size) self.app_window.resize(*window_size) # NOTE: setting BasePlotter is unnecessary and Segfaults CI # BasePlotter.window_size.fset(self, window_size) def __del__(self) -> None: # pragma: no cover """Delete the qt plotter.""" if not self._closed: self.app_window.close() def add_callback( self, func: Callable, interval: int = 1000, count: Optional[int] = None ) -> None: """Add a function that can update the scene in the background. Parameters ---------- func : Function to be called with no arguments. interval : Time interval between calls to `func` in milliseconds. count : Number of times `func` will be called. If None, `func` will be called until the main window is closed. """ self._callback_timer = QTimer(parent=self.app_window) self._callback_timer.timeout.connect(func) self._callback_timer.start(interval) self.app_window.signal_close.connect(self._callback_timer.stop) if count is not None: counter = Counter(count) counter.signal_finished.connect(self._callback_timer.stop) self._callback_timer.timeout.connect(counter.decrease) self.counters.append(counter) def save_camera_position(self) -> None: """Save camera position to saved camera menu for recall.""" if self.saved_camera_positions is not None: # pylint: disable=attribute-defined-outside-init self.camera_position: Any self.saved_camera_positions.append(self.camera_position) ncam = len(self.saved_camera_positions) if self.camera_position is not None: camera_position: Any = self.camera_position[:] # py2.7 copy compatibility if hasattr(self, "saved_cameras_tool_bar"): def load_camera_position() -> None: # pylint: disable=attribute-defined-outside-init self.camera_position = camera_position self.saved_cameras_tool_bar.addAction( "Cam %2d" % ncam, load_camera_position ) if ncam < 10: self.add_key_event(str(ncam), load_camera_position) def clear_camera_positions(self) -> None: """Clear all camera positions.""" if hasattr(self, "saved_cameras_tool_bar"): for action in self.saved_cameras_tool_bar.actions(): if action.text() not in [SAVE_CAM_BUTTON_TEXT, CLEAR_CAMS_BUTTON_TEXT]: self.saved_cameras_tool_bar.removeAction(action) self.saved_camera_positions = [] def _add_action(self, tool_bar: QToolBar, key: str, method: Any) -> QAction: action = QAction(key, self.app_window) action.triggered.connect(method) tool_bar.addAction(action) return action def add_toolbars(self) -> None: """Add the toolbars.""" # Camera toolbar self.default_camera_tool_bar = self.app_window.addToolBar("Camera Position") def _view_vector(*args: Any) -> None: return self.view_vector(*args) cvec_setters = { # Viewing vector then view up vector "Top (-Z)": lambda: _view_vector((0, 0, 1), (0, 1, 0)), "Bottom (+Z)": lambda: _view_vector((0, 0, -1), (0, 1, 0)), "Front (-Y)": lambda: _view_vector((0, 1, 0), (0, 0, 1)), "Back (+Y)": lambda: _view_vector((0, -1, 0), (0, 0, 1)), "Left (-X)": lambda: _view_vector((1, 0, 0), (0, 0, 1)), "Right (+X)": lambda: _view_vector((-1, 0, 0), (0, 0, 1)), "Isometric": lambda: _view_vector((1, 1, 1), (0, 0, 1)), } for key, method in cvec_setters.items(): self._view_action = self._add_action( self.default_camera_tool_bar, key, method ) # pylint: disable=unnecessary-lambda self._add_action( self.default_camera_tool_bar, "Reset", lambda: self.reset_camera() ) # Saved camera locations toolbar self.saved_camera_positions = [] self.saved_cameras_tool_bar = self.app_window.addToolBar( "Saved Camera Positions" ) self._add_action( self.saved_cameras_tool_bar, SAVE_CAM_BUTTON_TEXT, self.save_camera_position ) self._add_action( self.saved_cameras_tool_bar, CLEAR_CAMS_BUTTON_TEXT, self.clear_camera_positions, ) def add_menu_bar(self) -> None: """Add the main menu bar.""" self.main_menu = _create_menu_bar(parent=self.app_window) self.app_window.signal_close.connect(self.main_menu.clear) file_menu = self.main_menu.addMenu("File") file_menu.addAction("Take Screenshot", self._qt_screenshot) file_menu.addAction("Export as VTKjs", self._qt_export_vtkjs) file_menu.addSeparator() # member variable for testing only self._menu_close_action = file_menu.addAction("Exit", self.app_window.close) view_menu = self.main_menu.addMenu("View") self._edl_action = view_menu.addAction( "Toggle Eye Dome Lighting", self._toggle_edl ) view_menu.addAction("Scale Axes", self.scale_axes_dialog) view_menu.addAction("Clear All", self.clear) tool_menu = self.main_menu.addMenu("Tools") tool_menu.addAction("Enable Cell Picking (through)", self.enable_cell_picking) tool_menu.addAction( "Enable Cell Picking (visible)", lambda: self.enable_cell_picking(through=False), ) cam_menu = view_menu.addMenu("Camera") self._parallel_projection_action = cam_menu.addAction( "Toggle Parallel Projection", self._toggle_parallel_projection ) view_menu.addSeparator() # Orientation marker orien_menu = view_menu.addMenu("Orientation Marker") orien_menu.addAction("Show All", self.show_axes_all) orien_menu.addAction("Hide All", self.hide_axes_all) # Bounds axes axes_menu = view_menu.addMenu("Bounds Axes") axes_menu.addAction("Add Bounds Axes (front)", self.show_bounds) axes_menu.addAction("Add Bounds Grid (back)", self.show_grid) axes_menu.addAction("Add Bounding Box", self.add_bounding_box) axes_menu.addSeparator() axes_menu.addAction("Remove Bounding Box", self.remove_bounding_box) axes_menu.addAction("Remove Bounds", self.remove_bounds_axes) # A final separator to separate OS options view_menu.addSeparator() def add_editor(self) -> None: """Add the editor.""" self.editor = Editor(parent=self.app_window, renderers=self.renderers) self._editor_action = self.main_menu.addAction("Editor", self.editor.toggle) self.app_window.signal_close.connect(self.editor.close)
class ProgressDialog(QDialog, Ui_Dialog): """Progress dialog. + Batch execute function. + Interrupt function. """ stop_signal = Signal() def __init__(self, type_num: AlgorithmType, mech_params: Dict[str, Any], setting: Dict[str, Any], parent): super(ProgressDialog, self).__init__(parent) self.setupUi(self) flags = self.windowFlags() self.setWindowFlags(flags & ~Qt.WindowContextHelpButtonHint) self.rejected.connect(self.__close_work) self.mechanisms: List[Dict[str, Any]] = [] # Batch label if 'max_gen' in setting: self.limit = setting['max_gen'] if self.limit > 0: self.batch_label.setText(f"{self.limit} generation(s)") else: self.batch_label.setText('∞') self.limit_mode = 'max_gen' elif 'min_fit' in setting: self.limit = setting['min_fit'] self.batch_label.setText(f"fitness less then {self.limit}") self.limit_mode = 'min_fit' elif 'max_time' in setting: self.limit = setting['max_time'] self.batch_label.setText(f"{self.limit // 3600:02d}:" f"{self.limit % 3600 // 60:02d}:" f"{self.limit % 3600 % 60:02d}") self.limit_mode = 'max_time' else: self.limit = 0 self.batch_label.setText('∞') self.limit_mode = 'max_gen' self.loopTime.setEnabled(self.limit > 0) # Timer self.time = 0 self.timer = QTimer() self.timer.setInterval(1000) self.timer.timeout.connect(self.__set_time) self.time_spend = 0. # Worker thread self.work = WorkerThread(type_num, mech_params, setting, self) self.stop_signal.connect(self.work.stop) if self.work.is_two_kernel(): self.fast_kernel_label.hide() else: self.full_kernel_label.hide() self.work.progress_update.connect(self.__set_progress) self.work.result.connect(self.__get_result) self.work.finished.connect(self.__finish) @Slot(int, str) def __set_progress(self, progress: int, fitness: str) -> None: """Progress bar will always full.""" value = progress + self.limit * self.work.current_loop if self.limit_mode in {'min_fit', 'max_time'} or self.limit == 0: self.progress_bar.setMaximum(value) self.progress_bar.setValue(value) self.fitness_label.setText(fitness) @Slot() def __set_time(self) -> None: """Set time label.""" self.time += 1 t_min = self.time % 3600 self.time_label.setText(f"{self.time // 3600:02d}:" f"{t_min // 60:02d}:" f"{t_min % 60:02d}") @Slot(name='on_start_button_clicked') def __start(self) -> None: """Start the process.""" loop = self.loopTime.value() self.progress_bar.setMaximum(self.limit * loop) if self.limit_mode in {'min_fit', 'max_time'} or self.limit == 0: # Progress bar will show generations instead of percent. self.progress_bar.setFormat("%v generations") self.work.set_loop(loop) self.timer.start() self.work.start() self.start_button.setEnabled(False) self.loopTime.setEnabled(False) self.interrupt_button.setEnabled(True) @Slot(dict) def __get_result(self, mechanism: Dict[str, Any]) -> None: """Get the result.""" self.mechanisms.append(mechanism) self.time_spend += mechanism['time'] @Slot() def __finish(self) -> None: """Finish the process.""" self.timer.stop() self.accept() @Slot(name='on_interrupt_button_clicked') def __interrupt(self) -> None: """Interrupt the process.""" if self.work.isRunning(): self.stop_signal.emit() logger.info("The thread has been interrupted.") @Slot() def __close_work(self) -> None: """Close the thread.""" if not self.work.isRunning(): return self.stop_signal.emit() logger.info("The thread has been canceled.") self.work.wait()
class AutosaveForPlugin(object): """ Component of editor plugin implementing autosave functionality. Attributes: name_mapping (dict): map between names of opened and autosave files. file_hashes (dict): map between file names and hash of their contents. This is used for both files opened in the editor and their corresponding autosave files. """ # Interval (in ms) between two autosaves DEFAULT_AUTOSAVE_INTERVAL = 60 * 1000 def __init__(self, editor): """ Constructor. Autosave is disabled after construction and needs to be enabled explicitly if required. Args: editor (Editor): editor plugin. """ self.editor = editor self.name_mapping = {} self.file_hashes = {} self.timer = QTimer(self.editor) self.timer.setSingleShot(True) self.timer.timeout.connect(self.do_autosave) self._enabled = False # Can't use setter here self._interval = self.DEFAULT_AUTOSAVE_INTERVAL @property def enabled(self): """ Get or set whether autosave component is enabled. The setter will start or stop the autosave component if appropriate. """ return self._enabled @enabled.setter def enabled(self, new_enabled): if new_enabled == self.enabled: return self.stop_autosave_timer() self._enabled = new_enabled self.start_autosave_timer() @property def interval(self): """ Interval between two autosaves, in milliseconds. The setter will perform an autosave if the interval is changed and autosave is enabled. """ return self._interval @interval.setter def interval(self, new_interval): if new_interval == self.interval: return self.stop_autosave_timer() self._interval = new_interval if self.enabled: self.do_autosave() def start_autosave_timer(self): """ Start a timer which calls do_autosave() after `self.interval`. The autosave timer is only started if autosave is enabled. """ if self.enabled: self.timer.start(self.interval) def stop_autosave_timer(self): """Stop the autosave timer.""" self.timer.stop() def do_autosave(self): """Instruct current editorstack to autosave files where necessary.""" logger.debug('Autosave triggered') stack = self.editor.get_current_editorstack() stack.autosave.autosave_all() self.start_autosave_timer() def get_files_to_recover(self): """ Get list of files to recover from pid files in autosave dir. This returns a tuple `(files_to_recover, pid_files)`. In this tuple, `files_to_recover` is a list of tuples containing the original file names and the corresponding autosave file names, as recorded in the pid files in the autosave directory. Any files in the autosave directory which are not listed in a pid file, are also included, with the original file name set to `None`. The second entry, `pid_files`, is a list with the names of the pid files. """ autosave_dir = get_conf_path('autosave') if not os.access(autosave_dir, os.R_OK): return [], [] files_to_recover = [] files_mentioned = [] pid_files = [] non_pid_files = [] # In Python 3, easier to use os.scandir() for name in os.listdir(autosave_dir): full_name = osp.join(autosave_dir, name) match = re.match(r'pid([0-9]*)\.txt\Z', name) if match: pid_files.append(full_name) logger.debug('Reading pid file: {}'.format(full_name)) with open(full_name) as pidfile: txt = pidfile.read() txt_as_dict = ast.literal_eval(txt) files_mentioned += [ autosave for (orig, autosave) in txt_as_dict.items() ] pid = int(match.group(1)) if is_spyder_process(pid): logger.debug('Ignoring files in {}'.format(full_name)) else: files_to_recover += list(txt_as_dict.items()) else: non_pid_files.append(full_name) # Add all files not mentioned in any pid file. This can only happen if # the pid file somehow got corrupted. for filename in set(non_pid_files) - set(files_mentioned): files_to_recover.append((None, filename)) logger.debug('Added unmentioned file: {}'.format(filename)) return files_to_recover, pid_files def try_recover_from_autosave(self): """ Offer to recover files from autosave. Read pid files to get a list of files that can possibly be recovered, then ask the user what to do with these files, and finally remove the pid files. """ files_to_recover, pidfiles = self.get_files_to_recover() parent = self.editor if running_under_pytest() else self.editor.main dialog = RecoveryDialog(files_to_recover, parent=parent) dialog.exec_if_nonempty() self.recover_files_to_open = dialog.files_to_open[:] for pidfile in pidfiles: try: os.remove(pidfile) except (IOError, OSError): pass def register_autosave_for_stack(self, autosave_for_stack): """ Register an AutosaveForStack object. This replaces the `name_mapping` and `file_hashes` attributes in `autosave_for_stack` with references to the corresponding attributes of `self`, so that all AutosaveForStack objects share the same data. """ autosave_for_stack.name_mapping = self.name_mapping autosave_for_stack.file_hashes = self.file_hashes
class FindReplace(QWidget): """Find widget""" STYLE = { False: "background-color:rgb(255, 175, 90);", True: "", None: "", 'regexp_error': "background-color:rgb(255, 80, 80);", } TOOLTIP = { False: _("No matches"), True: _("Search string"), None: _("Search string"), 'regexp_error': _("Regular expression error") } visibility_changed = Signal(bool) return_shift_pressed = Signal() return_pressed = Signal() def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton( self, triggered=self.hide, icon=ima.icon('DialogCloseButton')) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.return_shift_pressed.connect( lambda: self.find(changed=False, forward=False, rehighlight=False, multiline_replace_check=False)) self.return_pressed.connect( lambda: self.find(changed=False, forward=True, rehighlight=False, multiline_replace_check=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.number_matches_text = QLabel(self) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=ima.icon('ArrowUp'), tip=_("Find previous")) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=ima.icon('ArrowDown'), tip=_("Find next")) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=ima.icon('regex'), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton( self, icon=ima.icon("format_letter_case"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton( self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [ self.close_button, self.search_text, self.number_matches_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button ] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_('Replace string')) self.replace_text.valid.connect( lambda _: self.replace_find(focus_replace_text=True)) self.replace_button = create_toolbutton( self, text=_('Replace/find next'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find, text_beside_icon=True) self.replace_sel_button = create_toolbutton( self, text=_('Replace in selection'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find_selection, text_beside_icon=True) self.replace_sel_button.clicked.connect(self.update_replace_combo) self.replace_sel_button.clicked.connect(self.update_search_combo) self.replace_all_button = create_toolbutton( self, text=_('Replace all'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find_all, text_beside_icon=True) self.replace_all_button.clicked.connect(self.update_replace_combo) self.replace_all_button.clicked.connect(self.update_search_combo) self.replace_layout = QHBoxLayout() widgets = [ replace_with, self.replace_text, self.replace_button, self.replace_sel_button, self.replace_all_button ] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches) self.search_text.installEventFilter(self) def eventFilter(self, widget, event): """Event filter for search_text widget. Emits signals when presing Enter and Shift+Enter. This signals are used for search forward and backward. Also, a crude hack to get tab working in the Find/Replace boxes. """ if event.type() == QEvent.KeyPress: key = event.key() shift = event.modifiers() & Qt.ShiftModifier if key == Qt.Key_Return: if shift: self.return_shift_pressed.emit() else: self.return_pressed.emit() if key == Qt.Key_Tab: if self.search_text.hasFocus(): self.replace_text.set_current_text( self.search_text.currentText()) self.focusNextChild() return super(FindReplace, self).eventFilter(widget, event) def create_shortcuts(self, parent): """Create shortcuts for this widget""" # Configurable findnext = CONF.config_shortcut(self.find_next, context='find_replace', name='Find next', parent=parent) findprev = CONF.config_shortcut(self.find_previous, context='find_replace', name='Find previous', parent=parent) togglefind = CONF.config_shortcut(self.show, context='find_replace', name='Find text', parent=parent) togglereplace = CONF.config_shortcut(self.show_replace, context='find_replace', name='Replace text', parent=parent) hide = CONF.config_shortcut(self.hide, context='find_replace', name='hide find and replace', parent=self) return [findnext, findprev, togglefind, togglereplace, hide] def get_shortcut_data(self): """ Returns shortcut data, a list of tuples (shortcut, text, default) shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def update_search_combo(self): self.search_text.lineEdit().returnPressed.emit() def update_replace_combo(self): self.replace_text.lineEdit().returnPressed.emit() @Slot(bool) def toggle_highlighting(self, state): """Toggle the 'highlight all results' feature""" if self.editor is not None: if state: self.highlight_matches() else: self.clear_matches() def show(self, hide_replace=True): """Overrides Qt Method""" QWidget.show(self) self.visibility_changed.emit(True) self.change_number_matches() if self.editor is not None: if hide_replace: if self.replace_widgets[0].isVisible(): self.hide_replace() text = self.editor.get_selected_text() # When selecting several lines, and replace box is activated the # text won't be replaced for the selection if hide_replace or len(text.splitlines()) <= 1: highlighted = True # If no text is highlighted for search, use whatever word is # under the cursor if not text: highlighted = False try: cursor = self.editor.textCursor() cursor.select(QTextCursor.WordUnderCursor) text = to_text_string(cursor.selectedText()) except AttributeError: # We can't do this for all widgets, e.g. WebView's pass # Now that text value is sorted out, use it for the search if text and not self.search_text.currentText() or highlighted: self.search_text.setEditText(text) self.search_text.lineEdit().selectAll() self.refresh() else: self.search_text.lineEdit().selectAll() self.search_text.setFocus() @Slot() def hide(self): """Overrides Qt Method""" for widget in self.replace_widgets: widget.hide() QWidget.hide(self) self.visibility_changed.emit(False) if self.editor is not None: self.editor.setFocus() self.clear_matches() def show_replace(self): """Show replace widgets""" if self.enable_replace: self.show(hide_replace=False) for widget in self.replace_widgets: widget.show() def hide_replace(self): """Hide replace widgets""" for widget in self.replace_widgets: widget.hide() def refresh(self): """Refresh widget""" if self.isHidden(): if self.editor is not None: self.clear_matches() return state = self.editor is not None for widget in self.widgets: widget.setEnabled(state) if state: self.find() def set_editor(self, editor, refresh=True): """ Set associated editor/web page: codeeditor.base.TextEditBaseWidget browser.WebView """ self.editor = editor # Note: This is necessary to test widgets/editor.py # in Qt builds that don't have web widgets try: from qtpy.QtWebEngineWidgets import QWebEngineView except ImportError: QWebEngineView = type(None) self.words_button.setVisible(not isinstance(editor, QWebEngineView)) self.re_button.setVisible(not isinstance(editor, QWebEngineView)) from spyder.plugins.editor.widgets.codeeditor import CodeEditor self.is_code_editor = isinstance(editor, CodeEditor) self.highlight_button.setVisible(self.is_code_editor) if refresh: self.refresh() if self.isHidden() and editor is not None: self.clear_matches() @Slot() def find_next(self, set_focus=True): """Find next occurrence""" state = self.find(changed=False, forward=True, rehighlight=False, multiline_replace_check=False) if set_focus: self.editor.setFocus() self.search_text.add_current_text() return state @Slot() def find_previous(self, set_focus=True): """Find previous occurrence""" state = self.find(changed=False, forward=False, rehighlight=False, multiline_replace_check=False) if set_focus: self.editor.setFocus() return state def text_has_been_edited(self, text): """Find text has been edited (this slot won't be triggered when setting the search pattern combo box text programmatically)""" self.find(changed=True, forward=True, start_highlight_timer=True) def highlight_matches(self): """Highlight found results""" if self.is_code_editor and self.highlight_button.isChecked(): text = self.search_text.currentText() case = self.case_button.isChecked() word = self.words_button.isChecked() regexp = self.re_button.isChecked() self.editor.highlight_found_results(text, word=word, regexp=regexp, case=case) def clear_matches(self): """Clear all highlighted matches""" if self.is_code_editor: self.editor.clear_found_results() def find(self, changed=True, forward=True, rehighlight=True, start_highlight_timer=False, multiline_replace_check=True): """Call the find function""" # When several lines are selected in the editor and replace box is # activated, dynamic search is deactivated to prevent changing the # selection. Otherwise we show matching items. if multiline_replace_check and self.replace_widgets[0].isVisible(): sel_text = self.editor.get_selected_text() if len(to_text_string(sel_text).splitlines()) > 1: return None text = self.search_text.currentText() if len(text) == 0: self.search_text.lineEdit().setStyleSheet("") if not self.is_code_editor: # Clears the selection for WebEngine self.editor.find_text('') self.change_number_matches() return None else: case = self.case_button.isChecked() word = self.words_button.isChecked() regexp = self.re_button.isChecked() found = self.editor.find_text(text, changed, forward, case=case, word=word, regexp=regexp) stylesheet = self.STYLE[found] tooltip = self.TOOLTIP[found] if not found and regexp: error_msg = regexp_error_msg(text) if error_msg: # special styling for regexp errors stylesheet = self.STYLE['regexp_error'] tooltip = self.TOOLTIP['regexp_error'] + ': ' + error_msg self.search_text.lineEdit().setStyleSheet(stylesheet) self.search_text.setToolTip(tooltip) if self.is_code_editor and found: block = self.editor.textCursor().block() TextHelper(self.editor).unfold_if_colapsed(block) if rehighlight or not self.editor.found_results: self.highlight_timer.stop() if start_highlight_timer: self.highlight_timer.start() else: self.highlight_matches() else: self.clear_matches() number_matches = self.editor.get_number_matches(text, case=case, regexp=regexp, word=word) if hasattr(self.editor, 'get_match_number'): match_number = self.editor.get_match_number(text, case=case, regexp=regexp, word=word) else: match_number = 0 self.change_number_matches(current_match=match_number, total_matches=number_matches) return found
def start_glue(gluefile=None, config=None, datafiles=None, maximized=True): """Run a glue session and exit Parameters ---------- gluefile : str An optional ``.glu`` file to restore. config : str An optional configuration file to use. datafiles : str An optional list of data files to load. maximized : bool Maximize screen on startup. Otherwise, use default size. """ import glue from glue.utils.qt import get_qapp app = get_qapp() splash = get_splash() splash.show() # Start off by loading plugins. We need to do this before restoring # the session or loading the configuration since these may use existing # plugins. load_plugins(splash=splash) from glue.app.qt import GlueApplication datafiles = datafiles or [] hub = None if gluefile is not None: app = restore_session(gluefile) return app.start() if config is not None: glue.env = glue.config.load_configuration(search_path=[config]) data_collection = glue.core.DataCollection() hub = data_collection.hub splash.set_progress(100) session = glue.core.Session(data_collection=data_collection, hub=hub) ga = GlueApplication(session=session) from qtpy.QtCore import QTimer timer = QTimer() timer.setInterval(1000) timer.setSingleShot(True) timer.timeout.connect(splash.close) timer.start() if datafiles: datasets = load_data_files(datafiles) ga.add_datasets(data_collection, datasets) return ga.start(maximized=maximized)
class _DownloadAPI(QObject): """Download API based on QNetworkAccessManager.""" def __init__(self, chunk_size=1024, load_rc_func=None): """Download API based on QNetworkAccessManager.""" super(_DownloadAPI, self).__init__() self._chunk_size = chunk_size self._head_requests = {} self._get_requests = {} self._paths = {} self._workers = {} self._load_rc_func = load_rc_func self._manager = QNetworkAccessManager(self) self._proxy_factory = NetworkProxyFactory(load_rc_func=load_rc_func) self._timer = QTimer() # Setup self._manager.setProxyFactory(self._proxy_factory) self._timer.setInterval(1000) self._timer.timeout.connect(self._clean) # Signals self._manager.finished.connect(self._request_finished) self._manager.sslErrors.connect(self._handle_ssl_errors) self._manager.proxyAuthenticationRequired.connect( self._handle_proxy_auth) @staticmethod def _handle_ssl_errors(reply, errors): """Callback for ssl_errors.""" logger.error(str(('SSL Errors', errors, reply))) @staticmethod def _handle_proxy_auth(proxy, authenticator): """Callback for ssl_errors.""" # authenticator.setUser('1')` # authenticator.setPassword('1') logger.error(str(('Proxy authentication Error. ' 'Enter credentials in condarc', proxy, authenticator))) def _clean(self): """Check for inactive workers and remove their references.""" if self._workers: for url in self._workers.copy(): w = self._workers[url] if w.is_finished(): self._workers.pop(url) self._paths.pop(url) if url in self._get_requests: self._get_requests.pop(url) else: self._timer.stop() def _request_finished(self, reply): """Callback for download once the request has finished.""" url = to_text_string(reply.url().toEncoded(), encoding='utf-8') if url in self._paths: path = self._paths[url] if url in self._workers: worker = self._workers[url] if url in self._head_requests: error = reply.error() # print(url, error) if error: logger.error(str(('Head Reply Error:', error))) worker.sig_download_finished.emit(url, path) worker.sig_finished.emit(worker, path, error) return self._head_requests.pop(url) start_download = not bool(error) header_pairs = reply.rawHeaderPairs() headers = {} for hp in header_pairs: headers[to_text_string(hp[0]).lower()] = to_text_string(hp[1]) total_size = int(headers.get('content-length', 0)) # Check if file exists if os.path.isfile(path): file_size = os.path.getsize(path) # Check if existing file matches size of requested file start_download = file_size != total_size if start_download: # File sizes dont match, hence download file qurl = QUrl(url) request = QNetworkRequest(qurl) self._get_requests[url] = request reply = self._manager.get(request) error = reply.error() if error: logger.error(str(('Reply Error:', error))) reply.downloadProgress.connect( lambda r, t, w=worker: self._progress(r, t, w)) else: # File sizes match, dont download file or error? worker.finished = True worker.sig_download_finished.emit(url, path) worker.sig_finished.emit(worker, path, None) elif url in self._get_requests: data = reply.readAll() self._save(url, path, data) def _save(self, url, path, data): """Save `data` of downloaded `url` in `path`.""" worker = self._workers[url] path = self._paths[url] if len(data): try: with open(path, 'wb') as f: f.write(data) except Exception: logger.error((url, path)) # Clean up worker.finished = True worker.sig_download_finished.emit(url, path) worker.sig_finished.emit(worker, path, None) self._get_requests.pop(url) self._workers.pop(url) self._paths.pop(url) @staticmethod def _progress(bytes_received, bytes_total, worker): """Return download progress.""" worker.sig_download_progress.emit( worker.url, worker.path, bytes_received, bytes_total) def download(self, url, path): """Download url and save data to path.""" # original_url = url # print(url) qurl = QUrl(url) url = to_text_string(qurl.toEncoded(), encoding='utf-8') logger.debug(str((url, path))) if url in self._workers: while not self._workers[url].finished: return self._workers[url] worker = DownloadWorker(url, path) # Check download folder exists folder = os.path.dirname(os.path.abspath(path)) if not os.path.isdir(folder): os.makedirs(folder) request = QNetworkRequest(qurl) self._head_requests[url] = request self._paths[url] = path self._workers[url] = worker self._manager.head(request) self._timer.start() return worker def terminate(self): """Terminate all download workers and threads.""" pass
class _RequestsDownloadAPI(QObject): """Download API based on requests.""" _sig_download_finished = Signal(str, str) _sig_download_progress = Signal(str, str, int, int) def __init__(self, load_rc_func=None): """Download API based on requests.""" super(QObject, self).__init__() self._conda_api = CondaAPI() self._queue = deque() self._threads = [] self._workers = [] self._timer = QTimer() self._load_rc_func = load_rc_func self._chunk_size = 1024 self._timer.setInterval(1000) self._timer.timeout.connect(self._clean) @property def proxy_servers(self): """Return the proxy servers available from the conda rc config file.""" if self._load_rc_func is None: return {} else: return self._load_rc_func().get('proxy_servers', {}) def _clean(self): """Check for inactive workers and remove their references.""" if self._workers: for w in self._workers: if w.is_finished(): self._workers.remove(w) if self._threads: for t in self._threads: if t.isFinished(): self._threads.remove(t) else: self._timer.stop() def _start(self): """Start the next threaded worker in the queue.""" if len(self._queue) == 1: thread = self._queue.popleft() thread.start() self._timer.start() def _create_worker(self, method, *args, **kwargs): """Create a new worker instance.""" thread = QThread() worker = RequestsDownloadWorker(method, args, kwargs) worker.moveToThread(thread) worker.sig_finished.connect(self._start) self._sig_download_finished.connect(worker.sig_download_finished) self._sig_download_progress.connect(worker.sig_download_progress) worker.sig_finished.connect(thread.quit) thread.started.connect(worker.start) self._queue.append(thread) self._threads.append(thread) self._workers.append(worker) self._start() return worker def _download(self, url, path=None, force=False): """Callback for download.""" if path is None: path = url.split('/')[-1] # Make dir if non existent folder = os.path.dirname(os.path.abspath(path)) if not os.path.isdir(folder): os.makedirs(folder) # Start actual download try: r = requests.get(url, stream=True, proxies=self.proxy_servers) except Exception as error: print('ERROR', 'here', error) logger.error(str(error)) # Break if error found! # self._sig_download_finished.emit(url, path) # return path total_size = int(r.headers.get('Content-Length', 0)) # Check if file exists if os.path.isfile(path) and not force: file_size = os.path.getsize(path) # Check if existing file matches size of requested file if file_size == total_size: self._sig_download_finished.emit(url, path) return path # File not found or file size did not match. Download file. progress_size = 0 with open(path, 'wb') as f: for chunk in r.iter_content(chunk_size=self._chunk_size): if chunk: f.write(chunk) progress_size += len(chunk) self._sig_download_progress.emit(url, path, progress_size, total_size) self._sig_download_finished.emit(url, path) return path def _is_valid_url(self, url): """Callback for is_valid_url.""" try: r = requests.head(url, proxies=self.proxy_servers) value = r.status_code in [200] except Exception as error: logger.error(str(error)) value = False return value def _is_valid_channel(self, channel, conda_url='https://conda.anaconda.org'): """Callback for is_valid_channel.""" if channel.startswith('https://') or channel.startswith('http://'): url = channel else: url = "{0}/{1}".format(conda_url, channel) if url[-1] == '/': url = url[:-1] plat = self._conda_api.get_platform() repodata_url = "{0}/{1}/{2}".format(url, plat, 'repodata.json') try: r = requests.head(repodata_url, proxies=self.proxy_servers) value = r.status_code in [200] except Exception as error: logger.error(str(error)) value = False return value def _is_valid_api_url(self, url): """Callback for is_valid_api_url.""" # Check response is a JSON with ok: 1 data = {} try: r = requests.get(url, proxies=self.proxy_servers) content = to_text_string(r.content, encoding='utf-8') data = json.loads(content) except Exception as error: logger.error(str(error)) return data.get('ok', 0) == 1 # --- Public API # ------------------------------------------------------------------------- def download(self, url, path=None, force=False): """Download file given by url and save it to path.""" logger.debug(str((url, path, force))) method = self._download return self._create_worker(method, url, path=path, force=force) def terminate(self): """Terminate all workers and threads.""" for t in self._threads: t.quit() self._thread = [] self._workers = [] def is_valid_url(self, url, non_blocking=True): """Check if url is valid.""" logger.debug(str((url))) if non_blocking: method = self._is_valid_url return self._create_worker(method, url) else: return self._is_valid_url(url) def is_valid_api_url(self, url, non_blocking=True): """Check if anaconda api url is valid.""" logger.debug(str((url))) if non_blocking: method = self._is_valid_api_url return self._create_worker(method, url) else: return self._is_valid_api_url(url=url) def is_valid_channel(self, channel, conda_url='https://conda.anaconda.org', non_blocking=True): """Check if a conda channel is valid.""" logger.debug(str((channel, conda_url))) if non_blocking: method = self._is_valid_channel return self._create_worker(method, channel, conda_url) else: return self._is_valid_channel(channel, conda_url=conda_url) def get_api_info(self, url): """Query anaconda api info.""" data = {} try: r = requests.get(url, proxies=self.proxy_servers) content = to_text_string(r.content, encoding='utf-8') data = json.loads(content) if not data: data['api_url'] = url if 'conda_url' not in data: data['conda_url'] = 'https://conda.anaconda.org' except Exception as error: logger.error(str(error)) return data
def create_app(datafiles=[], data_configs=[], data_configs_show=False, interactive=True): """ Create and initialize a cubeviz application instance Parameters ---------- datafiles : `list` A list of filenames representing data files to be loaded data_configs : `list` A list of filenames representing data configuration files to be used data_configs_show : `bool` Display matching info about data configuration files interactive : `bool` Flag to indicate whether session is interactive or not (i.e. for testing) """ app = get_qapp() # Splash screen if interactive: splash = get_splash() splash.image = QtGui.QPixmap(CUBEVIZ_LOGO_PATH) splash.show() else: splash = None # Start off by loading plugins. We need to do this before restoring # the session or loading the configuration since these may use existing # plugins. load_plugins(splash=splash) dfc_kwargs = dict(remove_defaults=True, check_ifu_valid=interactive) DataFactoryConfiguration(data_configs, data_configs_show, **dfc_kwargs) # Check to make sure each file exists and raise an Exception # that will show in the popup if it does not exist. _check_datafiles_exist(datafiles) # Show the splash screen for 1 second if interactive: timer = QTimer() timer.setInterval(1000) timer.setSingleShot(True) timer.timeout.connect(splash.close) timer.start() data_collection = glue.core.DataCollection() hub = data_collection.hub ga = _create_glue_app(data_collection, hub) ga.run_startup_action('cubeviz') # Load the data files. if datafiles: datasets = load_data_files(datafiles) ga.add_datasets(data_collection, datasets, auto_merge=False) if interactive: splash.set_progress(100) return ga
class ScriptRunner(object): """ Runs a script that interacts with a widget (tests it). If the script is a python generator then after each iteration controls returns to the QApplication's event loop. Generator scripts can yield a positive number. It is treated as the number of seconds before the next iteration is called. During the wait time the event loop is running. """ def __init__(self, script, widget=None, close_on_finish=True, pause=0, is_cli=False): """ Initialise a runner. :param script: The script to run. :param widget: The widget to test. :param close_on_finish: If true close the widget after the script has finished. :param is_cli: If true the script is to be run from a command line tool. Exceptions are treated slightly differently in this case. """ app = get_application() self.script = script self.widget = widget self.close_on_finish = close_on_finish self.pause = pause self.is_cli = is_cli self.error = None self.script_iter = [None] self.pause_timer = QTimer(app) self.pause_timer.setSingleShot(True) self.script_timer = QTimer(app) def run(self): ret = run_script(self.script, self.widget) if isinstance(ret, Exception): raise ret self.script_iter = [iter(ret) if inspect.isgenerator(ret) else None] if self.pause != 0: self.script_timer.setInterval(self.pause * 1000) # Zero-timeout timer runs script_runner() between Qt events self.script_timer.timeout.connect(self, Qt.QueuedConnection) QMetaObject.invokeMethod(self.script_timer, 'start', Qt.QueuedConnection) def __call__(self): app = get_application() if not self.pause_timer.isActive(): try: script_iter = self.script_iter[-1] if script_iter is None: if self.close_on_finish: app.closeAllWindows() app.exit() return # Run test script until the next 'yield' try: ret = next(script_iter) except ValueError: return while ret is not None: if inspect.isgenerator(ret): self.script_iter.append(ret) ret = None elif isinstance(ret, six.integer_types) or isinstance(ret, float): # Start non-blocking pause in seconds self.pause_timer.start(int(ret * 1000)) ret = None else: ret = ret() except StopIteration: if len(self.script_iter) > 1: self.script_iter.pop() else: self.script_iter = [None] self.script_timer.stop() if self.close_on_finish: app.closeAllWindows() app.exit(0) except Exception as e: self.script_iter = [None] traceback.print_exc() if self.close_on_finish: app.exit(1) self.error = e
def start_glue(gluefile=None, config=None, datafiles=None, maximized=True, startup_actions=None, auto_merge=False): """Run a glue session and exit Parameters ---------- gluefile : str An optional ``.glu`` file to restore. config : str An optional configuration file to use. datafiles : str An optional list of data files to load. maximized : bool Maximize screen on startup. Otherwise, use default size. auto_merge : bool, optional Whether to automatically merge data passed in `datafiles` (default is `False`) """ import glue # Some Qt modules are picky in terms of being imported before the # application is set up, so we import them here. We do it here rather # than in get_qapp since in the past, including this code in get_qapp # caused severe issues (e.g. segmentation faults) in plugin packages # during testing. try: from qtpy import QtWebEngineWidgets # noqa except ImportError: # Not all PyQt installations have this module pass from glue.utils.qt.decorators import die_on_error from glue.utils.qt import get_qapp app = get_qapp() splash = get_splash() splash.show() # Start off by loading plugins. We need to do this before restoring # the session or loading the configuration since these may use existing # plugins. load_plugins(splash=splash, require_qt_plugins=True) from glue.app.qt import GlueApplication datafiles = datafiles or [] hub = None from qtpy.QtCore import QTimer timer = QTimer() timer.setInterval(1000) timer.setSingleShot(True) timer.timeout.connect(splash.close) timer.start() if gluefile is not None: with die_on_error("Error restoring Glue session"): app = GlueApplication.restore_session(gluefile, show=False) return app.start(maximized=maximized) if config is not None: glue.env = glue.config.load_configuration(search_path=[config]) data_collection = glue.core.DataCollection() hub = data_collection.hub splash.set_progress(100) session = glue.core.Session(data_collection=data_collection, hub=hub) ga = GlueApplication(session=session) if datafiles: with die_on_error("Error reading data file"): datasets = load_data_files(datafiles) ga.add_datasets(datasets, auto_merge=auto_merge) if startup_actions is not None: for name in startup_actions: ga.run_startup_action(name) return ga.start(maximized=maximized)
class GarbageCollector(QObject): ''' Disable automatic garbage collection and instead collect manually on a timer. This is done to ensure that garbage collection only happens in the GUI thread, as otherwise Qt can crash. Parameters ========== @param interval float: timeout interval in seconds. Default: 1s @param debug bool: debug output. Default: False Version history: - Original: Credit: Erik Janssens Source: http://pydev.blogspot.com/2014/03/should-python-garbage-collector-be.html - Modified: pyqtgraph - Modified: qudi ''' def __init__(self, interval=1.0, debug=False): """ Initializes garbage collector @param interval float: timeout interval in seconds. Default: 1s @param debug bool: debug output. Default: False """ super().__init__() self.debug = debug if debug: gc.set_debug(gc.DEBUG_LEAK) self.timer = QTimer() self.timer.timeout.connect(self.check) self.threshold = gc.get_threshold() gc.disable() self.timer.start(interval * 1000) @Slot() def check(self): """ Method called by the garbage collector timer to check if there is something to collect. """ # return self.debug_cycles() # uncomment to just debug cycles l0, l1, l2 = gc.get_count() if self.debug: logger.debug('gc_check called: {0} {1} {2}'.format(l0, l1, l2)) if l0 > self.threshold[0]: num = gc.collect(0) if self.debug: logger.debug('collecting gen 0, found: {0:d} unreachable' ''.format(num)) if l1 > self.threshold[1]: num = gc.collect(1) if self.debug: logger.debug('collecting gen 1, found: {0:d} unreachable' ''.format(num)) if l2 > self.threshold[2]: num = gc.collect(2) if self.debug: logger.debug('collecting gen 2, found: {0:d} ' 'unreachable'.format(num)) def debug_cycles(self): """ Method called in check() to debug cycles. Uncomment the corresponding line in the implementation of check(). """ gc.collect() for obj in gc.garbage: print(obj, repr(obj), type(obj))
class QtInteractor(QVTKRenderWindowInteractor, BasePlotter): """Extend QVTKRenderWindowInteractor class. This adds the methods available to pyvista.Plotter. Parameters ---------- parent : Qt parent. title : Title of plotting window. multi_samples : The number of multi-samples used to mitigate aliasing. 4 is a good default but 8 will have better results with a potential impact on performance. line_smoothing : If True, enable line smothing point_smoothing : If True, enable point smothing polygon_smoothing : If True, enable polygon smothing auto_update : Automatic update rate in seconds. Useful for automatically updating the render window when actors are change without being automatically ``Modified``. """ # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-statements # Signals must be class attributes render_signal = Signal() key_press_event_signal = Signal(vtkGenericRenderWindowInteractor, str) # pylint: disable=too-many-arguments def __init__( self, parent: MainWindow = None, title: str = None, off_screen: bool = None, multi_samples: int = None, line_smoothing: bool = False, point_smoothing: bool = False, polygon_smoothing: bool = False, auto_update: Union[float, bool] = 5.0, **kwargs: Any, ) -> None: # pylint: disable=too-many-branches """Initialize Qt interactor.""" LOG.debug("QtInteractor init start") self.url: QtCore.QUrl = None # Cannot use super() here because # QVTKRenderWindowInteractor silently swallows all kwargs # because they use **kwargs in their constructor... qvtk_kwargs = dict(parent=parent) for key in ("stereo", "iren", "rw", "wflags"): if key in kwargs: qvtk_kwargs[key] = kwargs.pop(key) with _no_base_plotter_init(): QVTKRenderWindowInteractor.__init__(self, **qvtk_kwargs) BasePlotter.__init__(self, **kwargs) # backward compat for when we had this as a separate class self.interactor = self if multi_samples is None: multi_samples = global_theme.multi_samples self.setAcceptDrops(True) # Create and start the interactive renderer self.ren_win = self.GetRenderWindow() self.ren_win.SetMultiSamples(multi_samples) if line_smoothing: self.ren_win.LineSmoothingOn() if point_smoothing: self.ren_win.PointSmoothingOn() if polygon_smoothing: self.ren_win.PolygonSmoothingOn() for renderer in self.renderers: renderer.view_isometric() self.ren_win.AddRenderer(renderer) self.render_signal.connect(self._render) self.key_press_event_signal.connect(super().key_press_event) self.background_color = global_theme.background if self.title: self.setWindowTitle(title) if off_screen is None: off_screen = pyvista.OFF_SCREEN self._setup_interactor(off_screen) if off_screen: self.ren_win.SetOffScreenRendering(1) else: self._setup_key_press() # Make the render timer but only activate if using auto update self.render_timer = QTimer(parent=parent) if float(auto_update) > 0.0: # Can be False as well # Spawn a thread that updates the render window. # Sometimes directly modifiying object data doesn't trigger # Modified() and upstream objects won't be updated. This # ensures the render window stays updated without consuming too # many resources. twait = int((auto_update ** -1) * 1000.0) self.render_timer.timeout.connect(self.render) self.render_timer.start(twait) if global_theme.depth_peeling["enabled"]: if self.enable_depth_peeling(): for renderer in self.renderers: renderer.enable_depth_peeling() self._first_time = False # Crucial! LOG.debug("QtInteractor init stop") def _setup_interactor(self, off_screen: bool) -> None: if off_screen: self.iren: Any = None else: try: # pylint: disable=import-outside-toplevel from pyvista.plotting.render_window_interactor import ( RenderWindowInteractor, ) self.iren = RenderWindowInteractor( self, interactor=self.ren_win.GetInteractor() ) self.iren.interactor.RemoveObservers( "MouseMoveEvent" ) # slows window update? self.iren.initialize() except ImportError: self.iren = self.ren_win.GetInteractor() self.iren.RemoveObservers("MouseMoveEvent") # slows window update? self.iren.Initialize() self.enable_trackball_style() def _setup_key_press(self) -> None: try: self._observers: Dict[ None, None ] = {} # Map of events to observers of self.iren self.iren.add_observer("KeyPressEvent", self.key_press_event) except AttributeError: self._add_observer("KeyPressEvent", self.key_press_event) self.reset_key_events() def gesture_event(self, event: QGestureEvent) -> bool: """Handle gesture events.""" pinch = event.gesture(QtCore.Qt.PinchGesture) if pinch: self.camera.Zoom(pinch.scaleFactor()) event.accept() return True def key_press_event(self, obj: Any, event: Any) -> None: """Call `key_press_event` using a signal.""" self.key_press_event_signal.emit(obj, event) @wraps(BasePlotter.render) def _render(self, *args: Any, **kwargs: Any) -> BasePlotter.render: """Wrap ``BasePlotter.render``.""" return BasePlotter.render(self, *args, **kwargs) @conditional_decorator(threaded, platform.system() == "Darwin") def render(self) -> None: """Override the ``render`` method to handle threading issues.""" return self.render_signal.emit() @wraps(BasePlotter.enable) def enable(self) -> None: """Wrap ``BasePlotter.enable``.""" self.setEnabled(True) return BasePlotter.enable(self) @wraps(BasePlotter.disable) def disable(self) -> None: """Wrap ``BasePlotter.disable``.""" self.setDisabled(True) return BasePlotter.disable(self) # pylint: disable=invalid-name,no-self-use def dragEnterEvent(self, event: QtGui.QDragEnterEvent) -> None: """Event is called when something is dropped onto the vtk window. Only triggers event when event contains file paths that exist. User can drop anything in this window and we only want to allow files. """ # pragma: no cover try: for url in event.mimeData().urls(): if os.path.isfile(url.path()): # only call accept on files event.accept() except IOError as exception: # pragma: no cover warnings.warn("Exception when dropping files: %s" % str(exception)) # pylint: disable=invalid-name,useless-return def dropEvent(self, event: QtCore.QEvent) -> None: """Event is called after dragEnterEvent.""" for url in event.mimeData().urls(): # pragma: no cover self.url = url filename = self.url.path() if os.path.isfile(filename): try: self.add_mesh(pyvista.read(filename)) except IOError as exception: print(str(exception)) def close(self) -> None: """Quit application.""" if self._closed: return if hasattr(self, "render_timer"): self.render_timer.stop() BasePlotter.close(self) QVTKRenderWindowInteractor.close(self)
class ExternalShellBase(QWidget): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = None redirect_stdio = Signal(bool) sig_finished = Signal() def __init__(self, parent=None, fname=None, wdir=None, history_filename=None, show_icontext=True, light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): QWidget.__init__(self, parent) self.menu_actions = menu_actions self.run_button = None self.kill_button = None self.options_button = None self.icontext_action = None self.show_elapsed_time = show_elapsed_time self.fname = fname if wdir is None: wdir = osp.dirname(osp.abspath(fname)) self.wdir = wdir if osp.isdir(wdir) else None self.arguments = "" self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename)) self.shell.set_light_background(light_background) self.shell.execute.connect(self.send_to_process) self.shell.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Redirecting some SIGNALs: self.shell.redirect_stdio.connect( lambda state: self.redirect_stdio.emit(state)) self.state_label = None self.time_label = None vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() if show_buttons_inside: self.state_label = QLabel() hlayout = QHBoxLayout() hlayout.addWidget(self.state_label) hlayout.addStretch(0) hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) else: vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.get_shell_widget()) self.setLayout(vlayout) self.resize(640, 480) if parent is None: self.setWindowIcon(self.get_icon()) self.setWindowTitle(_("Console")) self.t0 = None self.timer = QTimer(self) self.process = None self.is_closing = False if show_buttons_inside: self.update_time_label_visibility() @Slot(bool) def set_elapsed_time_visible(self, state): self.show_elapsed_time = state if self.time_label is not None: self.time_label.setVisible(state) def create_time_label(self): """Create elapsed time label widget (if necessary) and return it""" if self.time_label is None: self.time_label = QLabel() return self.time_label def update_time_label_visibility(self): self.time_label.setVisible(self.show_elapsed_time) def is_running(self): if self.process is not None: return self.process.state() == QProcess.Running def get_toolbar_buttons(self): if self.run_button is None: self.run_button = create_toolbutton(self, text=_("Run"), icon=ima.icon('run'), tip=_("Run again this program"), triggered=self.start_shell) if self.kill_button is None: self.kill_button = create_toolbutton(self, text=_("Kill"), icon=ima.icon('kill'), tip=_("Kills the current process, " "causing it to exit immediately")) buttons = [self.run_button] if self.options_button is None: options = self.get_options_menu() if options: self.options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) self.options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, options) self.options_button.setMenu(menu) if self.options_button is not None: buttons.append(self.options_button) buttons.append(self.kill_button) return buttons def set_icontext_visible(self, state): """Set icon text visibility""" for widget in self.get_toolbar_buttons(): if state: widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) else: widget.setToolButtonStyle(Qt.ToolButtonIconOnly) def get_options_menu(self): self.show_time_action = create_action(self, _("Show elapsed time"), toggled=self.set_elapsed_time_visible) self.show_time_action.setChecked(self.show_elapsed_time) actions = [self.show_time_action] if self.menu_actions is not None: actions += [None]+self.menu_actions return actions def get_shell_widget(self): return self.shell def get_icon(self): raise NotImplementedError def show_time(self, end=False): if self.time_label is None: return elapsed_time = time()-self.t0 if elapsed_time > 24*3600: # More than a day...! format = "%d %H:%M:%S" else: format = "%H:%M:%S" if end: color = "#AAAAAA" else: color = "#AA6655" text = "<span style=\'color: %s\'><b>%s" \ "</b></span>" % (color, strftime(format, gmtime(elapsed_time))) self.time_label.setText(text) def closeEvent(self, event): if self.process is not None: self.is_closing = True self.process.kill() self.process.waitForFinished(100) try: self.timer.timeout.disconnect(self.show_time) except (RuntimeError, TypeError): pass def set_running_state(self, state=True): self.set_buttons_runnning_state(state) self.shell.setReadOnly(not state) if state: if self.state_label is not None: self.state_label.setText(_( "<span style=\'color: #44AA44\'><b>Running...</b></span>")) self.t0 = time() self.timer.timeout.connect(self.show_time) self.timer.start(1000) else: if self.state_label is not None: self.state_label.setText(_('Terminated.')) try: self.timer.timeout.disconnect(self.show_time) except (RuntimeError, TypeError): pass def set_buttons_runnning_state(self, state): self.run_button.setVisible(not state and not self.is_ipykernel) self.kill_button.setVisible(state) @Slot(bool) def start_shell(self, ask_for_arguments=False): """Start shell""" if ask_for_arguments and not self.get_arguments(): self.set_running_state(False) return try: self.terminate_button.clicked.disconnect(self.process.terminate) self.kill_button.clicked.disconnect(self.process.terminate) except (AttributeError, RuntimeError, TypeError): pass self.create_process() @Slot() def get_arguments(self): arguments, valid = QInputDialog.getText(self, _('Arguments'), _('Command line arguments:'), QLineEdit.Normal, self.arguments) if valid: self.arguments = to_text_string(arguments) return valid def create_process(self): raise NotImplementedError def finished(self, exit_code, exit_status): self.shell.flush() self.sig_finished.emit() if self.is_closing: return self.set_running_state(False) self.show_time(end=True) #=============================================================================== # Input/Output #=============================================================================== def transcode(self, qba): try: return to_text_string(qba.data(), 'utf8') except UnicodeDecodeError: return qba.data() def get_stdout(self): self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): qba += self.process.readAllStandardOutput() return self.transcode(qba) def get_stderr(self): self.process.setReadChannel(QProcess.StandardError) qba = QByteArray() while self.process.bytesAvailable(): qba += self.process.readAllStandardError() return self.transcode(qba) def write_output(self): self.shell.write(self.get_stdout(), flush=True) # Commenting the line below improves crashes on long # output. See Issue 2251 # QApplication.processEvents() def send_to_process(self, qstr): raise NotImplementedError def send_ctrl_to_process(self, letter): char = chr("abcdefghijklmnopqrstuvwxyz".index(letter) + 1) byte_array = QByteArray() byte_array.append(char) self.process.write(byte_array) self.process.waitForBytesWritten(-1) self.shell.write(LOCALE_CODEC.toUnicode(byte_array), flush=True) def keyboard_interrupt(self): raise NotImplementedError
class AutosaveForPlugin(object): """Component of editor plugin implementing autosave functionality.""" # Interval (in ms) between two autosaves DEFAULT_AUTOSAVE_INTERVAL = 60 * 1000 def __init__(self, editor): """ Constructor. Autosave is disabled after construction and needs to be enabled explicitly if required. Args: editor (Editor): editor plugin. """ self.editor = editor self.timer = QTimer(self.editor) self.timer.setSingleShot(True) self.timer.timeout.connect(self.do_autosave) self._enabled = False # Can't use setter here self._interval = self.DEFAULT_AUTOSAVE_INTERVAL @property def enabled(self): """ Get or set whether autosave component is enabled. The setter will start or stop the autosave component if appropriate. """ return self._enabled @enabled.setter def enabled(self, new_enabled): if new_enabled == self.enabled: return self.stop_autosave_timer() self._enabled = new_enabled self.start_autosave_timer() @property def interval(self): """ Interval between two autosaves, in milliseconds. The setter will perform an autosave if the interval is changed and autosave is enabled. """ return self._interval @interval.setter def interval(self, new_interval): if new_interval == self.interval: return self.stop_autosave_timer() self._interval = new_interval if self.enabled: self.do_autosave() def start_autosave_timer(self): """ Start a timer which calls do_autosave() after `self.interval`. The autosave timer is only started if autosave is enabled. """ if self.enabled: self.timer.start(self.interval) def stop_autosave_timer(self): """Stop the autosave timer.""" self.timer.stop() def do_autosave(self): """Instruct current editorstack to autosave files where necessary.""" logger.debug('Autosave triggered') stack = self.editor.get_current_editorstack() stack.autosave.autosave_all() self.start_autosave_timer() def try_recover_from_autosave(self): """Offer to recover files from autosave.""" autosave_dir = get_conf_path('autosave') autosave_mapping = CONF.get('editor', 'autosave_mapping', {}) dialog = RecoveryDialog(autosave_dir, autosave_mapping, parent=self.editor) dialog.exec_if_nonempty() self.recover_files_to_open = dialog.files_to_open[:]
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin): """ Shell base widget """ redirect_stdio = Signal(bool) sig_keyboard_interrupt = Signal() execute = Signal(str) append_to_history = Signal(str, str) def __init__(self, parent, history_filename, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert is_text_string(history_filename) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Cursor width self.setCursorWidth( CONF.get('main', 'cursor/width') ) def toggle_wrap_mode(self, enable): """Enable/disable wrap mode""" self.set_wrap_mode('character' if enable else None) def set_font(self, font): """Set shell styles font""" self.setFont(font) self.set_pythonshell_font(font) cursor = self.textCursor() cursor.select(QTextCursor.Document) charformat = QTextCharFormat() charformat.setFontFamily(font.family()) charformat.setFontPointSize(font.pointSize()) cursor.mergeCharFormat(charformat) #------ Context menu def setup_context_menu(self): """Setup shell context menu""" self.menu = QMenu(self) self.cut_action = create_action(self, _("Cut"), shortcut=keybinding('Cut'), icon=ima.icon('editcut'), triggered=self.cut) self.copy_action = create_action(self, _("Copy"), shortcut=keybinding('Copy'), icon=ima.icon('editcopy'), triggered=self.copy) paste_action = create_action(self, _("Paste"), shortcut=keybinding('Paste'), icon=ima.icon('editpaste'), triggered=self.paste) save_action = create_action(self, _("Save history log..."), icon=ima.icon('filesave'), tip=_("Save current history log (i.e. all " "inputs and outputs) in a text file"), triggered=self.save_historylog) self.delete_action = create_action(self, _("Delete"), shortcut=keybinding('Delete'), icon=ima.icon('editdelete'), triggered=self.delete) selectall_action = create_action(self, _("Select All"), shortcut=keybinding('SelectAll'), icon=ima.icon('selectall'), triggered=self.selectAll) add_actions(self.menu, (self.cut_action, self.copy_action, paste_action, self.delete_action, None, selectall_action, None, save_action) ) def contextMenuEvent(self, event): """Reimplement Qt method""" state = self.has_selected_text() self.copy_action.setEnabled(state) self.cut_action.setEnabled(state) self.delete_action.setEnabled(state) self.menu.popup(event.globalPos()) event.accept() #------ Input buffer def get_current_line_from_cursor(self): return self.get_text('cursor', 'eof') def _select_input(self): """Select current line (without selecting console prompt)""" line, index = self.get_position('eof') if self.current_prompt_pos is None: pline, pindex = line, index else: pline, pindex = self.current_prompt_pos self.setSelection(pline, pindex, line, index) @Slot() def clear_line(self): """Clear current line (without clearing console prompt)""" if self.current_prompt_pos is not None: self.remove_text(self.current_prompt_pos, 'eof') @Slot() def clear_terminal(self): """ Clear terminal window Child classes reimplement this method to write prompt """ self.clear() # The buffer being edited def _set_input_buffer(self, text): """Set input buffer""" if self.current_prompt_pos is not None: self.replace_text(self.current_prompt_pos, 'eol', text) else: self.insert(text) self.set_cursor_position('eof') def _get_input_buffer(self): """Return input buffer""" input_buffer = '' if self.current_prompt_pos is not None: input_buffer = self.get_text(self.current_prompt_pos, 'eol') input_buffer = input_buffer.replace(os.linesep, '\n') return input_buffer input_buffer = Property("QString", _get_input_buffer, _set_input_buffer) #------ Prompt def new_prompt(self, prompt): """ Print a new prompt and save its (line, index) position """ if self.get_cursor_line_column()[1] != 0: self.write('\n') self.write(prompt, prompt=True) # now we update our cursor giving end of prompt self.current_prompt_pos = self.get_position('cursor') self.ensureCursorVisible() self.new_input_line = False def check_selection(self): """ Check if selected text is r/w, otherwise remove read-only parts of selection """ if self.current_prompt_pos is None: self.set_cursor_position('eof') else: self.truncate_selection(self.current_prompt_pos) #------ Copy / Keyboard interrupt @Slot() def copy(self): """Copy text to clipboard... or keyboard interrupt""" if self.has_selected_text(): ConsoleBaseWidget.copy(self) elif not sys.platform == 'darwin': self.interrupt() def interrupt(self): """Keyboard interrupt""" self.sig_keyboard_interrupt.emit() @Slot() def cut(self): """Cut text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.cut(self) @Slot() def delete(self): """Remove selected text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.remove_selected_text(self) @Slot() def save_historylog(self): """Save current history log (all text in console)""" title = _("Save history log") self.redirect_stdio.emit(False) filename, _selfilter = getsavefilename(self, title, self.historylog_filename, "%s (*.log)" % _("History logs")) self.redirect_stdio.emit(True) if filename: filename = osp.normpath(filename) try: encoding.write(to_text_string(self.get_text_with_eol()), filename) self.historylog_filename = filename CONF.set('main', 'historylog_filename', filename) except EnvironmentError as error: QMessageBox.critical(self, title, _("<b>Unable to save file '%s'</b>" "<br><br>Error message:<br>%s" ) % (osp.basename(filename), to_text_string(error))) #------ Basic keypress event handler def on_enter(self, command): """on_enter""" self.execute_command(command) def execute_command(self, command): self.execute.emit(command) self.add_to_history(command) self.new_input_line = True def on_new_line(self): """On new input line""" self.set_cursor_position('eof') self.current_prompt_pos = self.get_position('cursor') self.new_input_line = False @Slot() def paste(self): """Reimplemented slot to handle multiline paste action""" if self.new_input_line: self.on_new_line() ConsoleBaseWidget.paste(self) def keyPressEvent(self, event): """ Reimplement Qt Method Basic keypress event handler (reimplemented in InternalShell to add more sophisticated features) """ if self.preprocess_keyevent(event): # Event was accepted in self.preprocess_keyevent return self.postprocess_keyevent(event) def preprocess_keyevent(self, event): """Pre-process keypress event: return True if event is accepted, false otherwise""" # Copy must be done first to be able to copy read-only text parts # (otherwise, right below, we would remove selection # if not on current line) ctrl = event.modifiers() & Qt.ControlModifier meta = event.modifiers() & Qt.MetaModifier # meta=ctrl in OSX if event.key() == Qt.Key_C and \ ((Qt.MetaModifier | Qt.ControlModifier) & event.modifiers()): if meta and sys.platform == 'darwin': self.interrupt() elif ctrl: self.copy() event.accept() return True if self.new_input_line and ( len(event.text()) or event.key() in \ (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ): self.on_new_line() return False def postprocess_keyevent(self, event): """Post-process keypress event: in InternalShell, this is method is called when shell is ready""" event, text, key, ctrl, shift = restore_keyevent(event) # Is cursor on the last line? and after prompt? if len(text): #XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ? if self.has_selected_text(): self.check_selection() self.restrict_cursor_position(self.current_prompt_pos, 'eof') cursor_position = self.get_position('cursor') if key in (Qt.Key_Return, Qt.Key_Enter): if self.is_cursor_on_last_line(): self._key_enter() # add and run selection else: self.insert_text(self.get_selected_text(), at_end=True) elif key == Qt.Key_Insert and not shift and not ctrl: self.setOverwriteMode(not self.overwriteMode()) elif key == Qt.Key_Delete: if self.has_selected_text(): self.check_selection() self.remove_selected_text() elif self.is_cursor_on_last_line(): self.stdkey_clear() elif key == Qt.Key_Backspace: self._key_backspace(cursor_position) elif key == Qt.Key_Tab: self._key_tab() elif key == Qt.Key_Space and ctrl: self._key_ctrl_space() elif key == Qt.Key_Left: if self.current_prompt_pos == cursor_position: # Avoid moving cursor on prompt return method = self.extend_selection_to_next if shift \ else self.move_cursor_to_next method('word' if ctrl else 'character', direction='left') elif key == Qt.Key_Right: if self.is_cursor_at_end(): return method = self.extend_selection_to_next if shift \ else self.move_cursor_to_next method('word' if ctrl else 'character', direction='right') elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl): self._key_home(shift, ctrl) elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl): self._key_end(shift, ctrl) elif key == Qt.Key_Up: if not self.is_cursor_on_last_line(): self.set_cursor_position('eof') y_cursor = self.get_coordinates(cursor_position)[1] y_prompt = self.get_coordinates(self.current_prompt_pos)[1] if y_cursor > y_prompt: self.stdkey_up(shift) else: self.browse_history(backward=True) elif key == Qt.Key_Down: if not self.is_cursor_on_last_line(): self.set_cursor_position('eof') y_cursor = self.get_coordinates(cursor_position)[1] y_end = self.get_coordinates('eol')[1] if y_cursor < y_end: self.stdkey_down(shift) else: self.browse_history(backward=False) elif key in (Qt.Key_PageUp, Qt.Key_PageDown): #XXX: Find a way to do this programmatically instead of calling # widget keyhandler (this won't work if the *event* is coming from # the event queue - i.e. if the busy buffer is ever implemented) ConsoleBaseWidget.keyPressEvent(self, event) elif key == Qt.Key_Escape and shift: self.clear_line() elif key == Qt.Key_Escape: self._key_escape() elif key == Qt.Key_L and ctrl: self.clear_terminal() elif key == Qt.Key_V and ctrl: self.paste() elif key == Qt.Key_X and ctrl: self.cut() elif key == Qt.Key_Z and ctrl: self.undo() elif key == Qt.Key_Y and ctrl: self.redo() elif key == Qt.Key_A and ctrl: self.selectAll() elif key == Qt.Key_Question and not self.has_selected_text(): self._key_question(text) elif key == Qt.Key_ParenLeft and not self.has_selected_text(): self._key_parenleft(text) elif key == Qt.Key_Period and not self.has_selected_text(): self._key_period(text) elif len(text) and not self.isReadOnly(): self.hist_wholeline = False self.insert_text(text) self._key_other(text) else: # Let the parent widget handle the key press event ConsoleBaseWidget.keyPressEvent(self, event) #------ Key handlers def _key_enter(self): command = self.input_buffer self.insert_text('\n', at_end=True) self.on_enter(command) self.flush() def _key_other(self, text): raise NotImplementedError def _key_backspace(self, cursor_position): raise NotImplementedError def _key_tab(self): raise NotImplementedError def _key_ctrl_space(self): raise NotImplementedError def _key_home(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_home(shift, ctrl, self.current_prompt_pos) def _key_end(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_end(shift, ctrl) def _key_pageup(self): raise NotImplementedError def _key_pagedown(self): raise NotImplementedError def _key_escape(self): raise NotImplementedError def _key_question(self, text): raise NotImplementedError def _key_parenleft(self, text): raise NotImplementedError def _key_period(self, text): raise NotImplementedError #------ History Management def load_history(self): """Load history from a .py file in user home directory""" if osp.isfile(self.history_filename): rawhistory, _ = encoding.readlines(self.history_filename) rawhistory = [line.replace('\n', '') for line in rawhistory] if rawhistory[1] != self.INITHISTORY[1]: rawhistory[1] = self.INITHISTORY[1] else: rawhistory = self.INITHISTORY history = [line for line in rawhistory \ if line and not line.startswith('#')] # Truncating history to X entries: while len(history) >= CONF.get('historylog', 'max_entries'): del history[0] while rawhistory[0].startswith('#'): del rawhistory[0] del rawhistory[0] # Saving truncated history: encoding.writelines(rawhistory, self.history_filename) return history def browse_history(self, backward): """Browse history""" if self.is_cursor_before('eol') and self.hist_wholeline: self.hist_wholeline = False tocursor = self.get_current_line_to_cursor() text, self.histidx = self.__find_in_history(tocursor, self.histidx, backward) if text is not None: if self.hist_wholeline: self.clear_line() self.insert_text(text) else: cursor_position = self.get_position('cursor') # Removing text from cursor to the end of the line self.remove_text('cursor', 'eol') # Inserting history text self.insert_text(text) self.set_cursor_position(cursor_position) def __find_in_history(self, tocursor, start_idx, backward): """Find text 'tocursor' in history, from index 'start_idx'""" if start_idx is None: start_idx = len(self.history) # Finding text in history step = -1 if backward else 1 idx = start_idx if len(tocursor) == 0 or self.hist_wholeline: idx += step if idx >= len(self.history) or len(self.history) == 0: return "", len(self.history) elif idx < 0: idx = 0 self.hist_wholeline = True return self.history[idx], idx else: for index in range(len(self.history)): idx = (start_idx+step*(index+1)) % len(self.history) entry = self.history[idx] if entry.startswith(tocursor): return entry[len(tocursor):], idx else: return None, start_idx #------ Simulation standards input/output def write_error(self, text): """Simulate stderr""" self.flush() self.write(text, flush=True, error=True) if DEBUG: STDERR.write(text) def write(self, text, flush=False, error=False, prompt=False): """Simulate stdout and stderr""" if prompt: self.flush() if not is_string(text): # This test is useful to discriminate QStrings from decoded str text = to_text_string(text) self.__buffer.append(text) ts = time.time() if flush or prompt: self.flush(error=error, prompt=prompt) elif ts - self.__timestamp > 0.05: self.flush(error=error) self.__timestamp = ts # Timer to flush strings cached by last write() operation in series self.__flushtimer.start(50) def flush(self, error=False, prompt=False): """Flush buffer, write text to console""" # Fix for Issue 2452 if PY3: try: text = "".join(self.__buffer) except TypeError: text = b"".join(self.__buffer) try: text = text.decode( locale.getdefaultlocale()[1] ) except: pass else: text = "".join(self.__buffer) self.__buffer = [] self.insert_text(text, at_end=True, error=error, prompt=prompt) QCoreApplication.processEvents() self.repaint() # Clear input buffer: self.new_input_line = True #------ Text Insertion def insert_text(self, text, at_end=False, error=False, prompt=False): """ Insert text at the current cursor position or at the end of the command line """ if at_end: # Insert text at the end of the command line self.append_text_to_shell(text, error, prompt) else: # Insert text at current cursor position ConsoleBaseWidget.insert_text(self, text) #------ Re-implemented Qt Methods def focusNextPrevChild(self, next): """ Reimplemented to stop Tab moving to the next window """ if next: return False return ConsoleBaseWidget.focusNextPrevChild(self, next) #------ Drag and drop def dragEnterEvent(self, event): """Drag and Drop - Enter event""" event.setAccepted(event.mimeData().hasFormat("text/plain")) def dragMoveEvent(self, event): """Drag and Drop - Move event""" if (event.mimeData().hasFormat("text/plain")): event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def dropEvent(self, event): """Drag and Drop - Drop event""" if (event.mimeData().hasFormat("text/plain")): text = to_text_string(event.mimeData().text()) if self.new_input_line: self.on_new_line() self.insert_text(text, at_end=True) self.setFocus() event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def drop_pathlist(self, pathlist): """Drop path list""" raise NotImplementedError
class Restarter(QWidget): """Widget in charge of displaying the splash information screen and the error messages. """ def __init__(self): super(Restarter, self).__init__() self.ellipsis = ['', '.', '..', '...', '..', '.'] # Widgets self.timer_ellipsis = QTimer(self) self.splash = QSplashScreen( QPixmap(get_image_path('splash.svg'), 'svg')) # Widget setup self.setVisible(False) font = self.splash.font() font.setPixelSize(10) self.splash.setFont(font) self.splash.show() self.timer_ellipsis.timeout.connect(self.animate_ellipsis) def _show_message(self, text): """Show message on splash screen.""" self.splash.showMessage( text, int(Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute), QColor(Qt.white)) def animate_ellipsis(self): """Animate dots at the end of the splash screen message.""" ellipsis = self.ellipsis.pop(0) text = ' ' * len(ellipsis) + self.splash_text + ellipsis self.ellipsis.append(ellipsis) self._show_message(text) def set_splash_message(self, text): """Sets the text in the bottom of the Splash screen.""" self.splash_text = text self._show_message(text) self.timer_ellipsis.start(500) def launch_error_message(self, error_type, error=None): """Launch a message box with a predefined error message. Parameters ---------- error_type : int [CLOSE_ERROR, RESET_ERROR, RESTART_ERROR] Possible error codes when restarting/reseting spyder. error : Exception Actual Python exception error caught. """ messages = { CLOSE_ERROR: _("It was not possible to close the previous " "Spyder instance.\nRestart aborted."), RESET_ERROR: _("Spyder could not reset to factory " "defaults.\nRestart aborted."), RESTART_ERROR: _("It was not possible to restart Spyder.\n" "Operation aborted.") } titles = { CLOSE_ERROR: _("Spyder exit error"), RESET_ERROR: _("Spyder reset error"), RESTART_ERROR: _("Spyder restart error") } if error: e = error.__repr__() message = messages[error_type] + "\n\n{0}".format(e) else: message = messages[error_type] title = titles[error_type] self.splash.hide() QMessageBox.warning(self, title, message, QMessageBox.Ok) raise RuntimeError(message)
class ProcessWorker(QObject): """Conda worker based on a QProcess for non blocking UI.""" sig_finished = Signal(object, object, object) sig_partial = Signal(object, object, object) def __init__(self, cmd_list, parse=False, pip=False, callback=None, extra_kwargs=None): """Conda worker based on a QProcess for non blocking UI. Parameters ---------- cmd_list : list of str Command line arguments to execute. parse : bool (optional) Parse json from output. pip : bool (optional) Define as a pip command. callback : func (optional) If the process has a callback to process output from comd_list. extra_kwargs : dict Arguments for the callback. """ super(ProcessWorker, self).__init__() self._result = None self._cmd_list = cmd_list self._parse = parse self._pip = pip self._conda = not pip self._callback = callback self._fired = False self._communicate_first = False self._partial_stdout = None self._extra_kwargs = extra_kwargs if extra_kwargs else {} self._timer = QTimer() self._process = QProcess() self._timer.setInterval(150) self._timer.timeout.connect(self._communicate) # self._process.finished.connect(self._communicate) self._process.readyReadStandardOutput.connect(self._partial) def _partial(self): """Callback for partial output.""" raw_stdout = self._process.readAllStandardOutput() stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8) json_stdout = stdout.replace('\n\x00', '') try: json_stdout = json.loads(json_stdout) except Exception: json_stdout = stdout if self._partial_stdout is None: self._partial_stdout = stdout else: self._partial_stdout += stdout self.sig_partial.emit(self, json_stdout, None) def _communicate(self): """Callback for communicate.""" if (not self._communicate_first and self._process.state() == QProcess.NotRunning): self.communicate() elif self._fired: self._timer.stop() def communicate(self): """Retrieve information.""" self._communicate_first = True self._process.waitForFinished() if self._partial_stdout is None: raw_stdout = self._process.readAllStandardOutput() stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8) else: stdout = self._partial_stdout raw_stderr = self._process.readAllStandardError() stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8) result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)] # FIXME: Why does anaconda client print to stderr??? if PY2: stderr = stderr.decode() if 'using anaconda' not in stderr.lower(): if stderr.strip() and self._conda: logger.error('{0}:\nSTDERR:\n{1}\nEND'.format( ' '.join(self._cmd_list), stderr)) elif stderr.strip() and self._pip: logger.error("pip error: {}".format(self._cmd_list)) result[-1] = '' if self._parse and stdout: try: result = json.loads(stdout), result[-1] except Exception as error: result = stdout, str(error) if 'error' in result[0]: if not isinstance(result[0], dict): result = {'error': str(result[0])}, None error = '{0}: {1}'.format(" ".join(self._cmd_list), result[0]['error']) result = result[0], error if self._callback: result = self._callback(result[0], result[-1], **self._extra_kwargs), result[-1] self._result = result self.sig_finished.emit(self, result[0], result[-1]) if result[-1]: logger.error(str(('error', result[-1]))) self._fired = True return result def close(self): """Close the running process.""" self._process.close() def is_finished(self): """Return True if worker has finished processing.""" return self._process.state() == QProcess.NotRunning and self._fired def start(self): """Start process.""" logger.debug(str(' '.join(self._cmd_list))) if not self._fired: self._partial_ouput = None self._process.start(self._cmd_list[0], self._cmd_list[1:]) self._timer.start() else: raise CondaProcessWorker('A Conda ProcessWorker can only run once ' 'per method call.')
class _ProjectAPIWrapper(QObject): """Project API based on conda-kapsel.""" MAX_THREADS = 1 def __init__(self): """Project API based on anaconda-project.""" super(QObject, self).__init__() self._project_api = AnacondaProject() self._queue = deque() self._queue_workers = deque() self._threads = [] self._workers = [] self._timer = QTimer() self._timer_worker_delete = QTimer() self._running_threads = 0 self._bag_collector = deque() # Keeps references to old workers self._chunk_size = 1024 self._timer.setInterval(333) self._timer.timeout.connect(self._start) self._timer_worker_delete.setInterval(5000) self._timer_worker_delete.timeout.connect(self._clean_workers) def _clean_workers(self): """Delete periodically workers in workers bag.""" while self._bag_collector: self._bag_collector.popleft() self._timer_worker_delete.stop() def _start(self): """Start threads and check for inactive workers.""" if self._queue_workers and self._running_threads < self.MAX_THREADS: # print('Queue: {0} Running: {1} Workers: {2} ' # 'Threads: {3}'.format(len(self._queue_workers), # self._running_threads, # len(self._workers), # len(self._threads))) self._running_threads += 1 thread = QThread() worker = self._queue_workers.popleft() worker.moveToThread(thread) worker.sig_finished.connect(thread.quit) thread.started.connect(worker.start) thread.start() self._threads.append(thread) if self._workers: for w in self._workers: if w.is_finished(): self._bag_collector.append(w) self._workers.remove(w) if self._threads: for t in self._threads: if t.isFinished(): self._threads.remove(t) self._running_threads -= 1 if len(self._threads) == 0 and len(self._workers) == 0: self._timer.stop() self._timer_worker_delete.start() def _create_worker(self, method, *args, **kwargs): """Create a new worker instance.""" worker = ProjectWorker(method, args, kwargs) self._workers.append(worker) self._queue_workers.append(worker) self._timer.start() return worker def create_project(self, path, make_directory=False, name=None, icon=None, description=None): """Initialize project given by path.""" logger.debug(str((path))) method = self._project_api.create_project return self._create_worker( method, path, make_directory=make_directory, name=name, icon=icon, description=description, ) def load_project(self, path): """Load project given by path.""" logger.debug(str((path))) return self._project_api.load_project(path) def upload(self, project, site=None, username=None, token=None, log_level=None): """Upload project to repo.""" logger.debug(str((project, site, username))) method = self._project_api.upload return self._create_worker(method, project, site=None, username=None, token=None, log_level=None)
class FindReplace(QWidget): """Find widget""" STYLE = {False: "background-color:rgb(255, 175, 90);", True: ""} visibility_changed = Signal(bool) def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton(self, triggered=self.hide, icon=ima.icon('DialogCloseButton')) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.search_text.valid.connect( lambda state: self.find(changed=False, forward=True, rehighlight=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=ima.icon('ArrowUp')) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=ima.icon('ArrowDown')) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=ima.icon('advanced'), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton(self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_('Replace string')) self.replace_button = create_toolbutton(self, text=_('Replace/find'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find, text_beside_icon=True) self.replace_button.clicked.connect(self.update_replace_combo) self.replace_button.clicked.connect(self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [replace_with, self.replace_text, self.replace_button, self.all_check] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches) def create_shortcuts(self, parent): """Create shortcuts for this widget""" # Configurable findnext = config_shortcut(self.find_next, context='_', name='Find next', parent=parent) findprev = config_shortcut(self.find_previous, context='_', name='Find previous', parent=parent) togglefind = config_shortcut(self.show, context='_', name='Find text', parent=parent) togglereplace = config_shortcut(self.toggle_replace_widgets, context='_', name='Replace text', parent=parent) # Fixed fixed_shortcut("Escape", self, self.hide) return [findnext, findprev, togglefind, togglereplace] def get_shortcut_data(self): """ Returns shortcut data, a list of tuples (shortcut, text, default) shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def update_search_combo(self): self.search_text.lineEdit().returnPressed.emit() def update_replace_combo(self): self.replace_text.lineEdit().returnPressed.emit() def toggle_replace_widgets(self): if self.enable_replace: # Toggle replace widgets if self.replace_widgets[0].isVisible(): self.hide_replace() self.hide() else: self.show_replace() self.replace_text.setFocus() @Slot(bool) def toggle_highlighting(self, state): """Toggle the 'highlight all results' feature""" if self.editor is not None: if state: self.highlight_matches() else: self.clear_matches() def show(self): """Overrides Qt Method""" QWidget.show(self) self.visibility_changed.emit(True) if self.editor is not None: text = self.editor.get_selected_text() # If no text is highlighted for search, use whatever word is under the cursor if not text: cursor = self.editor.textCursor() cursor.select(QTextCursor.WordUnderCursor) text = to_text_string(cursor.selectedText()) # Now that text value is sorted out, use it for the search if text: self.search_text.setEditText(text) self.search_text.lineEdit().selectAll() self.refresh() else: self.search_text.lineEdit().selectAll() self.search_text.setFocus() @Slot() def hide(self): """Overrides Qt Method""" for widget in self.replace_widgets: widget.hide() QWidget.hide(self) self.visibility_changed.emit(False) if self.editor is not None: self.editor.setFocus() self.clear_matches() def show_replace(self): """Show replace widgets""" self.show() for widget in self.replace_widgets: widget.show() def hide_replace(self): """Hide replace widgets""" for widget in self.replace_widgets: widget.hide() def refresh(self): """Refresh widget""" if self.isHidden(): if self.editor is not None: self.clear_matches() return state = self.editor is not None for widget in self.widgets: widget.setEnabled(state) if state: self.find() def set_editor(self, editor, refresh=True): """ Set associated editor/web page: codeeditor.base.TextEditBaseWidget browser.WebView """ self.editor = editor # Note: This is necessary to test widgets/editor.py # in Qt builds that don't have web widgets try: from qtpy.QtWebEngineWidgets import QWebEngineView except ImportError: QWebEngineView = type(None) self.words_button.setVisible(not isinstance(editor, QWebEngineView)) self.re_button.setVisible(not isinstance(editor, QWebEngineView)) from spyderlib.widgets.sourcecode.codeeditor import CodeEditor self.is_code_editor = isinstance(editor, CodeEditor) self.highlight_button.setVisible(self.is_code_editor) if refresh: self.refresh() if self.isHidden() and editor is not None: self.clear_matches() @Slot() def find_next(self): """Find next occurrence""" state = self.find(changed=False, forward=True, rehighlight=False) self.editor.setFocus() self.search_text.add_current_text() return state @Slot() def find_previous(self): """Find previous occurrence""" state = self.find(changed=False, forward=False, rehighlight=False) self.editor.setFocus() return state def text_has_been_edited(self, text): """Find text has been edited (this slot won't be triggered when setting the search pattern combo box text programmatically""" self.find(changed=True, forward=True, start_highlight_timer=True) def highlight_matches(self): """Highlight found results""" if self.is_code_editor and self.highlight_button.isChecked(): text = self.search_text.currentText() words = self.words_button.isChecked() regexp = self.re_button.isChecked() self.editor.highlight_found_results(text, words=words, regexp=regexp) def clear_matches(self): """Clear all highlighted matches""" if self.is_code_editor: self.editor.clear_found_results() def find(self, changed=True, forward=True, rehighlight=True, start_highlight_timer=False): """Call the find function""" text = self.search_text.currentText() if len(text) == 0: self.search_text.lineEdit().setStyleSheet("") return None else: case = self.case_button.isChecked() words = self.words_button.isChecked() regexp = self.re_button.isChecked() found = self.editor.find_text(text, changed, forward, case=case, words=words, regexp=regexp) self.search_text.lineEdit().setStyleSheet(self.STYLE[found]) if self.is_code_editor and found: if rehighlight or not self.editor.found_results: self.highlight_timer.stop() if start_highlight_timer: self.highlight_timer.start() else: self.highlight_matches() else: self.clear_matches() return found @Slot() def replace_find(self): """Replace and find""" if (self.editor is not None): replace_text = to_text_string(self.replace_text.currentText()) search_text = to_text_string(self.search_text.currentText()) pattern = search_text if self.re_button.isChecked() else None case = self.case_button.isChecked() first = True cursor = None while True: if first: # First found seltxt = to_text_string(self.editor.get_selected_text()) cmptxt1 = search_text if case else search_text.lower() cmptxt2 = seltxt if case else seltxt.lower() if self.editor.has_selected_text() and cmptxt1 == cmptxt2: # Text was already found, do nothing pass else: if not self.find(changed=False, forward=True, rehighlight=False): break first = False wrapped = False position = self.editor.get_position('cursor') position0 = position cursor = self.editor.textCursor() cursor.beginEditBlock() else: position1 = self.editor.get_position('cursor') if is_position_inf(position1, position0 + len(replace_text) - len(search_text) + 1): # Identify wrapping even when the replace string # includes part of the search string wrapped = True if wrapped: if position1 == position or \ is_position_sup(position1, position): # Avoid infinite loop: replace string includes # part of the search string break if position1 == position0: # Avoid infinite loop: single found occurrence break position0 = position1 if pattern is None: cursor.removeSelectedText() cursor.insertText(replace_text) else: seltxt = to_text_string(cursor.selectedText()) cursor.removeSelectedText() cursor.insertText(re.sub(pattern, replace_text, seltxt)) if self.find_next(): found_cursor = self.editor.textCursor() cursor.setPosition(found_cursor.selectionStart(), QTextCursor.MoveAnchor) cursor.setPosition(found_cursor.selectionEnd(), QTextCursor.KeepAnchor) else: break if not self.all_check.isChecked(): break self.all_check.setCheckState(Qt.Unchecked) if cursor is not None: cursor.endEditBlock()
class HalPlot(QWidget, HALWidget): """HAL Plot Plot for displaying HAL pin values. Input pin type is HAL type ( float). Up to four HAL pin values can be plotted .. table:: Generated HAL Pins ========================= =========== ========= HAL Pin Name Type Direction ========================= =========== ========= qtpyvcp.seriesXname.in float in ========================= =========== ========= """ def __init__(self, parent=None): super(HalPlot, self).__init__(parent) # HAL sampling frequency parameters self._frequency = 1 # Hz self._timeWindow = 600 # seconds # Internal timestamp for x-axis - data values are ms from when "timestamp" was started self.timestamp = QTime() self.timestamp.start() self._legend = False self._yAxisLabel = 'y label' self._yAxisUnits = 'y units' self._minY = 0 self._maxY = 1 self._s1enable = True self._s1name = "Series 1" self._s1colour = QColor('red') self._s1width = 1 self._s1style = Qt.SolidLine self._s1_pin = None self._s2enable = False self._s2name = "Series 2" self._s2colour = QColor('blue') self._s2width = 1 self._s2style = Qt.SolidLine self._s2_pin = None self._s3enable = False self._s3name = "Series 3" self._s3colour = QColor('green') self._s3width = 1 self._s3style = Qt.SolidLine self._s3_pin = None self._s4enable = False self._s4name = "Series 4" self._s4colour = QColor('yellow') self._s4width = 1 self._s4style = Qt.SolidLine self._s4_pin = None # PyQtGraph stuff self.graph = pg.GraphicsLayoutWidget() self.yAxis = pg.AxisItem(orientation='left') self.yAxis.setLabel(self._yAxisLabel, units=self._yAxisUnits) self.yAxis.setGrid(125) self.plot = self.graph.addPlot(axisItems={'bottom': TimeAxisItem(orientation='bottom'), 'left': self.yAxis}) self.plot.setYRange(self._minY, self._maxY, padding=0.0) self.legend = self.plot.addLegend() self.p1 = pg.PlotCurveItem(name=self._s1name) self.p2 = pg.PlotCurveItem(name=self._s2name) self.p3 = pg.PlotCurveItem(name=self._s3name) self.p4 = pg.PlotCurveItem(name=self._s4name) self.setSeries() self.setData() self.Vlayout = QVBoxLayout(self) self.Vlayout.addWidget(self.graph) # HAL stuff self._typ = "float" self._fmt = "%s" if IN_DESIGNER: return # QTimer self.updatetimer = QTimer(self) self.updatetimer.timeout.connect(self.updateplot) self.updatetimer.start(self._refreshRate) def setSeries(self): # first remove the legend as it does not update correnctly try: self.legend.scene().removeItem(self.legend) except: pass # remove all plot items self.plot.clear() # add the legend and plot itmes if self._legend: self.legend = self.plot.addLegend() if self._s1enable: self.p1 = pg.PlotCurveItem(name=self._s1name) self.plot.addItem(self.p1) self.p1.setPen(QColor(self._s1colour), width=self._s1width, style=self._s1style) if self._s2enable: self.p2 = pg.PlotCurveItem(name=self._s2name) self.plot.addItem(self.p2) self.p2.setPen(QColor(self._s2colour), width=self._s2width, style=self._s2style) if self._s3enable: self.p3 = pg.PlotCurveItem(name=self._s3name) self.plot.addItem(self.p3) self.p3.setPen(QColor(self._s3colour), width=self._s3width, style=self._s3style) if self._s4enable: self.p4 = pg.PlotCurveItem(name=self._s4name) self.plot.addItem(self.p4) self.p4.setPen(QColor(self._s4colour), width=self._s4width, style=self._s4style) def setData(self): # Data stuff self._period = 1.0/self._frequency self._refreshRate = int(self._period * 1000) # sample period in milliseconds self._timeWindowMS = self._timeWindow * 1000 # time window in milliseconds self._bufsize = int(self._timeWindowMS / self._refreshRate) # Data containers: collections.deque is list-like container with fast appends and pops on either end self.x = np.linspace(-self.timeWindow, 0.0, self._bufsize) self.now = self.timestamp.elapsed() self.x_data = deque(np.linspace(self.now-self._timeWindowMS, self.now, self._bufsize),self._bufsize) self.s1 = np.zeros(self._bufsize, dtype=np.float) self.s1_data = deque([0.0] * self._bufsize, self._bufsize) self.s2 = np.zeros(self._bufsize, dtype=np.float) self.s2_data = deque([0.0] * self._bufsize, self._bufsize) self.s3 = np.zeros(self._bufsize, dtype=np.float) self.s3_data = deque([0.0] * self._bufsize, self._bufsize) self.s4 = np.zeros(self._bufsize, dtype=np.float) self.s4_data = deque([0.0] * self._bufsize, self._bufsize) def updateplot(self): self.x_data.append(self.timestamp.elapsed()) self.x[:] = self.x_data if self._s1enable: self.s1_data.append(self._s1_pin.value) self.s1[:] = self.s1_data self.p1.setData(self.x, self.s1) if self._s2enable: self.s2_data.append(self._s2_pin.value) self.s2[:] = self.s2_data self.p2.setData(self.x, self.s2) if self._s3enable: self.s3_data.append(self._s3_pin.value) self.s3[:] = self.s3_data self.p3.setData(self.x, self.s3) if self._s4enable: self.s4_data.append(self._s4_pin.value) self.s4[:] = self.s4_data self.p4.setData(self.x, self.s4) def setyAxis(self): self.yAxis.setLabel(self._yAxisLabel, units=self._yAxisUnits) def setYRange(self): self.plot.setYRange(self._minY, self._maxY, padding = 0.0) @Property(int) def frequency(self): return self._frequency @frequency.setter def frequency(self, frequency): self._frequency = frequency return self.setData() @Property(int) def timeWindow(self): return self._timeWindow @timeWindow.setter def timeWindow(self, timeWindow): self._timeWindow = timeWindow return self.setData() @Property(str) def yAxisLabel(self): return self._yAxisLabel @yAxisLabel.setter def yAxisLabel(self, yAxisLabel): self._yAxisLabel = yAxisLabel return self.setyAxis() @Property(str) def yAxisUnits(self): return self._yAxisUnits @yAxisUnits.setter def yAxisUnits(self, yAxisUnits): self._yAxisUnits = yAxisUnits return self.setyAxis() @Property(float) def minYRange(self): return self._minY @minYRange.setter def minYRange(self, minY): self._minY = minY return self.setYRange() @Property(float) def maxYRange(self): return self._maxY @maxYRange.setter def maxYRange(self, maxY): self._maxY = maxY return self.setYRange() # Legend propterties @Property(bool) def legendenable(self): return self._legend @legendenable.setter def legendenable(self, legendenable): self._legend = legendenable self.setSeries() # Series 1 properties @Property(bool) def series1enable(self): return self._s1enable @series1enable.setter def series1enable(self, series1enable): self._s1enable = series1enable self.setSeries() @Property(str) def series1name(self): return self._s1name @series1name.setter def series1name(self, series1name): self._s1name = series1name self.setSeries() @Property(QColor) def series1colour(self): return self._s1colour @series1colour.setter def series1colour(self, series1colour): self._s1colour = series1colour self.setSeries() @Property(int) def series1width(self): return self._s1width @series1width.setter def series1width(self, series1width): self._s1width = series1width self.setSeries() @Property(Qt.PenStyle) def series1style(self): return self._s1style @series1style.setter def series1style(self, series1style): self._s1style = series1style self.setSeries() # Series 2 properties @Property(bool) def series2enable(self): return self._s2enable @series2enable.setter def series2enable(self, series2enable): self._s2enable = series2enable self.setSeries() @Property(str) def series2name(self): return self._s2name @series2name.setter def series2name(self, series2name): self._s2name = series2name self.setSeries() @Property(QColor) def series2colour(self): return self._s2colour @series2colour.setter def series2colour(self, series2colour): self._s2colour = series2colour self.setSeries() @Property(int) def series2width(self): return self._s2width @series2width.setter def series2width(self, series2width): self._s2width = series2width self.setSeries() @Property(Qt.PenStyle) def series2style(self): return self._s2style @series2style.setter def series2style(self, series2style): self._s2style = series2style self.setSeries() # Series 3 properties @Property(bool) def series3enable(self): return self._s3enable @series3enable.setter def series3enable(self, series3enable): self._s3enable = series3enable self.setSeries() @Property(str) def series3name(self): return self._s3name @series3name.setter def series3name(self, series3name): self._s3name = series3name self.setSeries() @Property(QColor) def series3colour(self): return self._s3colour @series3colour.setter def series3colour(self, series3colour): self._s3colour = series3colour self.setSeries() @Property(int) def series3width(self): return self._s3width @series3width.setter def series3width(self, series3width): self._s3width = series3width self.setSeries() @Property(Qt.PenStyle) def series3style(self): return self._s3style @series3style.setter def series3style(self, series3style): self._s3style = series3style self.setSeries() # Series 4 properties @Property(bool) def series4enable(self): return self._s4enable @series4enable.setter def series4enable(self, series4enable): self._s4enable = series4enable self.setSeries() @Property(str) def series4name(self): return self._s4name @series4name.setter def series4name(self, series4name): self._s4name = series4name self.setSeries() @Property(QColor) def series4colour(self): return self._s4colour @series4colour.setter def series4colour(self, series4colour): self._s4colour = series4colour self.setSeries() @Property(int) def series4width(self): return self._s4width @series4width.setter def series4width(self, series4width): self._s4width = series4width self.setSeries() @Property(Qt.PenStyle) def series4style(self): return self._s4style @series4style.setter def series4style(self, series4style): self._s4style = series4style self.setSeries() def initialize(self): comp = hal.getComponent() obj_name = self.getPinBaseName() # add HAL pins if self._s1enable: self._s1_pin = comp.addPin(obj_name + "." + self._s1name.replace(' ', ''), self._typ, "in") if self._s2enable: self._s2_pin = comp.addPin(obj_name + "." + self._s2name.replace(' ', ''), self._typ, "in") if self._s3enable: self._s3_pin = comp.addPin(obj_name + "." + self._s3name.replace(' ', ''), self._typ, "in") if self._s4enable: self._s4_pin = comp.addPin(obj_name + "." + self._s4name.replace(' ', ''), self._typ, "in")
class AsyncClient(QObject): """ A class which handles a connection to a client through a QProcess. """ # Emitted when the client has initialized. initialized = Signal() # Emitted when the client errors. errored = Signal() # Emitted when a request response is received. received = Signal(object) def __init__(self, target, executable=None, name=None, extra_args=None, libs=None, cwd=None, env=None): super(AsyncClient, self).__init__() self.executable = executable or sys.executable self.extra_args = extra_args self.target = target self.name = name or self self.libs = libs self.cwd = cwd self.env = env self.is_initialized = False self.closing = False self.context = zmq.Context() QApplication.instance().aboutToQuit.connect(self.close) # Set up the heartbeat timer. self.timer = QTimer(self) self.timer.timeout.connect(self._heartbeat) def run(self): """Handle the connection with the server. """ # Set up the zmq port. self.socket = self.context.socket(zmq.PAIR) self.port = self.socket.bind_to_random_port('tcp://*') # Set up the process. self.process = QProcess(self) if self.cwd: self.process.setWorkingDirectory(self.cwd) p_args = ['-u', self.target, str(self.port)] if self.extra_args is not None: p_args += self.extra_args # Set up environment variables. processEnvironment = QProcessEnvironment() env = self.process.systemEnvironment() if (self.env and 'PYTHONPATH' not in self.env) or DEV: python_path = osp.dirname(get_module_path('spyderlib')) # Add the libs to the python path. for lib in self.libs: try: path = osp.dirname(imp.find_module(lib)[1]) python_path = osp.pathsep.join([python_path, path]) except ImportError: pass env.append("PYTHONPATH=%s" % python_path) if self.env: env.update(self.env) for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) # Start the process and wait for started. self.process.start(self.executable, p_args) self.process.finished.connect(self._on_finished) running = self.process.waitForStarted() if not running: raise IOError('Could not start %s' % self) # Set up the socket notifer. fid = self.socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) self.notifier.activated.connect(self._on_msg_received) def request(self, func_name, *args, **kwargs): """Send a request to the server. The response will be a dictionary the 'request_id' and the 'func_name' as well as a 'result' field with the object returned by the function call or or an 'error' field with a traceback. """ if not self.is_initialized: return request_id = uuid.uuid4().hex request = dict(func_name=func_name, args=args, kwargs=kwargs, request_id=request_id) self._send(request) return request_id def close(self): """Cleanly close the connection to the server. """ self.closing = True self.is_initialized = False self.timer.stop() self.notifier.activated.disconnect(self._on_msg_received) self.notifier.setEnabled(False) del self.notifier self.request('server_quit') self.process.waitForFinished(1000) self.process.close() self.context.destroy() def _on_finished(self): """Handle a finished signal from the process. """ if self.closing: return if self.is_initialized: debug_print('Restarting %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.is_initialized = False self.notifier.setEnabled(False) self.run() else: debug_print('Errored %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.errored.emit() def _on_msg_received(self): """Handle a message trigger from the socket. """ self.notifier.setEnabled(False) while 1: try: resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK) except zmq.ZMQError: self.notifier.setEnabled(True) return if not self.is_initialized: self.is_initialized = True debug_print('Initialized %s' % self.name) self.initialized.emit() self.timer.start(HEARTBEAT) continue resp['name'] = self.name self.received.emit(resp) def _heartbeat(self): """Send a heartbeat to keep the server alive. """ self._send(dict(func_name='server_heartbeat')) def _send(self, obj): """Send an object to the server. """ try: self.socket.send_pyobj(obj) except Exception as e: debug_print(e) self.is_initialized = False self._on_finished()