def test_calltip(main_window, qtbot): """Hide the calltip in the editor when a matching ')' is found.""" # Load test file text = 'a = [1,2,3]\n(max' main_window.editor.new(fname="test.py", text=text) code_editor = main_window.editor.get_focus_widget() # Set text to start code_editor.set_text(text) code_editor.go_to_line(2) code_editor.move_cursor(5) calltip = code_editor.calltip_widget assert not calltip.isVisible() qtbot.keyPress(code_editor, Qt.Key_ParenLeft, delay=3000) qtbot.keyPress(code_editor, Qt.Key_A, delay=1000) qtbot.waitUntil(lambda: calltip.isVisible(), timeout=1000) qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000) qtbot.keyPress(code_editor, Qt.Key_Space) assert not calltip.isVisible() qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000) qtbot.keyPress(code_editor, Qt.Key_Enter, delay=1000) QTimer.singleShot(1000, lambda: close_save_message_box(qtbot)) main_window.editor.close_file()
def free_memory(self): """Free memory signal.""" self.main.free_memory() QTimer.singleShot(self.INITIAL_FREE_MEMORY_TIME_TRIGGER, lambda: self.main.free_memory()) QTimer.singleShot(self.SECONDARY_FREE_MEMORY_TIME_TRIGGER, lambda: self.main.free_memory())
def blink_figure(self): """Blink figure once.""" if self.fig: self._blink_flag = not self._blink_flag self.repaint() if self._blink_flag: timer = QTimer() timer.singleShot(40, self.blink_figure)
def set_font_size(self, old, new): current_font = self.app.font() current_font.setPointSizeF(current_font.pointSizeF()/old*new) QApplication.instance().setFont(current_font) for w in self.app.allWidgets(): w_c_f = w.font() w_c_f.setPointSizeF(w_c_f.pointSizeF()/old*new) w.setFont(w_c_f) QTimer.singleShot(0, self.resizeForNewDisplayWidget)
def revert(self): """ Takes the data stored in the models and displays them in the widgets. """ # make sure it is called from main thread if (not QThread.currentThread() == QCoreApplication.instance( ).thread()): QTimer.singleShot(0, self.revert) return for key in self._mappings: self._on_model_notification(key)
def focusOutEvent(self, event): """Handle focus out event restoring the last valid selected path.""" # Calling asynchronously the 'add_current_text' to avoid crash # https://groups.google.com/group/spyderlib/browse_thread/thread/2257abf530e210bd if not self.is_valid(): lineedit = self.lineEdit() QTimer.singleShot(50, lambda: lineedit.setText(self.selected_text)) hide_status = getattr(self.lineEdit(), 'hide_status_icon', None) if hide_status: hide_status() QComboBox.focusOutEvent(self, event)
def test_sort_dataframe_with_category_dtypes(qtbot): # cf. issue 5361 df = DataFrame({'A': [1, 2, 3, 4], 'B': ['a', 'b', 'c', 'd']}) df = df.astype(dtype={'B': 'category'}) df_cols = df.dtypes editor = DataFrameEditor(None) editor.setup_and_check(df_cols) dfm = editor.dataModel QTimer.singleShot(1000, lambda: close_message_box(qtbot)) editor.dataModel.sort(0) assert data(dfm, 0, 0) == 'int64' assert data(dfm, 1, 0) == 'category'
def environ_dialog(qtbot): "Setup the Environment variables Dialog taking into account the os." QTimer.singleShot(1000, lambda: close_message_box(qtbot)) if os.name == 'nt': from spyder.utils.environ import WinUserEnvDialog dialog = WinUserEnvDialog() else: from spyder.utils.environ import EnvDialog dialog = EnvDialog() qtbot.addWidget(dialog) return dialog
def test_sort_dataframe_with_duplicate_column(qtbot): df = DataFrame({'A': [1, 3, 2], 'B': [4, 6, 5]}) df = concat((df, df.A), axis=1) editor = DataFrameEditor(None) editor.setup_and_check(df) dfm = editor.dataModel QTimer.singleShot(1000, lambda: close_message_box(qtbot)) editor.dataModel.sort(0) assert [data(dfm, row, 0) for row in range(len(df))] == ['1', '3', '2'] assert [data(dfm, row, 1) for row in range(len(df))] == ['4', '6', '5'] editor.dataModel.sort(1) assert [data(dfm, row, 0) for row in range(len(df))] == ['1', '2', '3'] assert [data(dfm, row, 1) for row in range(len(df))] == ['4', '5', '6']
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_()
def follow_directories_loaded(self, fname): """Follow directories loaded during startup""" if self._to_be_loaded is None: return path = osp.normpath(to_text_string(fname)) if path in self._to_be_loaded: self._to_be_loaded.remove(path) if self._to_be_loaded is not None and len(self._to_be_loaded) == 0 \ and not is_pyqt46: self.fsmodel.directoryLoaded.disconnect( self.follow_directories_loaded) if self._scrollbar_positions is not None: # The tree view need some time to render branches: QTimer.singleShot(50, self.restore_scrollbar_positions)
def set_display_widget(self, new_widget): if new_widget == self._display_widget: return self.clear_display_widget() if not new_widget.layout(): new_widget.setMinimumSize(new_widget.size()) self._new_widget_size = new_widget.size() self._display_widget = new_widget self.setCentralWidget(self._display_widget) self.update_window_title() # Resizing to the new widget's dimensions needs to be # done on the event loop for some reason - you can't # just do it here. QTimer.singleShot(0, self.resizeForNewDisplayWidget)
def submit(self): """ Submits the current values stored in the widgets to the models. """ # make sure it is called from main thread if (not QThread.currentThread() == QCoreApplication.instance( ).thread()): QTimer.singleShot(0, self.submit) return submit_policy = self._submit_policy self.submit_policy = SUBMIT_POLICY_AUTO try: for key in self._mappings: self._on_widget_property_notification(key) finally: self.submit_policy = submit_policy
def test_load_kernel_file_from_id(ipyconsole, qtbot): """ Test that a new client is created using its id """ shell = ipyconsole.get_current_shellwidget() client = ipyconsole.get_current_client() qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT) connection_file = osp.basename(client.connection_file) id_ = connection_file.split('kernel-')[-1].split('.json')[0] QTimer.singleShot(2000, lambda: open_client_from_connection_info( id_, qtbot)) ipyconsole.create_client_for_kernel() qtbot.wait(1000) new_client = ipyconsole.get_clients()[1] assert new_client.id_ == dict(int_id='1', str_id='B')
def test_restart_kernel(ipyconsole, qtbot): """ Test that kernel is restarted correctly """ shell = ipyconsole.get_current_shellwidget() client = ipyconsole.get_current_client() qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT) # Do an assigment to verify that it's not there after restarting with qtbot.waitSignal(shell.executed): shell.execute('a = 10') # Restart kernel and wait until it's up again shell._prompt_html = None QTimer.singleShot(1000, lambda: close_message_box(qtbot)) client.restart_kernel() qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT) assert not shell.is_defined('a')
def test_close_when_file_is_changed(main_window, qtbot): """Test closing spyder when there is a file with modifications open.""" # Wait until the window is fully up shell = main_window.ipyconsole.get_current_shellwidget() qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT) # Load test file test_file = osp.join(LOCATION, 'script.py') main_window.editor.load(test_file) editorstack = main_window.editor.get_current_editorstack() editor = editorstack.get_current_editor() editor.document().setModified(True) # Close.main-window QTimer.singleShot(1000, lambda: close_save_message_box(qtbot)) main_window.close() # Wait for the segfault qtbot.wait(3000)
def test_load_kernel_file_from_location(ipyconsole, qtbot): """ Test that a new client is created using a connection file placed in a different location from jupyter_runtime_dir """ shell = ipyconsole.get_current_shellwidget() client = ipyconsole.get_current_client() qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT) connection_file = osp.join(tempfile.gettempdir(), osp.basename(client.connection_file)) shutil.copy2(client.connection_file, connection_file) QTimer.singleShot(2000, lambda: open_client_from_connection_info( connection_file, qtbot)) ipyconsole.create_client_for_kernel() qtbot.wait(1000) assert len(ipyconsole.get_clients()) == 2
def event(self, event): """Qt Override. Filter tab keys and process double tab keys. """ if (event.type() == QEvent.KeyPress) and (event.key() == Qt.Key_Tab): self.sig_tab_pressed.emit(True) self.numpress += 1 if self.numpress == 1: self.presstimer = QTimer.singleShot(400, self.handle_keypress) return True return QComboBox.event(self, event)
def test_open_files_in_new_editor_window(main_window, qtbot): """ This tests that opening files in a new editor window is working as expected. Test for issue 4085 """ # Set a timer to manipulate the open dialog while it's running QTimer.singleShot(2000, lambda: open_file_in_editor(main_window, 'script.py', directory=LOCATION)) # Create a new editor window # Note: editor.load() uses the current editorstack by default main_window.editor.create_new_window() main_window.editor.load() # Perform the test # Note: There's always one file open in the Editor editorstack = main_window.editor.get_current_editorstack() assert editorstack.get_stack_count() == 2
def test_load_kernel_file(ipyconsole, qtbot): """ Test that a new client is created using the connection file of an existing client """ shell = ipyconsole.get_current_shellwidget() client = ipyconsole.get_current_client() qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT) QTimer.singleShot(2000, lambda: open_client_from_connection_info( client.connection_file, qtbot)) ipyconsole.create_client_for_kernel() qtbot.wait(1000) new_client = ipyconsole.get_clients()[1] new_shell = new_client.shellwidget with qtbot.waitSignal(new_shell.executed): new_shell.execute('a = 10') assert new_client.id_ == dict(int_id='1', str_id='B') assert shell.get_value('a') == new_shell.get_value('a')
def update_rotation_xy(self, rotation): print('Rotation: ', rotation) # in degrees # ToDo: use transform matrix to rotate existing path? # probably not worth it since rotation is not used much ... # nasty hack so ensure the positions have updated before loading QTimer.singleShot(10, self.reload_program)
class XicamSplashScreen(QSplashScreen): minsplashtime = 3000 def __init__(self, mainwindow: Callable[[], QMainWindow] = None, f: int = Qt.WindowStaysOnTopHint | Qt.SplashScreen): """ A QSplashScreen customized to display an animated gif. The splash triggers launch when clicked. After minsplashtime, this splash waits until the animation finishes before triggering the launch. Parameters ---------- mainwindow : class Subclass of QMainWindow to display after splashing f : int Extra flags (see base class) """ # Get logo movie from relative path self.movie = QMovie(str(static.path("images/animated_logo.gif"))) # Setup drawing self.movie.frameChanged.connect(self.paintFrame) self.movie.jumpToFrame(1) self.pixmap = QPixmap(self.movie.frameRect().size()) super(XicamSplashScreen, self).__init__(self.pixmap, f) self.setMask(self.pixmap.mask()) self.movie.finished.connect(self.restartmovie) self._launching = False self._launchready = False self.timer = QTimer(self) self.mainwindow = mainwindow if args.nosplash: self.execlaunch() else: # Start splashing self.setAttribute(Qt.WA_DeleteOnClose) self.show() self.raise_() self.activateWindow() QApplication.instance().setActiveWindow(self) # Setup timed triggers for launching the QMainWindow self.timer.singleShot(self.minsplashtime, self.launchwindow) def showMessage(self, message: str, color=Qt.white): # TODO: Make this work. super(XicamSplashScreen, self).showMessage(message, color) def mousePressEvent(self, *args, **kwargs): # TODO: Apparently this doesn't work? self.timer.stop() self.execlaunch() def show(self): """ Start the animation when shown """ super(XicamSplashScreen, self).show() self.movie.start() def paintFrame(self): """ Paint the current frame """ self.pixmap = self.movie.currentPixmap() self.setMask(self.pixmap.mask()) self.setPixmap(self.pixmap) self.movie.setSpeed(self.movie.speed() + 20) def sizeHint(self): return self.movie.scaledSize() def restartmovie(self): """ Once the animation reaches the end, check if its time to launch, otherwise restart animation """ if self._launchready: self.execlaunch() return self.movie.start() def launchwindow(self): """ Save state, defer launch until animation finishes """ self._launchready = True def execlaunch(self): """ Launch the mainwindow """ if not self._launching: self._launching = True app = QApplication.instance() from xicam.gui.windows.mainwindow import XicamMainWindow self.mainwindow = XicamMainWindow() self.timer.stop() # Show the QMainWindow self.mainwindow.show() self.mainwindow.raise_() self.mainwindow.activateWindow() app.setActiveWindow(self.mainwindow) # Stop splashing self.hide() self.movie.stop() self.finish(self.mainwindow)
def reloadBackplot(self): QTimer.singleShot(100, lambda: self._reloadBackplot())
class PluginManager(QObject): introspection_complete = Signal(object) def __init__(self, executable): super(PluginManager, self).__init__() plugins = OrderedDict() for name in PLUGINS: try: plugin = PluginClient(name, executable) plugin.run() except Exception as e: debug_print('Introspection Plugin Failed: %s' % name) debug_print(str(e)) continue debug_print('Introspection Plugin Loaded: %s' % name) plugins[name] = plugin plugin.received.connect(self.handle_response) self.plugins = plugins self.timer = QTimer() self.desired = [] self.ids = dict() self.info = None self.request = None self.pending = None self.pending_request = None self.waiting = False def send_request(self, info): """Handle an incoming request from the user.""" if self.waiting: if info.serialize() != self.info.serialize(): self.pending_request = info else: debug_print('skipping duplicate request') return debug_print('%s request' % info.name) desired = None self.info = info editor = info.editor if (info.name == 'completion' and 'jedi' not in self.plugins and info.line.lstrip().startswith(('import ', 'from '))): desired = 'fallback' if ((not editor.is_python_like()) or sourcecode.is_keyword(info.obj) or (editor.in_comment_or_string() and info.name != 'info')): desired = 'fallback' plugins = self.plugins.values() if desired: plugins = [self.plugins[desired]] self.desired = [desired] elif (info.name == 'definition' and not info.editor.is_python() or info.name == 'info'): self.desired = list(self.plugins.keys()) else: # Use all but the fallback plugins = list(self.plugins.values())[:-1] self.desired = list(self.plugins.keys())[:-1] self._start_time = time.time() self.waiting = True method = 'get_%s' % info.name value = info.serialize() self.ids = dict() for plugin in plugins: request_id = plugin.request(method, value) self.ids[request_id] = plugin.name self.timer.stop() self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout) def validate(self): for plugin in self.plugins.values(): plugin.request('validate') def handle_response(self, response): name = self.ids.get(response['request_id'], None) if not name: return if response.get('error', None): debug_print('Response error:', response['error']) return if name == self.desired[0] or not self.waiting: if response.get('result', None): self._finalize(response) else: self.pending = response def close(self): for name, plugin in self.plugins.items(): plugin.close() debug_print("Introspection Plugin Closed: {}".format(name)) def _finalize(self, response): self.waiting = False self.pending = None if self.info: delta = time.time() - self._start_time debug_print('%s request from %s finished: "%s" in %.1f sec' % (self.info.name, response['name'], str(response['result'])[:100], delta)) response['info'] = self.info self.info = None self.introspection_complete.emit(response) if self.pending_request: info = self.pending_request self.pending_request = None self.send_request(info) def _handle_timeout(self): self.waiting = False if self.pending: self._finalize(self.pending) else: debug_print('No valid responses acquired')
def focusChangedEvent(self, old_w, new_w): if issubclass(new_w.__class__, QLineEdit): #print "QLineEdit got focus: ", new_w QTimer.singleShot(0, new_w.selectAll)
class AnimationWorker(QObject): """A thread to keep the animation timer independent of the main event loop. This prevents mouseovers and other events from causing animation lag. See QtDims.play() for public-facing docstring. """ frame_requested = Signal(int, int) # axis, point finished = Signal() started = Signal() def __init__(self, slider): super().__init__() self.slider = slider self.dims = slider.dims self.axis = slider.axis self.loop_mode = slider.loop_mode slider.fps_changed.connect(self.set_fps) slider.mode_changed.connect(self.set_loop_mode) slider.range_changed.connect(self.set_frame_range) self.set_fps(self.slider.fps) self.set_frame_range(slider.frame_range) # after dims.set_point is called, it will emit a dims.events.axis() # we use this to update this threads current frame (in case it # was some other event that updated the axis) self.dims.events.axis.connect(self._on_axis_changed) self.current = max(self.dims.point[self.axis], self.min_point) self.current = min(self.current, self.max_point) self.timer = QTimer() @Slot() def work(self): """Play the animation.""" # if loop_mode is once and we are already on the last frame, # return to the first frame... (so the user can keep hitting once) if self.loop_mode == LoopMode.ONCE: if self.step > 0 and self.current >= self.max_point - 1: self.frame_requested.emit(self.axis, self.min_point) elif self.step < 0 and self.current <= self.min_point + 1: self.frame_requested.emit(self.axis, self.max_point) self.timer.singleShot(self.interval, self.advance) else: # immediately advance one frame self.advance() self.started.emit() @Slot(float) def set_fps(self, fps): """Set the frames per second value for the animation. Parameters ---------- fps : float Frames per second for the animation. """ if fps == 0: return self.finish() self.step = 1 if fps > 0 else -1 # negative fps plays in reverse self.interval = 1000 / abs(fps) @Slot(tuple) def set_frame_range(self, frame_range): """Frame range for animation, as (minimum_frame, maximum_frame). Parameters ---------- frame_range : tuple(int, int) Frame range as tuple/list with range (minimum_frame, maximum_frame) """ self.dimsrange = self.dims.range[self.axis] if frame_range is not None: if frame_range[0] >= frame_range[1]: raise ValueError("frame_range[0] must be <= frame_range[1]") if frame_range[0] < self.dimsrange[0]: raise IndexError("frame_range[0] out of range") if frame_range[1] * self.dimsrange[2] >= self.dimsrange[1]: raise IndexError("frame_range[1] out of range") self.frame_range = frame_range if self.frame_range is not None: self.min_point, self.max_point = self.frame_range else: self.min_point = 0 self.max_point = int( np.floor(self.dimsrange[1] - self.dimsrange[2])) self.max_point += 1 # range is inclusive @Slot(str) def set_loop_mode(self, mode): """Set the loop mode for the animation. Parameters ---------- mode : str Loop mode for animation. Available options for the loop mode string enumeration are: - LoopMode.ONCE Animation will stop once movie reaches the max frame (if fps > 0) or the first frame (if fps < 0). - LoopMode.LOOP Movie will return to the first frame after reaching the last frame, looping continuously until stopped. - LoopMode.BACK_AND_FORTH Movie will loop continuously until stopped, reversing direction when the maximum or minimum frame has been reached. """ self.loop_mode = LoopMode(mode) def advance(self): """Advance the current frame in the animation. Takes dims scale into account and restricts the animation to the requested frame_range, if entered. """ self.current += self.step * self.dimsrange[2] if self.current < self.min_point: if (self.loop_mode == LoopMode.BACK_AND_FORTH ): # 'loop_back_and_forth' self.step *= -1 self.current = self.min_point + self.step * self.dimsrange[2] elif self.loop_mode == LoopMode.LOOP: # 'loop' self.current = self.max_point + self.current - self.min_point else: # loop_mode == 'once' return self.finish() elif self.current >= self.max_point: if (self.loop_mode == LoopMode.BACK_AND_FORTH ): # 'loop_back_and_forth' self.step *= -1 self.current = (self.max_point + 2 * self.step * self.dimsrange[2]) elif self.loop_mode == LoopMode.LOOP: # 'loop' self.current = self.min_point + self.current - self.max_point else: # loop_mode == 'once' return self.finish() with self.dims.events.axis.blocker(self._on_axis_changed): self.frame_requested.emit(self.axis, self.current) # using a singleShot timer here instead of timer.start() because # it makes it easier to update the interval using signals/slots self.timer.singleShot(self.interval, self.advance) def finish(self): """Emit the finished event signal.""" self.finished.emit() @Slot(Event) def _on_axis_changed(self, event): """Update the current frame if the axis has changed.""" # slot for external events to update the current frame if event.axis == self.axis and hasattr(event, 'value'): self.current = event.value
def leaveEvent(self, a0: QEvent) -> None: print('leave menu', self.underMouse(), self.tool_button.underMouse()) QTimer.singleShot(50, self.tool_button.hide_menu)
class _SearchResultsModel(QAbstractTableModel): """ Qt model connecting our model to Qt's model--view machinery This is implementing two layers of "laziness" to ensure that the app remains responsive when large tables are loaded. 1. Rows are added dynamically using Qt's canFetchMore / fetchMore machinery. 2. Data (which Qt assumes is readily available in memory) is immediately filled with LOADING_PLACEHOLDER. Work is kicked off on a thread to later update this with the actual data. """ def __init__(self, model, *args, **kwargs): self.model = model # our internal model for the components subpackage super().__init__(*args, **kwargs) # State related to dynamically adding rows self._current_num_rows = 0 self._catalog_length = len(self.model.catalog) # Cache for loaded data self._data = {} # Queue of indexes of data to be loaded self._work_queue = collections.deque() # Set of active workers self._active_workers = set() # Start a timer that will periodically load any data queued up to be loaded. self._data_loading_timer = QTimer(self) # We run this once to initialize it. The _process_work_queue schedules # it to be run again when it completes. This is better than a strictly # periodic timer because it ensures that requests do not pile up if # _process_work_queue takes longer than LOADING_LATENCY to complete. self._data_loading_timer.singleShot(LOADING_LATENCY, self._process_work_queue) # Changes to the model update the GUI. self.model.events.begin_reset.connect(self.on_begin_reset) self.model.events.end_reset.connect(self.on_end_reset) def _process_work_queue(self): if self._work_queue: worker = create_worker(_load_data, self.model.get_data, tuple(self._work_queue)) self._work_queue.clear() # Track this worker in case we need to ignore it and cancel due to # model reset. self._active_workers.add(worker) worker.finished.connect( lambda: self._active_workers.discard(worker)) worker.yielded.connect(self.on_item_loaded) worker.start() # Else, no work to do. # Schedule the next processing. self._data_loading_timer.singleShot(LOADING_LATENCY, self._process_work_queue) def on_item_loaded(self, payload): # Update state and trigger Qt to run data() to update its internal model. index, item = payload self._data[index] = item self.dataChanged.emit(index, index, []) def on_begin_reset(self, event): self.beginResetModel() self._current_num_rows = 0 self._catalog_length = len(self.model.catalog) for worker in self._active_workers: # Cease allowing this worker to mutate _data so that we do not get # any stale updates. worker.yielded.disconnect(self.on_item_loaded) # To avoid doing useless work, try to cancel the worker. We do not # rely on this request being effective. worker.quit() self._active_workers.clear() self._work_queue.clear() self._data.clear() def on_end_reset(self, event): self.endResetModel() def canFetchMore(self, parent=None): if parent.isValid(): return False return self._current_num_rows < self._catalog_length def fetchMore(self, parent=None): if parent.isValid(): return remainder = self._catalog_length - self._current_num_rows rows_to_add = min(remainder, CHUNK_SIZE) if rows_to_add <= 0: return self.beginInsertRows(parent, self._current_num_rows, self._current_num_rows + rows_to_add - 1) self._current_num_rows += rows_to_add self.endInsertRows() def rowCount(self, parent=None): return self._current_num_rows def columnCount(self, parent=None): return len(self.model.headings) def headerData(self, section, orientation, role=Qt.DisplayRole): if role != Qt.DisplayRole: return super().headerData(section, orientation, role) if orientation == Qt.Horizontal and section < self.columnCount(): return str(self.model.headings[section]) elif orientation == Qt.Vertical and section < self.rowCount(): return section def data(self, index, role=QtCore.Qt.DisplayRole): if not index.isValid(): # does > 0 bounds check return QtCore.QVariant() if index.column() >= self.columnCount() or index.row( ) >= self.rowCount(): return QtCore.QVariant() if role == QtCore.Qt.DisplayRole: if index in self._data: return self._data[index] else: self._data[index] = LOADING_PLACEHOLDER self._work_queue.append(index) return LOADING_PLACEHOLDER else: return QtCore.QVariant()
return legend = self.plotWidget.plotItem.legend print('Legend:', legend) legend.removeItem('%d' % sweepIndex) print('Removing item:', curve) self.plotWidget.removeItem(curve) print('items in plotWidget.plotItem:', self.plotWidget.plotItem.items) del self._sweepMap[sweepIndex] print('Removed sweep') if __name__ == '__main__': from qtpy.QtGui import QApplication from qtpy.QtCore import QTimer path = 'ExampleData/' fileName = path + 'TES2_IV_20180117_090049.h5' app = QApplication([]) print('Loading data') sweepCollection = IvSweepCollection(fileName) print('making widget') mw = IvGraphWidget(sweepCollection) for i in range(0, len(sweepCollection), 4): mw.showSweep(i) mw.show() QTimer.singleShot(3000, lambda: mw.setSelection('up+/up-')) app.exec_()
def _pararchute_heartbeat(self): self._queue.put(PARACHUTE_HEARTBEAT) QTimer.singleShot(1000 * PARACHUTE_HEARTBEAT_INTERVAL, self._pararchute_heartbeat)
def __alive_loopback(self): alive = self.is_alive() if not alive: self.terminal_closed.emit() else: QTimer.singleShot(250, self.__alive_loopback)
class XicamSplashScreen(QSplashScreen): minsplashtime = 5000 def __init__(self, log_path: str, initial_length: int, f: int = Qt.WindowStaysOnTopHint | Qt.SplashScreen): """ A QSplashScreen customized to display an animated gif. The splash triggers launch when clicked. After minsplashtime, this splash waits until the animation finishes before triggering the launch. Parameters ---------- log_path : str Path to the Xi-CAM log file to reflect initial_length: int Length in bytes to seek forward before reading f : int Extra flags (see base class) """ # Get logo movie from relative path self.movie = QMovie(str(static.path("images/animated_logo.gif"))) # Setup drawing self.movie.frameChanged.connect(self.paintFrame) self.movie.jumpToFrame(1) self.pixmap = QPixmap(self.movie.frameRect().size()) super(XicamSplashScreen, self).__init__(self.pixmap, f) self.setMask(self.pixmap.mask()) self.movie.finished.connect(self.restartmovie) self.showMessage("Starting Xi-CAM...") self._launching = False self._launchready = False self.timer = QTimer(self) self.log_file = open(log_path, "r") self.log_file.seek(initial_length) # Start splashing self.setAttribute(Qt.WA_DeleteOnClose) self.show() self.raise_() self.activateWindow() QApplication.instance().setActiveWindow(self) # Setup timed triggers for launching the QMainWindow self.timer.singleShot(self.minsplashtime, self.launchwindow) def showMessage(self, message: str, color=Qt.darkGray): # attempt to parse out everyting besides the message try: message=message.split(" - ")[-1] except Exception: pass else: super(XicamSplashScreen, self).showMessage(elide(message), color=color, alignment=Qt.AlignBottom) def mousePressEvent(self, *args, **kwargs): # TODO: Apparently this doesn't work? self.timer.stop() self.execlaunch() def show(self): """ Start the animation when shown """ super(XicamSplashScreen, self).show() self.movie.start() def paintFrame(self): """ Paint the current frame """ self.pixmap = self.movie.currentPixmap() self.setMask(self.pixmap.mask()) self.setPixmap(self.pixmap) self.movie.setSpeed(self.movie.speed() + 20) line = self.log_file.readline().strip() if line: self.showMessage(elide(line.split(">")[-1])) def sizeHint(self): return self.movie.scaledSize() def restartmovie(self): """ Once the animation reaches the end, check if its time to launch, otherwise restart animation """ if self._launchready: self.execlaunch() return self.movie.start() def launchwindow(self): """ Save state, defer launch until animation finishes """ self._launchready = True def execlaunch(self): """ Launch the mainwindow """ if not self._launching: self._launching = True self.timer.stop() # Stop splashing self.hide() self.movie.stop() self.close() QApplication.instance().quit()
def delayed_refresh(self): """Post an event to the event loop that causes the view to update on the next cycle""" if not self.refresh_queued: self.refresh_queued = True QTimer.singleShot(0, self.presenter.refresh_view)
def show_warning_icon(self): """ Show the warning icon. This is called when a shell command fails (i.e. exits with nonzero status) """ self.setIcon(self._warning_icon) QTimer.singleShot(5000, self.hide_warning_icon)
def setup_ui(self): """ Initialize the widgets and layouts. """ self.setLayout(self.main_layout) self.pv_layout.addWidget(self.pv_protocol_cmb) self.pv_layout.addWidget(self.pv_name_line_edt) self.pv_layout.addWidget(self.pv_connect_push_btn) self.pv_add_panel.setLayout(self.pv_layout) QTimer.singleShot(0, self.pv_name_line_edt.setFocus) self.curve_settings_tab.setLayout(self.curves_tab_layout) self.chart_settings_tab.setLayout(self.chart_settings_layout) self.setup_chart_settings_layout() self.data_settings_tab.setLayout(self.data_tab_layout) self.setup_data_tab_layout() self.tab_panel.addTab(self.curve_settings_tab, "Curves") self.tab_panel.addTab(self.data_settings_tab, "Data") self.tab_panel.addTab(self.chart_settings_tab, "Graph") self.crosshair_settings_layout.addWidget(self.enable_crosshair_chk) self.crosshair_settings_layout.addWidget(self.crosshair_coord_lbl) self.zoom_x_layout.addWidget(self.zoom_in_x_btn) self.zoom_x_layout.addWidget(self.zoom_out_x_btn) self.zoom_y_layout.addWidget(self.zoom_in_y_btn) self.zoom_y_layout.addWidget(self.zoom_out_y_btn) self.view_all_reset_chart_layout.addWidget(self.reset_chart_btn) self.view_all_reset_chart_layout.addWidget(self.view_all_btn) self.pause_chart_layout.addWidget(self.pause_chart_btn) self.import_export_data_layout.addWidget(self.import_data_btn) self.import_export_data_layout.addWidget(self.export_data_btn) self.chart_control_layout.addLayout(self.zoom_x_layout) self.chart_control_layout.addLayout(self.zoom_y_layout) self.chart_control_layout.addLayout(self.view_all_reset_chart_layout) self.chart_control_layout.addLayout(self.pause_chart_layout) self.chart_control_layout.addLayout(self.crosshair_settings_layout) self.chart_control_layout.addLayout(self.import_export_data_layout) self.chart_control_layout.insertSpacing(5, 30) self.chart_layout.addWidget(self.chart) self.chart_layout.addLayout(self.chart_control_layout) self.chart_panel.setLayout(self.chart_layout) self.splitter.addWidget(self.chart_panel) self.splitter.addWidget(self.tab_panel) self.splitter.setSizes([1, 0]) self.splitter.setHandleWidth(10) self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.charting_layout.addWidget(self.splitter) self.body_layout.addWidget(self.pv_add_panel) self.body_layout.addLayout(self.charting_layout) self.body_layout.setSpacing(0) self.body_layout.setContentsMargins(0, 0, 0, 0) self.main_layout.addLayout(self.body_layout) self.enable_chart_control_buttons(False) handle = self.splitter.handle(1) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) button = QToolButton(handle) button.setArrowType(Qt.LeftArrow) button.clicked.connect(lambda: self.handle_splitter_button(True)) layout.addWidget(button) button = QToolButton(handle) button.setArrowType(Qt.RightArrow) button.clicked.connect(lambda: self.handle_splitter_button(False)) layout.addWidget(button) handle.setLayout(layout)
def setupUI(self): mainLayout = QVBoxLayout() title = QLabel(config.thisTranslation["gitHubBibleMp3Files"]) mainLayout.addWidget(title) self.versionsLayout = QVBoxLayout() self.renditionsList = QListWidget() self.renditionsList.itemClicked.connect(self.selectItem) for rendition in self.bibles.keys(): self.renditionsList.addItem(rendition) self.renditionsList.setMaximumHeight(100) self.versionsLayout.addWidget(self.renditionsList) mainLayout.addLayout(self.versionsLayout) self.downloadTable = QTableView() self.downloadTable.setEnabled(False) self.downloadTable.setFocusPolicy(Qt.StrongFocus) self.downloadTable.setEditTriggers(QAbstractItemView.NoEditTriggers) self.downloadTable.setSortingEnabled(True) self.dataViewModel = QStandardItemModel(self.downloadTable) self.downloadTable.setModel(self.dataViewModel) mainLayout.addWidget(self.downloadTable) buttonsLayout = QHBoxLayout() selectAllButton = QPushButton(config.thisTranslation["selectAll"]) selectAllButton.setFocusPolicy(Qt.StrongFocus) selectAllButton.clicked.connect(self.selectAll) buttonsLayout.addWidget(selectAllButton) selectNoneButton = QPushButton(config.thisTranslation["selectNone"]) selectNoneButton.setFocusPolicy(Qt.StrongFocus) selectNoneButton.clicked.connect(self.selectNone) buttonsLayout.addWidget(selectNoneButton) otButton = QPushButton("1-39") otButton.setFocusPolicy(Qt.StrongFocus) otButton.clicked.connect(self.selectOT) buttonsLayout.addWidget(otButton) ntButton = QPushButton("40-66") ntButton.setFocusPolicy(Qt.StrongFocus) ntButton.clicked.connect(self.selectNT) buttonsLayout.addWidget(ntButton) # buttonsLayout.addStretch() mainLayout.addLayout(buttonsLayout) self.downloadButton = QPushButton(config.thisTranslation["download"]) self.downloadButton.setFocusPolicy(Qt.StrongFocus) self.downloadButton.setAutoDefault(True) self.downloadButton.setFocus() self.downloadButton.clicked.connect(self.download) mainLayout.addWidget(self.downloadButton) self.status = QLabel("") mainLayout.addWidget(self.status) buttonLayout = QHBoxLayout() self.closeButton = QPushButton(config.thisTranslation["close"]) self.closeButton.setFocusPolicy(Qt.StrongFocus) self.closeButton.clicked.connect(self.closeDialog) buttonLayout.addWidget(self.closeButton) mainLayout.addLayout(buttonLayout) self.setLayout(mainLayout) self.renditionsList.item(0).setSelected(True) bible = self.renditionsList.item(0).text() self.selectRendition(bible) self.downloadButton.setDefault(True) QTimer.singleShot(0, self.downloadButton.setFocus)
def add_data() -> Future[dtype]: # type: ignore future = Future() # simulate something that isn't immediately ready when function returns QTimer.singleShot(10, partial(future.set_result, data)) return future
def main(): """Execute QDarkStyle example.""" parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( '--qt_from', default='qtpy', type=str, choices=[ 'pyqt5', 'pyqt', 'pyside2', 'pyside', 'qtpy', 'pyqtgraph', 'qt.py' ], help= "Choose which binding and/or abstraction is to be used to run the example." ) parser.add_argument( '--no_dark', action='store_true', help="Exihibts the original window (without jam_darkstyle).") parser.add_argument('--test', action='store_true', help="Auto close window after 2s.") parser.add_argument('--reset', action='store_true', help="Reset GUI settings (position, size) then opens.") parser.add_argument('--screenshots', action='store_true', help="Generate screenshots on images folder.") # Parsing arguments from command line args = parser.parse_args() # To avoid problems when testing without screen if args.test or args.screenshots: os.environ['QT_QPA_PLATFORM'] = 'offscreen' # Set QT_API variable before importing QtPy if args.qt_from in ['pyqt', 'pyqt5', 'pyside', 'pyside2']: os.environ['QT_API'] = args.qt_from elif args.qt_from == 'pyqtgraph': os.environ['QT_API'] = os.environ['PYQTGRAPH_QT_LIB'] elif args.qt_from in ['qt.py', 'qt']: try: import Qt except ImportError: print('Could not import Qt (Qt.Py)') else: os.environ['QT_API'] = Qt.__binding__ # QtPy imports from qtpy import API_NAME, QT_VERSION, PYQT_VERSION, PYSIDE_VERSION from qtpy import __version__ as QTPY_VERSION from qtpy.QtWidgets import (QApplication, QMainWindow, QDockWidget, QStatusBar, QLabel, QPushButton, QMenu) from qtpy.QtCore import QTimer, Qt, QSettings # Set API_VERSION variable API_VERSION = '' if PYQT_VERSION: API_VERSION = PYQT_VERSION elif PYSIDE_VERSION: API_VERSION = PYSIDE_VERSION else: API_VERSION = 'Not found' # Import examples UI from mw_menus_ui import Ui_MainWindow as ui_main from dw_buttons_ui import Ui_DockWidget as ui_buttons from dw_displays_ui import Ui_DockWidget as ui_displays from dw_inputs_fields_ui import Ui_DockWidget as ui_inputs_fields from dw_inputs_no_fields_ui import Ui_DockWidget as ui_inputs_no_fields from dw_widgets_ui import Ui_DockWidget as ui_widgets from dw_views_ui import Ui_DockWidget as ui_views from dw_containers_tabs_ui import Ui_DockWidget as ui_containers_tabs from dw_containers_no_tabs_ui import Ui_DockWidget as ui_containers_no_tabs # create the application app = QApplication(sys.argv) app.setOrganizationName('QDarkStyle') app.setApplicationName('QDarkStyle Example') style = '' if not args.no_dark: style = jam_darkstyle.load_stylesheet() app.setStyleSheet(style) # create main window window = QMainWindow() window.setObjectName('mainwindow') ui = ui_main() ui.setupUi(window) title = ("QDarkStyle Example - " + "(QDarkStyle=v" + jam_darkstyle.__version__ + ", QtPy=v" + QTPY_VERSION + ", " + API_NAME + "=v" + API_VERSION + ", Qt=v" + QT_VERSION + ", Python=v" + platform.python_version() + ")") _logger.info(title) window.setWindowTitle(title) # Create docks for buttons dw_buttons = QDockWidget() dw_buttons.setObjectName('buttons') ui_buttons = ui_buttons() ui_buttons.setupUi(dw_buttons) window.addDockWidget(Qt.RightDockWidgetArea, dw_buttons) # Add actions on popup toolbuttons menu = QMenu() for action in ['Action A', 'Action B', 'Action C']: menu.addAction(action) ui_buttons.toolButtonDelayedPopup.setMenu(menu) ui_buttons.toolButtonInstantPopup.setMenu(menu) ui_buttons.toolButtonMenuButtonPopup.setMenu(menu) # Create docks for buttons dw_displays = QDockWidget() dw_displays.setObjectName('displays') ui_displays = ui_displays() ui_displays.setupUi(dw_displays) window.addDockWidget(Qt.RightDockWidgetArea, dw_displays) # Create docks for inputs - no fields dw_inputs_no_fields = QDockWidget() dw_inputs_no_fields.setObjectName('inputs_no_fields') ui_inputs_no_fields = ui_inputs_no_fields() ui_inputs_no_fields.setupUi(dw_inputs_no_fields) window.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_no_fields) # Create docks for inputs - fields dw_inputs_fields = QDockWidget() dw_inputs_fields.setObjectName('inputs_fields') ui_inputs_fields = ui_inputs_fields() ui_inputs_fields.setupUi(dw_inputs_fields) window.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_fields) # Create docks for widgets dw_widgets = QDockWidget() dw_widgets.setObjectName('widgets') ui_widgets = ui_widgets() ui_widgets.setupUi(dw_widgets) window.addDockWidget(Qt.LeftDockWidgetArea, dw_widgets) # Create docks for views dw_views = QDockWidget() dw_views.setObjectName('views') ui_views = ui_views() ui_views.setupUi(dw_views) window.addDockWidget(Qt.LeftDockWidgetArea, dw_views) # Create docks for containers - no tabs dw_containers_no_tabs = QDockWidget() dw_containers_no_tabs.setObjectName('containers_no_tabs') ui_containers_no_tabs = ui_containers_no_tabs() ui_containers_no_tabs.setupUi(dw_containers_no_tabs) window.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_no_tabs) # Create docks for containters - tabs dw_containers_tabs = QDockWidget() dw_containers_tabs.setObjectName('containers_tabs') ui_containers_tabs = ui_containers_tabs() ui_containers_tabs.setupUi(dw_containers_tabs) window.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_tabs) # Tabify right docks window.tabifyDockWidget(dw_buttons, dw_displays) window.tabifyDockWidget(dw_displays, dw_inputs_fields) window.tabifyDockWidget(dw_inputs_fields, dw_inputs_no_fields) # Tabify left docks window.tabifyDockWidget(dw_containers_no_tabs, dw_containers_tabs) window.tabifyDockWidget(dw_containers_tabs, dw_widgets) window.tabifyDockWidget(dw_widgets, dw_views) # Issues #9120, #9121 on Spyder qstatusbar = QStatusBar() qstatusbar.addWidget( QLabel('Issue Spyder #9120, #9121 - background not matching.')) qstatusbar.addWidget(QPushButton('OK')) # Add info also in status bar for screenshots get it qstatusbar.addWidget(QLabel('INFO: ' + title)) window.setStatusBar(qstatusbar) # Todo: add report info and other info in HELP graphical # Auto quit after 2s when in test mode if args.test: QTimer.singleShot(2000, app.exit) # Save screenshots for different displays and quit if args.screenshots: window.showFullScreen() create_screenshots(app, window, args.no_dark) # Do not read settings when taking screenshots - like reset else: _read_settings(window, args.reset, QSettings) window.showMaximized() app.exec_() _write_settings(window, QSettings) return window
def __init__(self, parent=None, opts=None, ui_file=None, stylesheet=None, maximize=False, fullscreen=False, position=None, size=None, confirm_exit=True, title=None, menu='default'): super(VCPMainWindow, self).__init__(parent) if opts is None: opts = qtpyvcp.OPTIONS self.setWindowTitle(title) self.app = QApplication.instance() self.confirm_exit = confirm_exit if opts.confirm_exit is None else opts.confirm_exit # Load the UI file AFTER defining variables, otherwise the values # set in QtDesigner get overridden by the default values if ui_file is not None: self.loadUi(ui_file) self.initUi() if menu is not None: try: # delete any preexisting menuBar added in QtDesigner # because it shadows the QMainWindow.menuBar() method del self.menuBar except AttributeError: pass if menu == 'default': menu = qtpyvcp.CONFIG.get('default_menubar', []) self.setMenuBar(self.buildMenuBar(menu)) if title is not None: self.setWindowTitle(title) if stylesheet is not None: self.loadStylesheet(stylesheet) maximize = opts.maximize if opts.maximize is not None else maximize if maximize: QTimer.singleShot(0, self.showMaximized) fullscreen = opts.fullscreen if opts.fullscreen is not None else fullscreen if fullscreen: QTimer.singleShot(0, self.showFullScreen) if opts.hide_menu_bar: self.menuBar().hide() size = opts.size or size if size: try: width, height = size.lower().split('x') self.resize(int(width), int(height)) except: LOG.exception('Error parsing --size argument: %s', size) pos = opts.position or position if pos: try: xpos, ypos = pos.lower().split('x') self.move(int(xpos), int(ypos)) except: LOG.exception('Error parsing --position argument: %s', pos) QShortcut(QKeySequence("F11"), self, self.toggleFullscreen) self.app.focusChanged.connect(self.focusChangedEvent)
def test_environ(qtbot): """Test the environment variables dialog.""" QTimer.singleShot(1000, lambda: close_message_box(qtbot)) dialog = setup_environ(qtbot) dialog.show() assert dialog
def onToolTableFileChanged(self, path): LOG.debug('Tool Table file changed: {}'.format(path)) # ToolEdit deletes the file and then rewrites it, so wait # a bit to ensure the new data has been writen out. QTimer.singleShot(50, self.reloadToolTable)
class MainWindow(QWidget): def __init__(self, config: Config) -> None: """ Main window with the GUI and whatever player is being used. """ super().__init__() self.setWindowTitle('vidify') # Setting the window to stay on top if config.stay_on_top: self.setWindowFlags(Qt.WindowStaysOnTopHint) # Setting the fullscreen and window size if config.fullscreen: self.showFullScreen() else: self.resize(config.width or 800, config.height or 600) # Loading the used fonts (Inter) font_db = QFontDatabase() for font in Res.fonts: font_db.addApplicationFont(font) # Initializing the player and saving the config object in the window. self.layout = QHBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.player = initialize_player(config.player, config) logging.info("Using %s as the player", config.player) self.config = config # The API initialization is more complex. For more details, please # check the flow diagram in vidify.api. First we have to check if # the API is saved in the config: try: api_data = get_api_data(config.api) except KeyError: # Otherwise, the user is prompted for an API. After choosing one, # it will be initialized from outside this function. logging.info("API not found: prompting the user") self.API_selection = APISelection() self.layout.addWidget(self.API_selection) self.API_selection.api_chosen.connect(self.on_api_selection) else: logging.info("Using %s as the API", config.api) self.initialize_api(api_data) @Slot(str) def on_api_selection(self, api_str: str) -> None: """ Method called when the API is selected with APISelection. The provided api string must be an existent entry inside the APIData enumeration. """ # Removing the widget used to obtain the API string self.layout.removeWidget(self.API_selection) self.API_selection.setParent(None) self.API_selection.hide() del self.API_selection # Saving the API in the config self.config.api = api_str # Starting the API initialization self.initialize_api(APIData[api_str]) def initialize_api(self, api_data: APIData) -> None: """ Initializes an API with the information from APIData. """ # The API may need interaction with the user to obtain credentials # or similar data. This function will already take care of the # rest of the initialization. if api_data.gui_init_fn is not None: fn = getattr(self, api_data.gui_init_fn) fn() return # Initializing the API with dependency injection. mod = importlib.import_module(api_data.module) cls = getattr(mod, api_data.class_name) self.api = cls() self.wait_for_connection( self.api.connect_api, message=api_data.connect_msg, event_loop_interval=api_data.event_loop_interval) def wait_for_connection(self, conn_fn: Callable[[], None], message: Optional[str] = None, event_loop_interval: int = 1000) -> None: """ Creates an APIConnecter instance and waits for the API to be available, or times out otherwise. """ self.event_loop_interval = event_loop_interval self.api_connecter = APIConnecter( conn_fn, message or "Waiting for connection") self.api_connecter.success.connect(self.on_conn_success) self.api_connecter.fail.connect(self.on_conn_fail) self.layout.addWidget(self.api_connecter) self.api_connecter.start() @Slot() def on_conn_fail(self) -> None: """ If the API failed to connect, the app will be closed. """ print("Timed out waiting for the connection") QCoreApplication.exit(1) @Slot(float) def on_conn_success(self, start_time: float) -> None: """ Once the connection has been established correctly, the API can be started properly. """ logging.info("Succesfully connected to the API") self.layout.removeWidget(self.api_connecter) del self.api_connecter # Initializing the optional audio synchronization extension, now # that there's access to the API's data. Note that this feature # is only available on Linux. if self.config.audiosync: from vidify.audiosync import AudiosyncWorker self.audiosync = AudiosyncWorker(self.api.player_name) self.audiosync.success.connect(self.on_audiosync_success) self.audiosync.failed.connect(self.on_audiosync_fail) # Loading the player self.setStyleSheet(f"background-color:{Colors.black};") self.layout.addWidget(self.player) self.play_video(self.api.artist, self.api.title, start_time) # Connecting to the signals generated by the API self.api.new_song_signal.connect(self.play_video) self.api.position_signal.connect(self.change_video_position) self.api.status_signal.connect(self.change_video_status) # Starting the event loop if it was initially passed as # a parameter. if self.event_loop_interval is not None: self.start_event_loop(self.api.event_loop, self.event_loop_interval) def start_event_loop(self, event_loop: Callable[[], None], ms: int) -> None: """ Starts a "manual" event loop with a timer every `ms` milliseconds. This is used with the SwSpotify API and the Web API to check every `ms` seconds if a change has happened, like if the song was paused. """ logging.info("Starting event loop") timer = QTimer(self) # Qt doesn't accept a method as the parameter so it's converted # to a function. if isinstance(event_loop, types.MethodType): timer.timeout.connect(lambda: event_loop()) else: timer.timeout.connect(event_loop) timer.start(ms) @Slot(bool) def change_video_status(self, is_playing: bool) -> None: """ Slot used for API updates of the video status. """ self.player.pause = not is_playing # If there is an audiosync thread running, this will pause the sound # recording and youtube downloading. if self.config.audiosync and self.audiosync.status != 'idle': self.audiosync.is_running = is_playing @Slot(int) def change_video_position(self, ms: int) -> None: """ Slot used for API updates of the video position. """ if not self.config.audiosync: self.player.position = ms # Audiosync is aborted if the position of the video changed, since # the audio being recorded won't make sense. if self.config.audiosync and self.audiosync.status != 'idle': self.audiosync.abort() @Slot(str, str, float) def play_video(self, artist: str, title: str, start_time: float) -> None: """ Slot used to play a video. This is called when the API is first initialized from this GUI, and afterwards from the event loop handler whenever a new song is detected. If an error was detected when downloading the video, the default one is shown instead. Both audiosync and youtubedl work in separate threads to avoid blocking the GUI. This method will start both of them. """ # Checking that the artist and title are valid first of all if self.api.artist in (None, '') and self.api.title in (None, ''): logging.info("The provided artist and title are empty.") self.on_youtubedl_fail() if self.config.audiosync: self.on_audiosync_fail() return # This delay is used to know the elapsed time until the video # actually starts playing, used in the audiosync feature. self.timestamp = start_time query = f"ytsearch:{format_name(artist, title)} Official Video" if self.config.audiosync: self.launch_audiosync(query) self.launch_youtubedl(query) def launch_audiosync(self, query: str) -> None: """ Starts the audiosync thread, that will call either self.on_audiosync_success, or self.on_audiosync_fail once it's finished. First trying to stop the previous audiosync thread, as only one audiosync thread can be running at once. Note: QThread.start() is guaranteed to work once QThread.run() has returned. Thus, this will wait until it's done and launch the new one. """ self.audiosync.abort() self.audiosync.wait() self.audiosync.youtube_title = query self.audiosync.start() logging.info("Started a new audiosync job") def launch_youtubedl(self, query: str) -> None: """ Starts a YoutubeDL thread that will call either self.on_youtubedl_success or self.on_youtubedl_fail once it's done. """ logging.info("Starting the youtube-dl thread") self.youtubedl = YouTubeDLWorker( query, self.config.debug, self.config.width, self.config.height) self.yt_thread = QThread() self.youtubedl.moveToThread(self.yt_thread) self.yt_thread.started.connect(self.youtubedl.get_url) self.youtubedl.success.connect(self.on_yt_success) self.youtubedl.fail.connect(self.on_youtubedl_fail) self.youtubedl.finish.connect(self.yt_thread.exit) self.yt_thread.start() @Slot() def on_youtubedl_fail(self) -> None: """ If Youtube-dl for whatever reason failed to load the video, a fallback error video is shown, along with a message to let the user know what happened. """ self.player.start_video(Res.default_video, self.api.is_playing) print("The video wasn't found, either because of an issue with your" " internet connection or because the provided data was invalid." " For more information, enable the debug mode.") @Slot(str) def on_yt_success(self, url: str) -> None: """ Obtains the video URL from the Youtube-dl thread and starts playing the video. Also shows the lyrics if enabled. The position of the video isn't set if it's using audiosync, because this is done by the AudiosyncWorker thread. """ self.player.start_video(url, self.api.is_playing) if not self.config.audiosync: try: self.player.position = self.api.position except NotImplementedError: self.player.position = 0 # Finally, the lyrics are displayed. If the video wasn't found, an # error message is shown. if self.config.lyrics: print(get_lyrics(self.api.artist, self.api.title)) @Slot() def on_audiosync_fail(self) -> None: """ Currently, when audiosync fails, nothing happens. """ logging.info("Audiosync module failed to return the lag") @Slot(int) def on_audiosync_success(self, lag: int) -> None: """ Slot used after the audiosync function has finished. It sets the returned lag in milliseconds on the player. This assumes that the song wasn't paused until this issue is fixed: https://github.com/vidify/audiosync/issues/12 """ logging.info("Audiosync module returned %d ms", lag) # The current API position according to what's being recorded. playback_delay = round((time.time() - self.timestamp) * 1000) \ - self.player.position lag += playback_delay # The user's custom audiosync delay. This is basically the time taken # until the module started recording (which may depend on the user # hardware and other things). Thus, it will almost always be a # negative value. lag += self.config.audiosync_calibration logging.info("Total delay is %d ms", lag) if lag > 0: self.player.position += lag elif lag < 0: # If a negative delay is larger than the current player position, # the player position is set to zero after the lag has passed # with a timer. if self.player.position < -lag: self.sync_timer = QTimer(self) self.sync_timer.singleShot( -lag, lambda: self.change_video_position(0)) else: self.player.position += lag def init_spotify_web_api(self) -> None: """ SPOTIFY WEB API CUSTOM FUNCTION Note: the Tekore imports are done inside the functions so that Tekore isn't needed for whoever doesn't plan to use the Spotify Web API. """ from vidify.api.spotify.web import get_token from vidify.gui.api.spotify_web import SpotifyWebPrompt token = get_token(self.config.refresh_token, self.config.client_id, self.config.client_secret) if token is not None: # If the previous token was valid, the API can already start. logging.info("Reusing a previously generated token") self.start_spotify_web_api(token, save_config=False) else: # Otherwise, the credentials are obtained with the GUI. When # a valid auth token is ready, the GUI will initialize the API # automatically exactly like above. The GUI won't ask for a # redirect URI for now. logging.info("Asking the user for credentials") # The SpotifyWebPrompt handles the interaction with the user and # emits a `done` signal when it's done. self._spotify_web_prompt = SpotifyWebPrompt( self.config.client_id, self.config.client_secret, self.config.redirect_uri) self._spotify_web_prompt.done.connect(self.start_spotify_web_api) self.layout.addWidget(self._spotify_web_prompt) def start_spotify_web_api(self, token: 'RefreshingToken', save_config: bool = True) -> None: """ SPOTIFY WEB API CUSTOM FUNCTION Initializes the Web API, also saving them in the config for future usage (if `save_config` is true). """ from vidify.api.spotify.web import SpotifyWebAPI logging.info("Initializing the Spotify Web API") # Initializing the web API self.api = SpotifyWebAPI(token) api_data = APIData['SPOTIFY_WEB'] self.wait_for_connection( self.api.connect_api, message=api_data.connect_msg, event_loop_interval=api_data.event_loop_interval) # The obtained credentials are saved for the future if save_config: logging.info("Saving the Spotify Web API credentials") self.config.client_secret = self._spotify_web_prompt.client_secret self.config.client_id = self._spotify_web_prompt.client_id self.config.refresh_token = token.refresh_token # The credentials prompt widget is removed after saving the data. It # may not exist because start_spotify_web_api was called directly, # so errors are taken into account. try: self.layout.removeWidget(self._spotify_web_prompt) self._spotify_web_prompt.hide() del self._spotify_web_prompt except AttributeError: pass
class QtRemoteDispatcher(QObject): """ Dispatch documents received over the network from a Kafka broker. This is designed to be run in a Qt application. Parameters ---------- topics: list List of topics as strings such as ["topic-1", "topic-2"] bootstrap_servers : str Comma-delimited list of Kafka server addresses as a string such as ``'127.0.0.1:9092'`` group_id: str Required string identifier for Kafka Consumer group consumer_config: dict Override default configuration or specify additional configuration options to confluent_kafka.Consumer. polling_duration: float Time in seconds to wait for a message before running function work_while_waiting. Default is 0.05. deserializer: function, optional optional function to deserialize data. Default is msgpack.loads. parent_qobject: QObject optional parent in the QT sense Example ------- Plot data from all documents generated by remote RunEngines. >>> d = RemoteDispatcher( >>> topics=["abc.bluesky.documents", "ghi.bluesky.documents"], >>> bootstrap_servers='localhost:9092', >>> group_id="document-printers", >>> consumer_config={ >>> "auto.offset.reset": "latest" >>> } >>> ) >>> d.subscribe(stream_documents_into_runs(model.add_run)) >>> d.start() # launches periodic workers on background threads >>> d.stop() # stop launching workers """ def __init__( self, topics, bootstrap_servers, group_id, consumer_config=None, polling_duration=0.05, deserializer=msgpack.loads, parent_qobject=None, ): super().__init__(parent_qobject) self.closed = False self._timer = QTimer(self) self._dispatcher = bluesky_kafka.RemoteDispatcher( topics=topics, bootstrap_servers=bootstrap_servers, group_id=group_id, consumer_config=consumer_config, polling_duration=polling_duration, deserializer=deserializer, ) self.subscribe = self._dispatcher.subscribe self._waiting_for_start = True self.worker = None def _receive_data(self, continue_polling=None): # TODO Think about back pressure. if continue_polling is None: def continue_polling_forever(): return True continue_polling = continue_polling_forever while continue_polling(): try: # there should maybe be a poll method on the dispatcher msg = self._dispatcher._bluesky_consumer.consumer.poll( self._dispatcher._bluesky_consumer.polling_duration) if msg is None: logger.debug("no message") break elif msg.error(): logger.error("Kafka Consumer error: %s", msg.error()) else: try: # there should be a more direct way to deserialize the message ( name, document, ) = self._dispatcher._bluesky_consumer._deserializer( msg.value()) if self._waiting_for_start: if name == "start": self._waiting_for_start = False else: # We subscribed midstream and are seeing documents for # which we do not have the full run. Wait for a 'start' # doc. logger.debug( "keep waiting for a start document") return yield self._dispatcher._bluesky_consumer.consumer, msg.topic( ), name, document except Exception as exc: logger.exception(exc) except Exception as exc: logger.exception(exc) logger.debug("polling loop has ended cleanly") def start(self, continue_polling=None): logger.debug("QtRemoteDispatcher.start") if self.closed: raise RuntimeError("This RemoteDispatcher has already been " "started and interrupted. Create a fresh " "instance with {}".format(repr(self))) self._work_loop(continue_polling=continue_polling) def _work_loop(self, continue_polling=None): self.worker = create_worker( self._receive_data, continue_polling=continue_polling, ) # Schedule this method to be run again after a brief wait. self.worker.finished.connect(lambda: self._timer.singleShot( int(LOADING_LATENCY), functools.partial(self._work_loop, continue_polling), )) self.worker.yielded.connect(self._process_result) self.worker.start() def _process_result(self, result): if result is None: return consumer, topic, name, document = result self._dispatcher.process_document(consumer=consumer, topic=topic, name=name, document=document) def stop(self): logger.debug("QtRemoteDispatcher.stop") self.closed = True
def event_jupyter_execute_finished(self): """If the jupyter kernel is done, execute the next queued cell.""" if self._cells_to_run_queue: QTimer.singleShot(1000, self._run_cell_from_queue)
def toggleLeds(self): """Toggle leds state.""" for led in self.leds: led.toggleState() QTimer.singleShot(1000, self.toggleLeds)
class RemoteDispatcher(QObject): """ Dispatch documents received over the network from a 0MQ proxy. This is designed to be run in a Qt application. Parameters ---------- address : tuple | str Address of a running 0MQ proxy, given either as a string like ``'127.0.0.1:5567'`` or as a tuple like ``('127.0.0.1', 5567)`` prefix : bytes, optional User-defined bytestring used to distinguish between multiple Publishers. If set, messages without this prefix will be ignored. If unset, no mesages will be ignored. deserializer: function, optional optional function to deserialize data. Default is pickle.loads Examples -------- Print all documents generated by remote RunEngines. >>> d = RemoteDispatcher(('localhost', 5568)) >>> d.subscribe(stream_documents_into_runs(model.add_run)) >>> d.start() # launches periodic workers on background threads >>> d.stop() # stop launching workers """ def __init__(self, address, *, prefix=b"", deserializer=pickle.loads, parent=None): super().__init__(parent) if isinstance(prefix, str): raise ValueError("prefix must be bytes, not string") if b" " in prefix: raise ValueError("prefix {!r} may not contain b' '".format(prefix)) self._prefix = prefix if isinstance(address, str): address = address.split(":", maxsplit=1) self._deserializer = deserializer self.address = (address[0], int(address[1])) self._context = zmq.Context() self._socket = self._context.socket(zmq.SUB) url = "tcp://%s:%d" % self.address self._socket.connect(url) self._socket.setsockopt_string(zmq.SUBSCRIBE, "") self._task = None self.closed = False self._timer = QTimer(self) self._dispatcher = Dispatcher() self.subscribe = self._dispatcher.subscribe self._waiting_for_start = True def _receive_data(self): our_prefix = self._prefix # local var to save an attribute lookup # TODO Pull on the socket more than once here, until it blocks, to # ensure we do not get more and more behind over time. message = self._socket.recv() prefix, name, doc = message.split(b" ", 2) name = name.decode() if (not our_prefix) or prefix == our_prefix: if self._waiting_for_start: if name == "start": self._waiting_for_start = False else: # We subscribed midstream and are seeing documents for # which we do not have the full run. Wait for a 'start' # doc. return doc = self._deserializer(doc) return name, doc def start(self): if self.closed: raise RuntimeError("This RemoteDispatcher has already been " "started and interrupted. Create a fresh " "instance with {}".format(repr(self))) self._work_loop() def _work_loop(self): worker = create_worker(self._receive_data, ) # Schedule this method to be run again after a brief wait. worker.finished.connect( lambda: self._timer.singleShot(LOADING_LATENCY, self._work_loop)) worker.returned.connect(self._process_result) worker.start() def _process_result(self, result): if result is None: return name, doc = result self._dispatcher.process(DocumentNames[name], doc) def stop(self): self.closed = True
def start_delayed(self): QTimer.singleShot(100, self._start_when_locked)
class PluginManager(QObject): introspection_complete = Signal(object) def __init__(self, executable): super(PluginManager, self).__init__() plugins = OrderedDict() for name in PLUGINS: try: plugin = PluginClient(name, executable) plugin.run() except Exception as e: debug_print('Introspection Plugin Failed: %s' % name) debug_print(str(e)) continue debug_print('Introspection Plugin Loaded: %s' % name) plugins[name] = plugin plugin.received.connect(self.handle_response) self.plugins = plugins self.timer = QTimer() self.desired = [] self.ids = dict() self.info = None self.request = None self.pending = None self.pending_request = None self.waiting = False def send_request(self, info): """Handle an incoming request from the user.""" if self.waiting: if info.serialize() != self.info.serialize(): self.pending_request = info else: debug_print('skipping duplicate request') return debug_print('%s request' % info.name) desired = None self.info = info editor = info.editor if (info.name == 'completion' and 'jedi' not in self.plugins and info.line.lstrip().startswith(('import ', 'from '))): desired = 'fallback' if ((not editor.is_python_like()) or sourcecode.is_keyword(info.obj) or (editor.in_comment_or_string() and info.name != 'info')): desired = 'fallback' plugins = self.plugins.values() if desired: plugins = [self.plugins[desired]] self.desired = [desired] elif (info.name == 'definition' and not info.editor.is_python() or info.name == 'info'): self.desired = list(self.plugins.keys()) else: # Use all but the fallback plugins = list(self.plugins.values())[:-1] self.desired = list(self.plugins.keys())[:-1] self._start_time = time.time() self.waiting = True method = 'get_%s' % info.name value = info.serialize() self.ids = dict() for plugin in plugins: request_id = plugin.request(method, value) self.ids[request_id] = plugin.name self.timer.stop() self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout) def validate(self): for plugin in self.plugins.values(): plugin.request('validate') def handle_response(self, response): name = self.ids.get(response['request_id'], None) if not name: return if response.get('error', None): debug_print('Response error:', response['error']) return if name == self.desired[0] or not self.waiting: if response.get('result', None): self._finalize(response) else: self.pending = response def close(self): [plugin.close() for plugin in self.plugins] def _finalize(self, response): self.waiting = False self.pending = None if self.info: delta = time.time() - self._start_time debug_print('%s request from %s finished: "%s" in %.1f sec' % (self.info.name, response['name'], str(response['result'])[:100], delta)) response['info'] = self.info self.introspection_complete.emit(response) self.info = None if self.pending_request: info = self.pending_request self.pending_request = None self.send_request(info) def _handle_timeout(self): self.waiting = False if self.pending: self._finalize(self.pending) else: debug_print('No valid responses acquired')
def _main(args): # To avoid problems when testing without screen if args.test or args.screenshots: os.environ['QT_QPA_PLATFORM'] = 'offscreen' # Set QT_API variable before importing QtPy if args.qt_from in ['pyqt', 'pyqt5', 'pyside', 'pyside2']: os.environ['QT_API'] = args.qt_from elif args.qt_from == 'pyqtgraph': os.environ['QT_API'] = os.environ['PYQTGRAPH_QT_LIB'] elif args.qt_from in ['qt.py', 'qt']: try: import Qt except ImportError: print('Could not import Qt (Qt.Py)') else: os.environ['QT_API'] = Qt.__binding__ # QtPy imports from qtpy import API_NAME, QT_VERSION, PYQT_VERSION, PYSIDE_VERSION from qtpy import __version__ as QTPY_VERSION from qtpy.QtWidgets import (QApplication, QMainWindow, QDockWidget, QStatusBar, QLabel, QMenu) from qtpy.QtCore import QTimer, Qt, QSettings # Set API_VERSION variable API_VERSION = '' if PYQT_VERSION: API_VERSION = PYQT_VERSION elif PYSIDE_VERSION: API_VERSION = PYSIDE_VERSION else: API_VERSION = 'Not found' # Import examples UI from mw_menus_ui import Ui_MainWindow as ui_main from dw_buttons_ui import Ui_DockWidget as ui_buttons from dw_displays_ui import Ui_DockWidget as ui_displays from dw_inputs_fields_ui import Ui_DockWidget as ui_inputs_fields from dw_inputs_no_fields_ui import Ui_DockWidget as ui_inputs_no_fields from dw_widgets_ui import Ui_DockWidget as ui_widgets from dw_views_ui import Ui_DockWidget as ui_views from dw_containers_tabs_ui import Ui_DockWidget as ui_containers_tabs from dw_containers_no_tabs_ui import Ui_DockWidget as ui_containers_no_tabs # qrainbowstyle.useDarwinButtons() QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # create the application if not QApplication.instance(): app = QApplication(sys.argv) else: app = QApplication.instance() app.setOrganizationName('QRainbowStyle') app.setApplicationName('QRainbowStyle Example') styles = qrainbowstyle.getAvailableStyles() style = args.style if not args.style: style = styles[random.randint(0, len(styles)) - 1] app.setStyleSheet(qrainbowstyle.load_stylesheet(style=str(style))) # create main window window = qrainbowstyle.windows.FramelessWindow() window.setTitlebarHeight(30) widget = QMainWindow(window) widget.setWindowFlags(Qt.Widget) widget.setObjectName('mainwindow') ui = ui_main() ui.setupUi(widget) window.addContentWidget(widget) title = ("QRainbowStyle Example - " + "(QRainbowStyle=v" + qrainbowstyle.__version__ + ", QtPy=v" + QTPY_VERSION + ", " + API_NAME + "=v" + API_VERSION + ", Qt=v" + QT_VERSION + ", Python=v" + platform.python_version() + ")") _logger.info(title) window.setWindowTitle(title) # Create docks for buttons dw_buttons = QDockWidget() dw_buttons.setObjectName('buttons') ui_buttons = ui_buttons() ui_buttons.setupUi(dw_buttons) widget.addDockWidget(Qt.RightDockWidgetArea, dw_buttons) # Add actions on popup toolbuttons menu = QMenu() for action in ['Action A', 'Action B', 'Action C']: menu.addAction(action) ui_buttons.toolButtonDelayedPopup.setMenu(menu) ui_buttons.toolButtonInstantPopup.setMenu(menu) ui_buttons.toolButtonMenuButtonPopup.setMenu(menu) # Create docks for buttons dw_displays = QDockWidget() dw_displays.setObjectName('displays') ui_displays = ui_displays() ui_displays.setupUi(dw_displays) widget.addDockWidget(Qt.RightDockWidgetArea, dw_displays) # Create docks for inputs - no fields dw_inputs_no_fields = QDockWidget() dw_inputs_no_fields.setObjectName('inputs_no_fields') ui_inputs_no_fields = ui_inputs_no_fields() ui_inputs_no_fields.setupUi(dw_inputs_no_fields) widget.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_no_fields) # Create docks for inputs - fields dw_inputs_fields = QDockWidget() dw_inputs_fields.setObjectName('inputs_fields') ui_inputs_fields = ui_inputs_fields() ui_inputs_fields.setupUi(dw_inputs_fields) widget.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_fields) # Create docks for widgets dw_widgets = QDockWidget() dw_widgets.setObjectName('widgets') ui_widgets = ui_widgets() ui_widgets.setupUi(dw_widgets) widget.addDockWidget(Qt.LeftDockWidgetArea, dw_widgets) # Create docks for views dw_views = QDockWidget() dw_views.setObjectName('views') ui_views = ui_views() ui_views.setupUi(dw_views) widget.addDockWidget(Qt.LeftDockWidgetArea, dw_views) # Create docks for containers - no tabs dw_containers_no_tabs = QDockWidget() dw_containers_no_tabs.setObjectName('containers_no_tabs') ui_containers_no_tabs = ui_containers_no_tabs() ui_containers_no_tabs.setupUi(dw_containers_no_tabs) widget.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_no_tabs) # Create docks for containters - tabs dw_containers_tabs = QDockWidget() dw_containers_tabs.setObjectName('containers_tabs') ui_containers_tabs = ui_containers_tabs() ui_containers_tabs.setupUi(dw_containers_tabs) widget.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_tabs) # Tabify right docks widget.tabifyDockWidget(dw_buttons, dw_displays) widget.tabifyDockWidget(dw_displays, dw_inputs_fields) widget.tabifyDockWidget(dw_inputs_fields, dw_inputs_no_fields) # Tabify left docks widget.tabifyDockWidget(dw_containers_no_tabs, dw_containers_tabs) widget.tabifyDockWidget(dw_containers_tabs, dw_widgets) widget.tabifyDockWidget(dw_widgets, dw_views) # Issues #9120, #9121 on Spyder qstatusbar = QStatusBar() qstatusbar.addWidget(QLabel('Style')) qstatusbarbutton = qrainbowstyle.widgets.StylePickerHorizontal() qstatusbar.addWidget(qstatusbarbutton) qstatusbar.setSizeGripEnabled(False) # Add info also in status bar for screenshots get it qstatusbar.addWidget(QLabel('INFO: ' + title)) widget.setStatusBar(qstatusbar) # Todo: add report info and other info in HELP graphical # Auto quit after 2s when in test mode if args.test: QTimer.singleShot(2000, app.exit) _read_settings(widget, args.reset, QSettings) window.show() # window.showMaximized() app.exec_() _write_settings(widget, QSettings)