def __init__(self) -> None: super().__init__() self._handle = TranslateToolHandle.TranslateToolHandle( ) #type: TranslateToolHandle.TranslateToolHandle #Because for some reason MyPy thinks this variable contains Optional[ToolHandle]. self._enabled_axis = [ ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis ] self._grid_snap = False self._grid_size = 10 self._moved = False self._shortcut_key = Qt.Key.Key_T self._distance_update_time = None #type: Optional[float] self._distance = None #type: Optional[Vector] self.setExposedProperties("ToolHint", "X", "Y", "Z", SceneNodeSettings.LockPosition) self._update_selection_center_timer = QTimer() self._update_selection_center_timer.setInterval(50) self._update_selection_center_timer.setSingleShot(True) self._update_selection_center_timer.timeout.connect( self.propertyChanged.emit) # Ensure that the properties (X, Y & Z) are updated whenever the selection center is changed. Selection.selectionCenterChanged.connect( self._onSelectionCenterChanged) # CURA-5966 Make sure to render whenever objects get selected/deselected. Selection.selectionChanged.connect(self.propertyChanged)
def __init__(self, *args, **kwargs): super(ProgressBar, self).__init__(*args, **kwargs) self.setValue(0) if self.minimum() != self.maximum(): self.timer = QTimer(self) self.timer.timeout.connect(self.onTimeout) self.timer.start(100)
def __init__(self, parent=None) -> None: super().__init__(parent) self.addRoleName(self.NameRole, "name") self.addRoleName(self.IdRole, "id") self.addRoleName(self.MetaDataRole, "metadata") self.addRoleName(self.ReadOnlyRole, "readOnly") self.addRoleName(self.SectionRole, "section") #We keep track of two sets: One for normal containers that are already fully loaded, and one for containers of which only metadata is known. #Both of these are indexed by their container ID. self._instance_containers = {} #type: Dict[str, InstanceContainer] self._instance_containers_metadata = { } # type: Dict[str, Dict[str, Any]] self._section_property = "" # Listen to changes ContainerRegistry.getInstance().containerAdded.connect( self._onContainerChanged) ContainerRegistry.getInstance().containerRemoved.connect( self._onContainerChanged) ContainerRegistry.getInstance().containerLoadComplete.connect( self._onContainerLoadComplete) self._container_change_timer = QTimer() self._container_change_timer.setInterval(150) self._container_change_timer.setSingleShot(True) self._container_change_timer.timeout.connect(self._update) # List of filters for queries. The result is the union of the each list of results. self._filter_dicts = [] # type: List[Dict[str, str]] self._container_change_timer.start()
def createTimer(self): self.timer = QTimer() self.timer.timeout.connect(self.updateTime) self.timer.timeout.connect(self.maybeChangeMode) self.timer.setInterval(1000) self.timer.setSingleShot(False) self.timer.start()
def close_tab(self, tab=None): tab = tab or self.current_tab if tab is not None: self.delete_removed_tabs(self.tab_tree.remove_tab(tab)) if not self.tabs: self.open_url(WELCOME_URL, switch_to_tab=True) QTimer.singleShot(0, self.current_tab_changed)
class PlayerLabel(QLabel): loading_movie = None def __init__(self, fontsize, parent=None): super().__init__(parent) self.fontsize = fontsize cls = type(self) if not cls.loading_movie: cls.loading_movie = QMovie(resource_path("loading.gif")) cls.loading_movie.setScaledSize(QSize(MOVIEWIDTH, MOVIEWIDTH)) cls.loading_movie.start() # self.player_heading.setGeometry(0, 140, self.rect().width(), 50) # self.player_heading.setAlignment(Qt.AlignmentFlag.AlignHCenter) f = self.font() f.setPointSize(self.fontsize) self.setFont(f) self.setAutoFillBackground(True) self.setMovie(cls.loading_movie) self.blink_timer = None def buzz_hint(self): self.setStyleSheet("QLabel { background-color : grey}") self.blink_timer = QTimer() # self.blink_timer.moveToThread(QApplication.instance().thread()) self.blink_timer.timeout.connect(self._buzz_hint_callback) self.blink_timer.start(100) def _buzz_hint_callback(self): self.setStyleSheet("QLabel { background-color : none}")
def restart(self): self.player_view.close() self.player_view = PlayerView(self.rect() - QMargins(0, 210, 0, 0), parent=self) # self.show_overlay() QTimer.singleShot(500, self.show_overlay) self.startButton.setEnabled(False)
def __init__(self, size=10 * 1024 * 1024): QObject.__init__(self) self._state = QWebEngineDownloadRequest.DownloadState.DownloadRequested self._received = self._total = -1 FakeDownloadItem.idc += 1 self._id = FakeDownloadItem.idc self._size = size self.fname = '%s.epub' % self.id() self.mimeType = lambda: mimetypes.guess_type(self.fname)[0] QTimer.singleShot(100, self._tick)
def showMessage(self, message: Message) -> None: with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setLifetimeTimer(QTimer()) message.setInactivityTimer(QTimer()) self.visibleMessageAdded.emit(message) # also show toast message when the main window is minimized self.showToastMessage(self._app_name, message.getText())
def start_update_timer(self, mins: int = 180) -> None: """Check for updates every 3 hrs""" if not cf.SYS_FROZEN: return msec = mins * 60 * 1000 self.update_timer = QTimer(parent=self) self.update_timer.timeout.connect(self.check_update) self.update_timer.start(msec)
def __init__( self, parent=None, buttons=None, exercises=None, index: int = None, ): super(ChangeKeyDialog, self).__init__(parent) layout = QVBoxLayout(self) self.setLayout(layout) widget = QWidget() keyLayout = QVBoxLayout() widget.setStyleSheet(""" QWidget{ border-radius: 12px; border: 1px solid grey; background-color: #b5b5b5; color: white; font-size: 40px; } """) # widget.setFixedSize(100, 100) self.currentKeyLabel = QLabel('W') keyLayout.addWidget(self.currentKeyLabel) keyLayout.setAlignment(self.currentKeyLabel, Qt.Alignment.AlignCenter) widget.setLayout(keyLayout) label = QLabel("Press a key to swap") emptyKey = QPushButton('Use empty slot') emptyKey.setFocusPolicy(Qt.FocusPolicy.ClickFocus) emptyKey.clicked.connect(self.useEmpty) acceptKey = QPushButton('Accept') acceptKey.clicked.connect(self.accept) acceptKey.setFocusPolicy(Qt.FocusPolicy.ClickFocus) layout.addWidget(label) layout.addWidget(widget) actions = QHBoxLayout() actions.addWidget(emptyKey) actions.addWidget(acceptKey) layout.addLayout(actions) layout.setAlignment(widget, Qt.Alignment.AlignCenter) self.buttons = buttons self.exercises = exercises self.index = index self.monitor = KeyMonitor() self.monitor.start_monitoring() self.currentKey = self.monitor.currentKey self.timer = QTimer() self.timer.timeout.connect(self.onTimeout) self.timer.start() print("Dialog init done!")
def __init__(self, request_id: str, http_method: str, request: "QNetworkRequest", manager_timeout_callback: Callable[["HttpRequestData"], None], data: Optional[Union[bytes, bytearray]] = None, callback: Optional[Callable[["QNetworkReply"], None]] = None, error_callback: Optional[ Callable[["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None, download_progress_callback: Optional[Callable[[int, int], None]] = None, upload_progress_callback: Optional[Callable[[int, int], None]] = None, timeout: Optional[float] = None, reply: Optional["QNetworkReply"] = None, parent: Optional["QObject"] = None) -> None: super().__init__(parent=parent) # Sanity checks if timeout is not None and timeout <= 0: raise ValueError( "Timeout must be a positive value, but got [%s] instead." % timeout) self._request_id = request_id self.http_method = http_method self.request = request self.data = data self.callback = callback self.error_callback = error_callback self.download_progress_callback = download_progress_callback self.upload_progress_callback = upload_progress_callback self._timeout = timeout self.reply = reply # For benchmarking. For calculating the time a request spent pending. self._create_time = time.time() # The timestamp when this request was initially issued to the QNetworkManager. This field to used to track and # manage timeouts (if set) for the requests. self._start_time = None # type: Optional[float] self.is_aborted_due_to_timeout = False self._last_response_time = float(0) self._timeout_timer = QTimer(parent=self) if self._timeout is not None: self._timeout_timer.setSingleShot(True) timeout_check_interval = int(self._timeout * 1000 * (1 + self.TIMEOUT_CHECK_TOLERANCE)) self._timeout_timer.setInterval(timeout_check_interval) self._timeout_timer.timeout.connect(self._onTimeoutTimerTriggered) self._manager_timeout_callback = manager_timeout_callback
def __init__(self): self._clock_fmt = '%Y-%m-%d %H:%M:%S %Z%z' self._tz_utc = pytz.timezone('UTC') self._tz_eastern = pytz.timezone('US/Eastern') self._tz_central = pytz.timezone('US/Central') self._tz_mountain = pytz.timezone('US/Mountain') self._tz_pacific = pytz.timezone('US/Pacific') self._tz_berlin = pytz.timezone('Europe/Berlin') self._tz_london = pytz.timezone('Europe/London') self._tz_paris = pytz.timezone('Europe/Paris') self._timer = QTimer() self._timer.timeout.connect(self._show_clock)
class KeysWidget(QWidget): keyPressed = QtCore.pyqtSignal(QtCore.QEvent) def __init__(self, parent=None): super(KeysWidget, self).__init__(parent) self.classifyExercises = None if parent is not None: self.classifyExercises = parent.classifyExercises self.infoLabel = parent.infoLabel self.ui = Ui_KeysPanel() self.ui.setupUi(self) self.monitor = KeyMonitor() self.monitor.start_monitoring() self.timer = QTimer() self.timer.timeout.connect(self.onTimeout) self.timer.start() self.ui.saveProfile.clicked.connect(self.saveBindings) def saveBindings(self): if self.classifyExercises.subject is not None: key_list = [x.serialize() for x in self.classifyExercises.exercises.values()] content = { self.classifyExercises.subject: key_list } print(content) with open(MAPPED_KEYS_PATH + self.classifyExercises.subject + '.json', "w") as f: json.dump(content, f) def onTimeout(self): if self.monitor.released: for b in self.ui.buttons: b.setStyleSheet( """ QPushButton { border: 1px solid grey; background-color: white; } """) else: for ind in range(0, len(self.ui.exercises)): if self.monitor.currentKey == self.ui.exercises[ind].assigned_key[1]: self.ui.buttons[ind].setStyleSheet( """ QPushButton { border: 1px solid green; background-color: #7FFFD4; } """)
def call_auto_get_cookie(self): """自动读取浏览器cookie槽函数""" try: self._cookie = get_cookie_from_browser() except Exception as e: logger.error(f"Browser_cookie3 Error: {e}") self.auto_get_cookie_ok.setPlainText(f"❌获取失败,错误信息\n{e}") else: if self._cookie: self._user = self._pwd = '' self.auto_get_cookie_ok.setPlainText("✅获取成功即将登录……") QTimer.singleShot(2000, self._close_dialog) else: self.auto_get_cookie_ok.setPlainText("❌获取失败\n请提前使用支持的浏览器登录蓝奏云,读取前完全退出浏览器!\n支持的浏览器与顺序:\nchrome, chromium, opera, edge, firefox")
def __init__(self, output_controller: PrinterOutputController) -> None: """Constructor. Args: output_controller: Printer's output controller. """ super().__init__(output_controller=output_controller, key='', name='') self._state = 'not_started' self._progress = 0 # type: int self._elapsed_print_time_millis = 0 # type: int self._elapsed_percentage_points = None # type: Optional[int] # Estimated printing time left, in seconds. self._remaining_print_time_secs = _MAX_REMAINING_TIME_SECS self._stopwatch = QTimer(self) self._stopwatch.timeout.connect(self._tick) self._reset()
def run_app(urls=(), callback=None, callback_wait=0, master_password=None, new_instance=False, shutdown=False, restart_state=None, no_session=False, startup_session=None): env = os.environ.copy() app = Application(master_password=master_password, urls=urls, new_instance=new_instance, shutdown=shutdown, restart_state=restart_state, no_session=no_session) os.environ['QTWEBENGINE_DICTIONARIES_PATH'] = os.path.join( config_dir, 'spell') original_env = env style = Style() app.setStyle(style) try: if startup_session is not None: with open(startup_session, 'rb') as f: app.unserialize_state(pickle.load(f)) elif restart_state is not None: app.unserialize_state(restart_state) else: last_session = last_saved_session(no_session) if last_session is None or urls: app.open_urls(urls) else: app.unserialize_state(last_session) if callback is not None: QTimer.singleShot(callback_wait, callback) app.exec() finally: app.break_cycles() delete_profile() places.close() app.sendPostedEvents() restart_state = getattr(app, 'restart_state', None) sip.delete(app) del app gc.collect(), gc.collect(), gc.collect() if restart_state is not None: restart(restart_state, original_env)
def __init__(self, parent=None): super(KeysWidget, self).__init__(parent) self.classifyExercises = None if parent is not None: self.classifyExercises = parent.classifyExercises self.infoLabel = parent.infoLabel self.ui = Ui_KeysPanel() self.ui.setupUi(self) self.monitor = KeyMonitor() self.monitor.start_monitoring() self.timer = QTimer() self.timer.timeout.connect(self.onTimeout) self.timer.start() self.ui.saveProfile.clicked.connect(self.saveBindings)
def open_devtools_tab(self, web_page): ''' Open devtools tab''' self.devtools_page = web_page eval_in_emacs('eaf-open-devtool-page', []) # We need adjust web window size after open developer tool. QTimer().singleShot( 1000, lambda: eval_in_emacs('eaf-monitor-configuration-change', []))
def _tick(self): if self._state not in (QWebEngineDownloadRequest.DownloadState.DownloadInProgress, QWebEngineDownloadRequest.DownloadState.DownloadRequested): return if self._total == -1: self._total = self._size self._received = 0 self._state = QWebEngineDownloadRequest.DownloadState.DownloadInProgress self.stateChanged.emit(self._state) self.downloadProgress.emit(self._received, self._total) QTimer.singleShot(100, self._tick) elif self._received < self._total: self._received += min(self._total - self._received, self._size // 100) self.downloadProgress.emit(self._received, self._total) if self._received >= self._total: self._state = QWebEngineDownloadRequest.DownloadState.DownloadCompleted self.stateChanged.emit(self._state) self.finished.emit() else: QTimer.singleShot(100, self._tick)
class WinForm(QWidget): def __init__(self, parent=None): super(WinForm, self).__init__(parent) self.setWindowTitle("QTimer demo") self.listFile = QListWidget() self.label = QLabel("显示当前时间") self.startButton = QPushButton("开始") self.endButton = QPushButton("结束") layout = QGridLayout(self) # 初始化定时器 self.timer = QTimer(self) # 显示时间 self.timer.timeout.connect( self.showTime) # timeout 信号连接到特定的槽,当定时器超时,发出 timeout 信号 layout.addWidget(self.label, 0, 0, 1, 2) layout.addWidget(self.startButton, 1, 0) layout.addWidget(self.endButton, 1, 1) self.startButton.clicked.connect(self.start_timer) self.endButton.clicked.connect(self.end_timer) self.setLayout(layout) def showTime(self): # 获取当前系统时间 time = QDateTime.currentDateTime() # 设置时间格式 timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd") self.label.setText(timeDisplay) def start_timer(self): # 设置时间间隔并启动定时器 self.timer.start(1000) # start 内设置时间间隔,启动或重新启动计时器,如果计时器在运行,则重启 self.startButton.setEnabled(False) self.endButton.setEnabled(True) def end_timer(self): self.timer.stop() # 停止计时器 self.startButton.setEnabled(True) self.endButton.setEnabled(False)
def __init__(self, parent=None) -> None: super().__init__(parent=parent) self._property_map = QQmlPropertyMap(self) self._stack = None # type: Optional[ContainerStack] self._key = "" self._relations = set() # type: Set[str] self._watched_properties = [] # type: List[str] self._store_index = 0 self._value_used = None # type: Optional[bool] self._stack_levels = [] # type: List[int] self._remove_unused_value = True self._validator = None # type: Optional[Validator] self._update_timer = QTimer(self) self._update_timer.setInterval(100) self._update_timer.setSingleShot(True) self._update_timer.timeout.connect(self._update) self.storeIndexChanged.connect(self._storeIndexChanged)
class ProgressBar(QProgressBar): def __init__(self, *args, **kwargs): super(ProgressBar, self).__init__(*args, **kwargs) self.setValue(0) if self.minimum() != self.maximum(): self.timer = QTimer(self) self.timer.timeout.connect(self.onTimeout) self.timer.start(100) def start(self): self.timer.start(100) def stop(self): self.timer.stop() def onTimeout(self): if self.value() >= 100: self.timer.stop() self.timer.deleteLater() del self.timer return self.setValue(self.value() + 1)
def _scheduleDelayedCallEvent(self, event: "_CallFunctionEvent") -> None: if event.delay is None: return timer = QTimer(self) timer.setSingleShot(True) timer.setInterval(event.delay * 1000 * (1 + self.TIME_TOLERANCE)) timer_callback = lambda e=event: self._onDelayReached(e) timer.timeout.connect(timer_callback) timer.start() self._delayed_events[event] = { "event": event, "timer": timer, "timer_callback": timer_callback, }
def __init__(self, parent=None): super(WinForm, self).__init__(parent) self.setWindowTitle("QTimer demo") self.listFile = QListWidget() self.label = QLabel("显示当前时间") self.startButton = QPushButton("开始") self.endButton = QPushButton("结束") layout = QGridLayout(self) # 初始化定时器 self.timer = QTimer(self) # 显示时间 self.timer.timeout.connect( self.showTime) # timeout 信号连接到特定的槽,当定时器超时,发出 timeout 信号 layout.addWidget(self.label, 0, 0, 1, 2) layout.addWidget(self.startButton, 1, 0) layout.addWidget(self.endButton, 1, 1) self.startButton.clicked.connect(self.start_timer) self.endButton.clicked.connect(self.end_timer) self.setLayout(layout)
def __init__(self, on_cancelled: Callable) -> None: """Constructor. Args: on_cancelled: Called when user cancels printer upload. """ super().__init__(title=I18N_CATALOG.i18nc( '@info:status', 'Uploading model to printer'), text=self.CALCULATING_TEXT, progress=-1, lifetime=0, dismissable=False, use_inactivity_timer=False) self._on_cancelled = on_cancelled self._elapsed_upload_time_millis = 0 self._remaining_time_millis = self.MAX_REMAINING_MILLIS self.addAction('cancel', I18N_CATALOG.i18nc('@action:button', 'Cancel'), 'cancel', I18N_CATALOG.i18nc('@action', 'Cancels job upload.')) self.actionTriggered.connect(self._on_action_triggered) self._stopwatch = QTimer(self) self._stopwatch.timeout.connect(self._tick) self._reset_calculation_time()
def __init__(self, parent): QStackedWidget.__init__(self, parent) self.permanent_message = '' self.temporary_message = TemporaryMessage(1, '', 'info', monotonic()) self.msg = Message(self, parent.sb_background) self.addWidget(self.msg) self.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.msg.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.search = SearchPanel(self) self.search.edit.abort_search.connect(self.hide_search) self.search.edit.editingFinished.connect(self.hide_search) self.addWidget(self.search) self.update_timer = t = QTimer(self) self.fg_color = color('status bar foreground', None) if self.fg_color: self.fg_color = QColor(self.fg_color) t.setSingleShot(True), t.setInterval(100), t.timeout.connect( self.update_message)
class QDoublePushButton(QPushButton): """加入了双击事件的按钮""" doubleClicked = pyqtSignal() clicked = pyqtSignal() def __init__(self, *args, **kwargs): QPushButton.__init__(self, *args, **kwargs) self.timer = QTimer() self.timer.setSingleShot(True) self.timer.timeout.connect(self.clicked.emit) super().clicked.connect(self.checkDoubleClick) def checkDoubleClick(self): if self.timer.isActive(): self.doubleClicked.emit() self.timer.stop() else: self.timer.start(250)
def __init__(self, icon_name='busy.svg', icon_size=24, duration=2, frames=120, parent=None): QObject.__init__(self, parent) pmap = get_icon(icon_name).pixmap(icon_size, icon_size) self.interval = duration * 1000 // frames self.timer = t = QTimer(self) t.setInterval(self.interval) t.timeout.connect(self.do_update) self.frame_number = 0 self.frames = [] angle_delta = 360 / frames angle = -angle_delta for i in range(frames): angle += angle_delta p = pmap if angle: p = self.rotated_by(pmap, angle) self.frames.append(p)
class MainWindow(QMainWindow): """Main application window""" minesite_changed = pyqtSignal(str) def __init__(self): super().__init__() self.app = QApplication.instance() self.setWindowTitle(gbl.title) self.setMinimumSize(QSize(1000, 400)) self.minesite_changed.connect(self.update_minesite_label) self.minesite_label = QLabel( self ) # permanent label for status bar so it isnt changed by statusTips self.minesite_label.setToolTip( 'Global MineSite > Set with [Ctrl + Shift + M]') self.rows_label = QLabel(self) self.statusBar().addPermanentWidget(self.rows_label) self.statusBar().addPermanentWidget(self.minesite_label) # Settings s = QSettings('sms', 'smseventlog', self) screen_point = s.value('window position', False) screen_size = s.value('window size', False) # if screen size/left anchor pt values are not set or out of range, use default if not (screen_point and screen_size and gbl.check_screen_point(screen_point)): screen_point = QPoint(50, 50) screen_size = QSize(1200, 1000) # move/resize MainWindow to last position/size self.resize(screen_size) self.move(screen_point) self.settings = s self.menus = {} self.create_actions() self.tabs = TabWidget(self) self.setCentralWidget(self.tabs) self.update_minesite_label() self.threadpool = QThreadPool(self) log.debug('Mainwindow init finished.') @property def minesite(self) -> str: """Global minesite setting""" return self.settings.value('minesite', defaultValue='FortHills') # return self._minesite @minesite.setter def minesite(self, val: Any) -> None: """Save minesite back to settings""" # self._minesite = val self.settings.setValue('minesite', val) self.minesite_changed.emit(val) def update_minesite_label(self, *args): """minesite_label is special label to always show current minesite (bottom right)""" self.minesite_label.setText(f'Minesite: {self.minesite}') def update_rows_label(self, *args): view = self.active_table() if view is None: return # not init yet model = view.data_model visible_rows = model.visible_rows total_rows = model.total_rows if total_rows == visible_rows: num_rows = visible_rows else: num_rows = f'{visible_rows}/{total_rows}' self.rows_label.setText(f'Rows: {num_rows}') def warn_not_implemented(self) -> None: """Let user know feature not implemented""" self.update_statusbar('Warning: This feature not yet implemented.') def update_statusbar(self, msg: str = None, warn: bool = False, success: bool = False, log_: bool = False, *args) -> None: """Statusbar shows temporary messages that disappear on any context event""" if not msg is None: # allow warn or success status to be passed with msg as dict if isinstance(msg, dict): warn = msg.get('warn', False) success = msg.get('success', False) msg = msg.get('msg', None) # kinda sketch if log_: log.info(msg) bar = self.statusBar() self.prev_status = bar.currentMessage() bar.showMessage(msg) msg_lower = msg.lower() if warn or 'warn' in msg_lower or 'error' in msg_lower: color = '#ff5454' # '#fa7070' elif success or 'success' in msg_lower: color = '#70ff94' else: color = 'white' palette = bar.palette() palette.setColor(QPalette.ColorRole.WindowText, QColor(color)) bar.setPalette(palette) self.app.processEvents() def revert_status(self): # revert statusbar to previous status if not hasattr(self, 'prev_status'): self.prev_status = '' self.update_statusbar(msg=self.prev_status) @er.errlog() def after_init(self): """Steps to run before MainWindow is shown. - Everything in here must suppress errors and continue """ self.username = self.get_username() self.init_sentry() self.u = users.User(username=self.username, mainwindow=self).login() log.debug('user init') last_tab_name = self.settings.value('active table', 'Event Log') self.tabs.init_tabs() self.tabs.activate_tab(title=last_tab_name) log.debug('last tab activated') # initialize updater self.updater = Updater(mw=self, dev_channel=self.get_setting('dev_channel')) log.debug(f'updater initialized, channel={self.updater.channel}') t = self.active_table_widget() if t.refresh_on_init: t.refresh(default=True, save_query=False) log.debug('last table refreshed') # startup update checks can allow ignoring dismissed versions self.check_update(allow_dismissed=True) self.start_update_timer() log.debug('Finished after_init') def start_update_timer(self, mins: int = 180) -> None: """Check for updates every 3 hrs""" if not cf.SYS_FROZEN: return msec = mins * 60 * 1000 self.update_timer = QTimer(parent=self) self.update_timer.timeout.connect(self.check_update) self.update_timer.start(msec) @er.errlog('Failed to check for update!', display=True) def check_update(self, allow_dismissed: bool = False, *args): """Check for update and download in a worker thread """ if not cf.SYS_FROZEN: self.update_statusbar('App not frozen, not checking for updates.') return if self.updater.update_available: # update has been previously checked and downloaded but user declined to install initially self._install_update(updater=self.updater, allow_dismissed=allow_dismissed) else: Worker(func=self.updater.check_update, mw=self) \ .add_signals(signals=( 'result', dict(func=lambda updater: self._install_update(updater, allow_dismissed=allow_dismissed)))) \ .start() def _install_update(self, updater: Updater = None, ask_user: bool = True, allow_dismissed: bool = False) -> None: """Ask if user wants to update and show changelog Parameters ---------- updater : Updater, optional Updater obj, default None ask_user : bool, optional prompt user to update or just install, default True allow_dismissed : bool, optional allow ignoring patch updates if user has dismissed once """ # update check failed, None result from thread if updater is None: return v_current = updater.version v_latest = updater.ver_latest # check if PATCH update has been dismissed if not updater.needs_update and allow_dismissed: log.info('User declined current update. current:' + f'{v_latest}, dismissed: {updater.ver_dismissed}') return # show changelog between current installed and latest version markdown_msg = updater.get_changelog_new() # prompt user to install update and restart msg = 'An updated version of the Event Log is available.\n\n' \ + f'Current: {v_current}\n' \ + f'Latest: {v_latest}\n\n' \ + 'Would you like to restart and update now?' \ + '\n\nNOTE - Patch updates (eg x.x.1) can be dismissed. Use Help > Check for Update ' \ + 'to prompt again.' if ask_user: if not dlgs.msgbox(msg=msg, yesno=True, markdown_msg=markdown_msg): # mark version as dismissed self.settings.setValue('ver_dismissed', str(v_latest)) self.update_statusbar( f'User dismissed update version: {v_latest}', log_=True) return Worker(func=updater.install_update, mw=self).start() self.update_statusbar('Extracting update and restarting...') def show_full_changelog(self) -> None: """Show full changelog""" msg = self.updater.get_changelog_full() dlgs.msgbox(msg='Changelog:', markdown_msg=msg) def init_sentry(self): """Add user-related scope information to sentry""" with configure_scope() as scope: # type: ignore scope.user = dict(username=self.username, email=self.get_setting('email')) # scope.set_extra('version', VERSION) # added to sentry release field def active_table_widget(self) -> tbls.TableWidget: """Current active TableWidget""" return self.tabs.currentWidget() @property def t(self) -> tbls.TableWidget: """Convenience property wrapper for active TableWidget""" return self.active_table_widget() def active_table(self) -> Union[tbls.TableView, None]: """Current active TableView""" table_widget = self.active_table_widget() if not table_widget is None: return table_widget.view @property def tv(self) -> Union[tbls.TableView, None]: """Convenience property wrapper for active TableView""" return self.active_table() def show_changeminesite(self): dlg = dlgs.ChangeMinesite(parent=self) return dlg.exec() @er.errlog('Close event failed.') def closeEvent(self, event): s = self.settings s.setValue('window size', self.size()) s.setValue('window position', self.pos()) s.setValue('screen', self.geometry().center()) s.setValue('minesite', self.minesite) s.setValue('active table', self.active_table_widget().title) # save current TableView column state self.tv.save_header_state() # update on closeEvent if update available... maybe not yet # if self.updater.update_available: # self._install_update(updater=self.updater, ask_user=False) def get_setting(self, key: str, default: Any = None) -> Any: """Convenience accessor to global settings""" return gbl.get_setting(key=key, default=default) def get_username(self): s = self.settings username = self.get_setting('username') email = self.get_setting('email') if username is None or email is None: self.set_username() username = self.username return username def set_username(self): # show username dialog and save first/last name to settings s = self.settings dlg = dlgs.InputUserName(self) if not dlg.exec(): return s.setValue('username', dlg.username) s.setValue('email', dlg.email) self.username = dlg.username if hasattr(self, 'u'): self.u.username = dlg.username self.u.email = dlg.email @property def driver(self) -> Union[WebDriver, None]: """Save global Chrome WebDriver to reuse etc for TSI or SAP""" return self._driver if hasattr(self, '_driver') else None @driver.setter def driver(self, driver: WebDriver): self._driver = driver def open_sap(self): from smseventlog.utils.web import SuncorWorkRemote self.sc = SuncorWorkRemote(mw=self, _driver=self.driver) Worker(func=self.sc.open_sap, mw=self) \ .add_signals(signals=('result', dict(func=self.handle_sap_result))) \ .start() self.update_statusbar('Opening SAP...') def handle_sap_result(self, sc=None): """just need to keep a referece to the driver in main thread so chrome doesnt close""" if sc is None: log.warning('SAP not opened properly') return self.driver = sc.driver self.update_statusbar('SAP started.', success=True) def get_menu(self, name: Union[str, 'QMenu']) -> 'QMenu': """Get QMenu if exists or create Returns ------- QMenu menu bar """ if isinstance(name, str): menu = self.menus.get(name, None) if menu is None: bar = self.menuBar() menu = bar.addMenu(name.title()) self.menus[name] = menu else: menu = name return menu def add_action(self, name: str, func: Callable, menu: str = None, shortcut: str = None, tooltip: str = None, label_text: str = None, parent: QWidget = None, **kw) -> QAction: """Convenience func to create QAction and add to menu bar Parameters ---------- name : str Action name Returns ------- QAction """ name_action = name.replace(' ', '_').lower() name_key = f'act_{name_action}' name = f.nice_title(name.replace( '_', ' ')) if label_text is None else label_text if parent is None: parent = self act = QAction(name, parent, triggered=func, **kw) if not shortcut is None: act.setShortcut(QKeySequence(shortcut)) act.setToolTip(tooltip) # act.setShortcutContext(Qt.ShortcutContext.WidgetShortcut) act.setShortcutVisibleInContextMenu(True) setattr(parent, name_key, act) if not menu is None: menu = self.get_menu(menu) menu.addAction(act) else: parent.addAction(act) return act def add_actions(self, actions: dict, menu: Union[str, 'QMenu'] = None) -> None: """Add dict of multiple actions to menu bar Parameters ---------- actions : dict dict of menu_name: {action: func|kw} """ menu = self.get_menu(menu) for name, kw in actions.items(): if not isinstance(kw, dict): kw = dict(func=kw) if 'submenu' in name: # create submenu, recurse submenu = menu.addMenu(name.replace('submenu_', '').title()) self.add_actions(menu=submenu, actions=kw) else: if 'sep' in kw: kw.pop('sep') menu.addSeparator() self.add_action(name=name, menu=menu, **kw) def create_actions(self) -> None: """Initialize menubar actions""" t, tv = self.active_table_widget, self.active_table menu_actions = dict( file=dict( add_new_row=dict(func=lambda: t().show_addrow(), shortcut='Ctrl+Shift+N'), refresh_menu=dict(sep=True, func=lambda: t().show_refresh(), shortcut='Ctrl+R'), refresh_all_open=dict( func=lambda: t().refresh_allopen(default=True), shortcut='Ctrl+Shift+R'), reload_last_query=dict( func=lambda: t().refresh(last_query=True), shortcut='Ctrl+Shift+L'), previous_tab=dict(sep=True, func=lambda: self.tabs.activate_previous(), shortcut='Meta+Tab'), change_minesite=dict(func=self.show_changeminesite, shortcut='Ctrl+Shift+M'), view_folder=dict(func=lambda: t().view_folder(), shortcut='Ctrl+Shift+V'), submenu_reports=dict( fleet_monthly_report=lambda: self.create_monthly_report( 'Fleet Monthly'), FC_report=lambda: self.create_monthly_report('FC'), SMR_report=lambda: self.create_monthly_report('SMR'), PLM_report=dict(sep=True, func=self.create_plm_report), import_PLM_manual=self.import_plm_manual), import_downloads=dict(sep=True, func=self.import_downloads), preferences=dict(sep=True, func=self.show_preferences, shortcut='Ctrl+,')), edit=dict( find=dict(func=lambda: tv().show_search(), shortcut='Ctrl+F')), table=dict( email_table=lambda: t().email_table(), email_table_selection=lambda: t().email_table(selection=True), export_table_excel=lambda: t().export_df('xlsx'), export_table_CSV=lambda: t().export_df('csv'), toggle_color=dict(sep=True, func=lambda: tv().data_model.toggle_color()), jump_first_last_row=dict(func=lambda: tv().jump_top_bottom(), shortcut='Ctrl+Shift+J'), reset_column_layout=dict( func=lambda: tv().reset_header_state())), rows=dict(open_tsi=dict(func=lambda: t().open_tsi(), label_text='Open TSI'), delete_row=lambda: t().remove_row(), update_component=lambda: t().show_component(), details_view=dict(func=lambda: t().show_details(), shortcut='Ctrl+Shift+D')), database=dict( update_component_SMR=update_comp_smr, update_FC_status_clipboard=lambda: fc. update_scheduled_sap(exclude=dlgs.inputbox( msg= '1. Enter FCs to exclude\n2. Copy FC Data from SAP to clipboard\n\nExclude:', title='Update Scheduled FCs SAP'), table_widget=t()), reset_database_connection=dict(sep=True, func=db.reset), reset_database_tables=db.clear_saved_tables, open_SAP=dict(sep=True, func=self.open_sap)), help=dict(about=dlgs.about, check_for_update=self.check_update, show_changelog=self.show_full_changelog, email_error_logs=self.email_err_logs, open_documentation=lambda: f.open_url(cf.config['url'][ 'docs']), submit_issue=dict( func=lambda: f.open_url(cf.config['url']['issues']), label_text='Submit issue or Feature Request'), reset_username=dict(sep=True, func=self.set_username), test_error=self.test_error)) # reset credentials prompts for c in ('TSI', 'SMS', 'exchange', 'SAP'): menu_actions['help'][ f'reset_{c}_credentials'] = lambda x, c=c: CredentialManager( c).prompt_credentials() for menu, m_act in menu_actions.items(): self.add_actions(actions=m_act, menu=menu) # other actions which don't go in menubar other_actions = dict( refresh_last_week=lambda: t().refresh_lastweek(base=True), refresh_last_month=lambda: t().refresh_lastmonth(base=True), update_SMR=dict( func=lambda: t().update_smr(), tooltip='Update selected event with SMR from database.'), show_SMR_history=lambda: t().show_smr_history()) self.add_actions(actions=other_actions) def test_error(self) -> None: """Just raise test error""" raise RuntimeError('This is a test error.') def contextMenuEvent(self, event): """Add actions to right click menu, dependent on currently active table """ child = self.childAt(event.pos()) menu = QMenu(self) # menu.setToolTipsVisible(True) table_widget = self.active_table_widget() for section in table_widget.context_actions.values(): for action in section: name_action = f'act_{action}' try: menu.addAction(getattr(self, name_action)) except Exception as e: try: menu.addAction(getattr(table_widget, name_action)) except Exception as e: log.warning( f'Couldn\'t add action to context menu: {action}') menu.addSeparator() action = menu.exec(self.mapToGlobal(event.pos())) def create_monthly_report(self, name: str): """Create report in worker thread from dialog menu Parameters ---------- name : str ['Fleet Monthly', 'FC'] """ dlg = dlgs.BaseReportDialog(window_title=f'{name} Report') if not dlg.exec(): return from smseventlog.reports import FCReport, FleetMonthlyReport from smseventlog.reports import Report as _Report from smseventlog.reports import SMRReport Report = { 'Fleet Monthly': FleetMonthlyReport, 'FC': FCReport, 'SMR': SMRReport }[name] # type: _Report rep = Report(d=dlg.d, minesite=dlg.items['MineSite']) # type: ignore Worker(func=rep.create_pdf, mw=self) \ .add_signals(signals=('result', dict(func=self.handle_monthly_report_result))) \ .start() self.update_statusbar('Creating Fleet Monthly Report...') def handle_monthly_report_result(self, rep=None): if rep is None: return rep.open_() msg = f'Report:\n\n"{rep.title}"\n\nsuccessfully created. Email now?' if dlgs.msgbox(msg=msg, yesno=True): rep.email() def import_plm_manual(self): """Allow user to manually select haulcycle files to upload""" t = self.active_table_widget() e = t.e if not e is None: from smseventlog import eventfolders as efl unit, dateadded = e.Unit, e.DateAdded uf = efl.UnitFolder(unit=unit) p = uf.p_unit else: # No unit selected, try to get minesite equip path p = cf.p_drive / cf.config['EquipPaths'].get( self.minesite.replace('-', ''), '') if p is None: p = Path.home() / 'Desktop' lst_csv = dlgs.select_multi_files(p_start=p) if not lst_csv: return # user didn't select anything from smseventlog.data.internal import utils as utl Worker(func=utl.combine_import_csvs, mw=self, lst_csv=lst_csv, ftype='plm') \ .add_signals(('result', dict(func=self.handle_import_result_manual))) \ .start() self.update_statusbar( 'Importing haul cylce files from network drive (this may take a few minutes)...' ) def create_plm_report(self): """Trigger plm report from current unit selected in table""" from smseventlog.data.internal import plm view = self.active_table() try: e = view.e unit, d_upper = e.Unit, e.DateAdded except er.NoRowSelectedError: # don't set dialog w unit and date, just default unit, d_upper, e = None, None, None # Report dialog will always set final unit etc dlg = dlgs.PLMReport(unit=unit, d_upper=d_upper) ok = dlg.exec() if not ok: return # user exited m = dlg.get_items(lower=True) # unit, d_upper, d_lower # check if unit selected matches event selected if not e is None: if not e.Unit == m['unit']: e = None m['e'] = e # NOTE could make a func 'rename_dict_keys' m['d_upper'], m['d_lower'] = m['date upper'], m['date lower'] # check max date in db maxdate = plm.max_date_plm(unit=m['unit']) if maxdate + delta(days=5) < m['d_upper']: # worker will call back and make report when finished if not fl.drive_exists(warn=False): msg = 'Can\'t connect to P Drive. Create report without updating records first?' if dlgs.msgbox(msg=msg, yesno=True): self.make_plm_report(**m) return Worker(func=plm.update_plm_single_unit, mw=self, unit=m['unit']) \ .add_signals( signals=('result', dict( func=self.handle_import_result, kw=m))) \ .start() msg = f'Max date in db: {maxdate:%Y-%m-%d}. ' \ + 'Importing haul cylce files from network drive, this may take a few minutes...' self.update_statusbar(msg=msg) else: # just make report now self.make_plm_report(**m) def handle_import_result_manual(self, rowsadded=None, **kw): if not rowsadded is None: msg = dict(msg=f'PLM records added to database: {rowsadded}', success=rowsadded > 0) else: msg = 'Warning: Failed to import PLM records.' self.update_statusbar(msg) def handle_import_result(self, m_results=None, **kw): if m_results is None: return rowsadded = m_results['rowsadded'] self.update_statusbar(f'PLM records added to database: {rowsadded}', success=True) self.make_plm_report(**kw) def make_plm_report(self, e=None, **kw): """Actually make the report pdf""" from smseventlog import eventfolders as efl from smseventlog.reports import PLMUnitReport rep = PLMUnitReport(mw=self, **kw) if not e is None: ef = efl.EventFolder.from_model(e) p = ef._p_event else: ef = None # If cant get event folder, ask to create at desktop if ef is None or not ef.check(check_pics=False, warn=False): p = Path.home() / 'Desktop' msg = 'Can\'t get event folder, create report at desktop?' if not dlgs.msgbox(msg=msg, yesno=True): return Worker(func=rep.create_pdf, mw=self, p_base=p) \ .add_signals(signals=('result', dict(func=self.handle_plm_result, kw=kw))) \ .start() self.update_statusbar(f'Creating PLM report for unit {kw["unit"]}...') def handle_plm_result(self, rep=None, unit=None, **kw): if rep is False: # not super robust, but just warn if no rows in query msg = 'No rows returned in query, can\'t create report!' dlgs.msg_simple(msg=msg, icon='warning') if not rep or not rep.p_rep.exists(): self.update_statusbar('Failed to create PLM report.', warn=True) return self.update_statusbar(f'PLM report created for unit {unit}', success=True) msg = f'Report:\n\n"{rep.title}"\n\nsuccessfully created. Open now?' if dlgs.msgbox(msg=msg, yesno=True): rep.open_() def email_err_logs(self): """Collect and email error logs to simplify for user""" docs = [] def _collect_logs(p): return [p for p in p.glob('*log*')] if p.exists() else [] # collect sms logs p_sms = cf.p_applocal / 'logging' docs.extend(_collect_logs(p_sms)) # collect pyupdater logs i = 1 if cf.is_win else 0 p_pyu = cf.p_applocal.parents[1] / 'Digital Sapphire/PyUpdater/logs' docs.extend(_collect_logs(p_pyu)) from smseventlog.utils import email as em subject = f'Error Logs - {self.username}' body = 'Thanks Jayme,<br><br>I know you\'re trying your best. \ The Event Log is amazing and we appreciate all your hard work!' msg = em.Message(subject=subject, body=body, to_recip=['*****@*****.**'], show_=False) msg.add_attachments(docs) msg.show() def import_downloads(self) -> None: """Select and import dls files to p-drive""" if not fl.drive_exists(): return from smseventlog.data.internal import dls # get dls filepath lst_dls = dlgs.select_multi_folders(p_start=cf.desktop) if lst_dls is None: msg = 'User failed to select downloads folders.' self.update_statusbar(msg=msg, warn=True) return # start uploads for each dls folder selected for p_dls in lst_dls: Worker(func=dls.import_dls, mw=self, p=p_dls) \ .add_signals(signals=('result', dict(func=self.handle_dls_result))) \ .start() self.update_statusbar(msg='Started downloads upload in worker thread.') def handle_dls_result(self, result: dict = None, **kw): if isinstance(result, dict): name, time_total = '', '' try: name = result.pop('name') time_total = f.mins_secs(result.pop('time_total')) # join remaining processed files/times msg_result = ', '.join([ f'{k}: ({m["num"]}, {f.mins_secs(m["time"])})' for k, m in result.items() ]) except: msg_result = '' log.warning('Failed to build upload string') msg = f'Successfully uploaded downloads folder "{name}", ({time_total}). \ Files processed/rows imported: {msg_result}' msg = dict(msg=msg, success=True) else: msg = dict(msg='Failed to upload downloads.', warn=True) self.update_statusbar(msg=msg) def show_preferences(self) -> None: """Show preferences dialog to allow user to change global settings""" dlg = dlgs.Preferences(parent=self) dlg.exec()