class ProgressDialog(ProgressDialogUI, ProgressDialogBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setupUi(self) self.setWindowFlags(Qt.Tool | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint) if HAS_WINEXTRAS: self._button = QWinTaskbarButton(self) self._button.setWindow(self.windowHandle()) self._button.setOverlayIcon(self.icon()) def value(self): return self.progressBar.value() def setValue(self, value): self.progressBar.setValue(value) if HAS_WINEXTRAS: self._button.progress().setValue(value) def format(self): return self.windowTitle() def setFormat(self, value): self.setWindowTitle(value) def minimum(self): return self.progressBar.minimum() def setMinimum(self, minimum): self.progressBar.setMinimum(minimum) if HAS_WINEXTRAS: self._button.progress().setMinimum(minimum) def maximum(self): return self.progressBar.maximum() def setMaximum(self, maximum): self.progressBar.setMaximum(maximum) if HAS_WINEXTRAS: self._button.progress().setMaximum(maximum) def setRange(self, minimum, maximum): self.progresBar.setRange(minimum, maximum) if HAS_WINEXTRAS: self._button.progress().setRange(minimum, maximum) def reject(self): return super().reject() def done(self, r: int): return super().done(r) def show(self): super().show() if HAS_WINEXTRAS: self._button.progress().show()
def b(): button = QWinTaskbarButton() button.setWindow(self.windowHandle()) progress = button.progress() self.taskbarButton = button self.taskbarProgress = progress while len(self.tasksDependOnWindow) > 0: task = self.tasksDependOnWindow.pop(0) task()
def b(): # self.setWindowFlag(Qt.WindowStaysOnTopHint, True) button = QWinTaskbarButton() button.setWindow(self.windowHandle()) progress = button.progress() self.taskbarButton = button self.taskbarProgress = progress while len(self.tasks) > 0: self.tasks.pop(0)()
class DownloadManager(QWidget): # DownloadOption OpenFile = 0 SaveFile = 1 ExternalManager = 2 NoOption = 3 class DownloadInfo: def __init__(self, page): ''' @param: page WebPage ''' self.page = page # WebPage self.suggestedFileName = '' self.askWhatToDo = True self.forceChoosingPath = False def __init__(self, parent=None): super().__init__(parent) self.ui = None # Ui::DownloadManager self._timer = QBasicTimer() self._lastDownloadPath = '' self._downloadPath = '' self._useNativeDialog = False self._isClosing = False self._closeOnFinish = False self._activeDownloadsCount = 0 self._useExternalManager = False self._externalExecutable = '' self._externalArguments = '' self._lastDownloadOption = self.NoOption # DownloadOption self._taskbarButton = None # QPointer<QWinTaskbarButton> self._ui = uic.loadUi('mc/downloads/DownloadManager.ui', self) self.setWindowFlags(self.windowFlags() ^ Qt.WindowMaximizeButtonHint) if const.OS_WIN: if QtWin.isCompositionEnabled(): # TODO: ? QtWin.extendFrameIntoClientArea(self, -1, -1, -1, -1) self._ui.clearButton.setIcon(QIcon.fromTheme('edit-clear')) gVar.appTools.centerWidgetOnScreen(self) self._ui.clearButton.clicked.connect(self._clearList) clearShortcut = QShortcut(QKeySequence('CTRL+L'), self) clearShortcut.activated.connect(self._clearList) self.loadSettings() gVar.appTools.setWmClass('Download Manager', self) def loadSettings(self): settings = Settings() settings.beginGroup("DownloadManager") self._downloadPath = settings.value("defaultDownloadPath", '') self._lastDownloadPath = settings.value( "lastDownloadPath", QStandardPaths.writableLocation(QStandardPaths.DownloadLocation)) self._closeOnFinish = settings.value("CloseManagerOnFinish", False) self._useNativeDialog = settings.value( "useNativeDialog", const.DEFAULT_DOWNLOAD_USE_NATIVE_DIALOG) self._useExternalManager = settings.value("UseExternalManager", False) self._externalExecutable = settings.value("ExternalManagerExecutable", '') self._externalArguments = settings.value("ExternalManagerArguments", '') settings.endGroup() if "%d" not in self._externalArguments: self._externalArguments += " %d" def download(self, downloadItem): # noqa C901 ''' @param: downloadItem QWebEngineDownloadItem ''' downloadTimer = QTime() downloadTimer.start() self.closeDownloadTab(downloadItem) downloadPath = '' openFile = False fileName = basename(downloadItem.path()) forceAsk = downloadItem.savePageFormat() != QWebEngineDownloadItem.UnknownSaveFormat \ or downloadItem.type() == QWebEngineDownloadItem.UserRequested if self._useExternalManager: self.startExternalManager(downloadItem.url()) elif forceAsk or not self._downloadPath: (Unknown, Open, Save, ExternalManager, SavePage) = range(5) result = Unknown if downloadItem.savePageFormat( ) != QWebEngineDownloadItem.UnknownSaveFormat: # Save Page Requested result = SavePage elif downloadItem.type() == QWebEngineDownloadItem.UserRequested: # Save x as... requested result = Save else: # Ask what to do optionsDialog = DownloadOptionsDialog(fileName, downloadItem, gVar.app.activeWindow()) optionsDialog.showExternalManagerOption( self._useExternalManager) optionsDialog.setLastDownloadOption(self._lastDownloadOption) result = optionsDialog.exec_() if result == Open: openFile = True downloadPath = gVar.appTools.ensureUniqueFilename( pathjoin(DataPaths.path(DataPaths.Temp), fileName)) self._lastDownloadOption = self.OpenFile elif result == Save: downloadPath, selectedFitler = QFileDialog.getSaveFileName( gVar.app.activeWindow(), _('Save file as...'), pathjoin(self._lastDownloadPath, fileName)) if downloadPath: self._lastDownloadPath = QFileInfo( downloadPath).absolutePath() Settings().setValue('DownloadManager/lastDownloadPath', self._lastDownloadPath) self._lastDownloadOption = self.SaveFile elif result == SavePage: mhtml = _('MIME HTML Archive (*.mhtml)') htmlSingle = _('HTML Page, single (*.html)') htmlComplete = _('HTML Page, complete (*.html)') filter_ = '%s;;%s;;%s' % (mhtml, htmlSingle, htmlComplete) selectedFilter = '' downloadPath, selectedFilter = QFileDialog.getSaveFileName( gVar.app.activeWindow(), _('Save page as...'), pathjoin(self._lastDownloadPath, fileName), filter_, selectedFilter) if downloadPath: self._lastDownloadPath = QFileInfo( downloadPath).absolutePath() Settings().setValue('DownloadManager/lastDownloadPath', self._lastDownloadPath) self._lastDownloadOption = self.SaveFile format_ = QWebEngineDownloadItem.UnknownSaveFormat if selectedFilter == mhtml: format_ = QWebEngineDownloadItem.MimeHtmlSaveFormat elif selectedFilter == htmlSingle: format_ = QWebEngineDownloadItem.SingleHtmlSaveFormat elif selectedFilter == htmlComplete: format_ = QWebEngineDownloadItem.CompleteHtmlSaveFormat if format_ == QWebEngineDownloadItem.UnknownSaveFormat: downloadItem.setSavePageFormat(format_) elif result == ExternalManager: self.startExternalManager(downloadItem.url()) downloadItem.cancel() else: downloadItem.cancel() else: downloadPath = gVar.appTools.ensureUniqueFilename( pathjoin(self._downloadPath, fileName)) if not downloadPath: downloadItem.cancel() return # Set download path ad accept downloadItem.setPath(downloadPath) downloadItem.accept() # Create download item listItem = QListWidgetItem(self._ui.list) downItem = DownloadItem(listItem, downloadItem, QFileInfo(downloadPath).absolutePath(), basename(downloadPath), openFile, self) downItem.setDownTimer(downloadTimer) downItem.startDownloading() downItem.deleteItem.connect(self._deleteItem) downItem.downloadFinished.connect(self._downloadFinished) self._ui.list.setItemWidget(listItem, downItem) listItem.setSizeHint(downItem.sizeHint()) downItem.show() self._activeDownloadsCount += 1 self.downloadsCountChanged.emit() def downloadsCount(self): return self._ui.list.count() def activeDownloadsCount(self): return self._activeDownloadsCount def canClose(self): ''' @return: bool ''' if self._isClosing: return True isDownloading = False for idx in range(self._ui.list.count()): downItem = self._ui.list.itemWidget(self._ui.list.item(idx)) if not downItem: continue if downItem.isDownloading(): isDownloading = True break return not isDownloading def useExternalManager(self): ''' @return: bool ''' return self._useExternalManager def startExternalManager(self, url): ''' param: url QUrl ''' arguments = self._externalArguments arguments.replace('%d', url.toEncoded().data().decode()) gVar.appTools.startExternalProcess(self._externalExecutable, arguments) self._lastDownloadOption = self.ExternalManager def setLastDownloadPath(self, lastPath): self._lastDownloadPath = lastPath def setLastDownloadOption(self, option): self._lastDownloadOption = option # public Q_SLOTS: def show(self): self._timer.start(500, self) super().show() self.raise_() self.activateWindow() # private Q_SLOTS: def _clearList(self): items = [] # QList<DownloadItem> listItems = [] for idx in range(self._ui.list.count()): listItem = self._ui.list.item(idx) downItem = self._ui.list.itemWidget(listItem) if not downItem: continue if downItem.isDownloading(): continue items.append(downItem) listItems.append(listItem) for listItem in listItems: row = self._ui.list.row(listItem) self._ui.list.takeItem(row) qtUtil.qDeleteAll(items) self.downloadsCountChanged.emit() def _deleteItem(self, item): ''' @parma: item DownloadItem ''' if item and not item.isDownloading(): row = self._ui.list.row(item.item()) self._ui.list.takeItem(row) item.deleteLater() def _downloadFinished(self, success): ''' @param: success bool ''' self._activeDownloadsCount = 0 downloadingAllFilesFinished = True for idx in range(self._ui.list.count()): downItem = self._ui.list.itemWidget(self._ui.list.item(idx)) if not isinstance(downItem, DownloadItem): continue if downItem.isDownloading(): self._activeDownloadsCount += 1 if downItem.isCancelled() or not downItem.isDownloading(): continue downloadingAllFilesFinished = False self.downloadsCountChanged.emit() if downloadingAllFilesFinished: if success and gVar.app.activeWindow() != self: icon = QIcon.fromTheme('download', QIcon(':/icons/other/download.svg')) gVar.app.desktopNotifications().showNotification( icon.pixmap(48), _('App: Download Finished'), _('All files have been successfully downloaded.')) if not self._closeOnFinish: self.raise_() self.activateWindow() self._ui.speedLabel.clear() self.setWindowTitle(_('Download Manager')) if const.OS_WIN: self.taskbarButton().progress().hide() if self._closeOnFinish: self.close() # Q_SIGNALS resized = pyqtSignal(QSize) downloadsCountChanged = pyqtSignal() # private: # override def timerEvent(self, event): ''' @param: event QTimerEvent ''' remTimes = [] # QVector<QTime> progresses = [] # QVector<int> speeds = [] # QVector<double> if event.timerId() == self._timer.timerId(): if not self._ui.list.count(): self._ui.speedLabel.clear() self.setWindowTitle(_('Download Manager')) if const.OS_WIN: self.taskbarButton().progress().hide() return for idx in range(self._ui.list.count()): downItem = self._ui.list.itemWidget(self._ui.list.item(idx)) if not isinstance(downItem, DownloadItem) or downItem.isCancelled() \ or not downItem.isDownloading(): continue progresses.append(downItem.progress()) remTimes.append(downItem.remainingTime()) speeds.append(downItem.currentSpeed()) if not remTimes: return remaining = QTime() for time in remTimes: if time > remaining: remaining = time progress = 0 for prog in progresses: progress += prog progress = int(progress / len(progresses) * 100) / 100 speed = sum(speeds) if not const.OS_WIN: self._ui.speedLabel.setText( _('%s%% of %s files (%s) %s remaining') % (progress, len(progresses), DownloadItem.currentSpeedToString(speed), DownloadItem.remainingTimeToString(remaining))) else: self.setWindowTitle(_('%s%% - Download Manager') % progress) if const.OS_WIN: self.taskbarButton().progress().show() self.taskbarButton().progress().setValue(progress) super().timerEvent(event) # override def closeEvent(self, event): ''' @param: event QCloseEvent ''' if gVar.app.windowCount( ) == 0: # No main window -> we are going to quit if not self.canClose(): # QMessageBox.StandardButton button = QMessageBox.warning( self, _('Warning'), _('Are you sure you want to quit? All uncompleted downloads will be cancelled!' ), QMessageBox.Yes | QMessageBox.No) if button != QMessageBox.Yes: event.ignore() return self._isClosing = True gVar.app.quitApplication() event.accept() # override def resizeEvent(self, event): ''' @param: event QResizeEvent ''' super().resizeEvent(event) self.resized.emit(self.size()) # override def keyPressEvent(self, event): ''' @param: event QKeyEvent ''' if event.key() == Qt.Key_Escape or (event.key() == Qt.Key_W and event.modifiers() == Qt.ControlModifier): self.close() super().keyPressEvent(event) def closeDownloadTab(self, item): # noqa C901 ''' @param: item QWebEngineDownloadItem ''' # Attemp to close empty tab that was opened only for loading the # download url def testWebView(view, url): ''' @param: view TabbedWebView @param: url QUrl ''' if not view.webTab().isRestored(): return False if view.browserWindow().tabWidget().tabBar().normalTabsCount() < 2: return False page = view.page() if page.history().count() != 0: return False if const.QTWEBENGINEWIDGETS_VERSION >= const.QT_VERSION_CHECK( 5, 12, 0): return True else: if page.url() != QUrl(): return False tabUrl = page.requestedUrl() if tabUrl.isEmpty(): tabUrl = QUrl(view.webTab().locationBar().text()) return tabUrl.host() == url.host() if const.QTWEBENGINEWIDGETS_VERSION >= const.QT_VERSION_CHECK( 5, 12, 0): from mc.webengine.WebPage import WebPage from mc.webtab.TabbedWebView import TabbedWebView page = item.page() if not page: return if not isinstance(page, WebPage): return view = page.view() if not isinstance(view, TabbedWebView): return if testWebView(view, QUrl()): view.closeView() else: mainWindow = gVar.app.getWindow() # If the main window was closed, threre is no need to go further if mainWindow == None: return if testWebView(mainWindow.weView(), item.url()): mainWindow.weView().closeView() return windows = gVar.app.windows() for window in windows: tabs = window.tabWidget().allTabs() for tab in tabs: view = tab.webView() if testWebView(view, item.url()): view.closeView() return def taskbarButton(self): ''' @return: QWinTaskbarButton ''' if const.OS_WIN: if not self._taskbarButton: window = gVar.app.getWindow() self._taskbarButton = QWinTaskbarButton( window and window.windowHandle() or self.windowHandle()) self._taskbarButton.progress().setRange(0, 100) return self._taskbarButton else: return None
class Window(QMainWindow): INTERVALS = ( (0, 'None', ''), (5, '5 secs.', '5s'), (15 * 60, '15 mins.', '15m'), (30 * 60, '30 mins.', '30m'), (60 * 60, '60 mins.', '60m') ) CONTEXTS = (None, 'work', 'play') def __init__(self): super().__init__() self.setWindowIcon(QIcon('clock.ico')) self.resize(250, 300) self.interval = None self.context = None self.timer = Timer() self.timer.textChanged.connect(self.updateTitle) self.timer.started.connect(self.updateTitle) self.timer.stopped.connect(self.updateTitle) self.timer.reset_.connect(self.updateTitle) self.progressButton = QWinTaskbarButton() self.progress = self.progressButton.progress() self.updateTitle() self.setupWidgets() def showEvent(self, event): super().showEvent(event) self.progressButton.setWindow(self.windowHandle()) def updateTitle(self, text=None): title = '' if self.context: title += self.context.title() if self.interval: title += ' ({})'.format(self.INTERVALS[self.interval][2]) elif self.interval: title = '{}'.format(self.INTERVALS[self.interval][2]) if self.context or self.interval: title += ' | ' title += text or self.timer.text() if self.interval: secs = self.INTERVALS[self.interval][0] title += ' ({}%)'.format(int(100 * self.timer.seconds() / secs)) self.setWindowTitle(title) if self.interval: seconds = self.timer.seconds() if seconds <= self.progress.maximum(): self.progress.setValue(seconds) elif not self.progress.isStopped(): self.progress.stop() def setupWidgets(self): central = QWidget() layout = QVBoxLayout() central.setLayout(layout) layout.addWidget(self.controlButtons()) layout.addWidget(self.timer, 1) layout.addWidget(self.intervalButtons()) layout.addWidget(self.contextButtons()) self.setCentralWidget(central) def controlButtons(self): widget = QWidget() group = QButtonGroup(widget) layout = QHBoxLayout() widget.setLayout(layout) startButton = QPushButton('Start') startButton.setCheckable(True) def onStart(): self.timer.start() if self.progress.isPaused(): self.progress.resume() startButton.clicked.connect(lambda: self.timer.start()) group.addButton(startButton) layout.addWidget(startButton) stopButton = QPushButton('Stop') stopButton.setCheckable(True) def onStop(): self.timer.stop() self.progress.setPaused(True) stopButton.clicked.connect(onStop) group.addButton(stopButton) layout.addWidget(stopButton) resetButton = QPushButton('Reset') def onReset(): self.timer.reset() self.progress.resume() resetButton.clicked.connect(onReset) layout.addWidget(resetButton) return widget def intervalButtons(self): widget = QGroupBox('Interval') group = QButtonGroup(widget) layout = QHBoxLayout() widget.setLayout(layout) def setInterval(): self.interval = group.checkedId() interval = self.INTERVALS[self.interval][0] self.updateTitle() if interval: self.progress.show() self.progress.setMaximum(interval) value = self.timer.seconds() if value < interval: self.progress.resume() else: self.progress.stop() self.progress.setValue(min(interval, value)) else: self.progress.hide() for i, interval in enumerate(self.INTERVALS): button = QPushButton(interval[1]) button.setCheckable(True) button.clicked.connect(setInterval) group.addButton(button, i) layout.addWidget(button, 1 if i > 0 else 0) return widget def contextButtons(self): widget = QGroupBox('Context') group = QButtonGroup(widget) layout = QHBoxLayout() widget.setLayout(layout) def setContext(): self.context = self.CONTEXTS[group.checkedId()] self.updateTitle() for i, context in enumerate(self.CONTEXTS): button = QPushButton(context.title() if context else 'None') button.setCheckable(True) button.clicked.connect(setContext) group.addButton(button, i) layout.addWidget(button, 1 if i > 0 else 0) return widget
class Updater(QDialog): def __init__(self, server, parent=None): super(Updater, self).__init__(parent) self.server = server self.reporter_thread = None self.update_thread = None self.step_unit = None self.progress = 0 if getattr(sys, 'frozen', False): self.app_path = os.path.abspath(sys.executable) else: self.app_path = os.path.abspath(__file__) self.root_path = QLineEdit() self.root_path.textChanged[str].connect(self.on_path_changed) self.browse_button = QPushButton('Browse...') self.browse_button.clicked.connect(self.on_browse_button) self.progress_bar = QProgressBar() self.progress_bar.setValue(0) self.from_combo_box = QComboBox() self.from_combo_box.addItems(self.server.available_indexes()) self.from_combo_box.setEnabled(False) self.autodetect_checkbox = QCheckBox('Autodetect') self.autodetect_checkbox.setChecked(True) self.autodetect_checkbox.toggled.connect(self.on_autodetect_checked) self.to_combo_box = QComboBox() self.to_combo_box.addItems(self.server.available_updates()) self.update_button = QPushButton('Go!') self.update_button.setEnabled(False) self.update_button.clicked.connect(self.on_update_button) self.status_label = QLabel('Idle.') self.step_label = QLabel() # Update to the latest version by default pos = self.to_combo_box.findText(self.server.get_latest()) if pos != -1: self.to_combo_box.setCurrentIndex(pos) self.win_taskbar = None if os.name == 'nt': from PyQt5.QtWinExtras import QWinTaskbarButton self.win_taskbar = QWinTaskbarButton(self) self.win_taskbar.progress().setVisible(True) bottom = QVBoxLayout() bottom.addWidget(self.status_label) bottom.addWidget(self.progress_bar) bottom.addWidget(self.step_label) layout = QGridLayout() layout.addWidget(QLabel('Root Path'), 0, 0) layout.addWidget(self.root_path, 0, 1) layout.addWidget(self.browse_button, 0, 2) layout.addWidget(QLabel('From'), 1, 0) layout.addWidget(self.from_combo_box, 1, 1) layout.addWidget(self.autodetect_checkbox, 1, 2) layout.addWidget(QLabel('To'), 2, 0) layout.addWidget(self.to_combo_box, 2, 1) layout.addWidget(self.update_button, 2, 2) layout.addLayout(bottom, 3, 0, 2, 0) self.setLayout(layout) self.setGeometry(100, 100, 350, 100) self.setWindowFlag(Qt.WindowContextHelpButtonHint, False) def set_task(self, task, unit, length): self.status_label.setText(task) self.progress_bar.setRange(0, length) self.progress_bar.setValue(0) if self.win_taskbar: self.win_taskbar.progress().setRange(0, length) self.win_taskbar.progress().setValue(0) self.step_unit = unit if not self.step_unit: self.step_label.setText('') def step(self, payload): self.progress_bar.setValue(self.progress_bar.value() + 1) if self.win_taskbar: self.win_taskbar.progress().setValue(self.progress_bar.value()) if self.step_unit: self.step_label.setText('Current %s: %s' % (self.step_unit, payload)) def set_done(self, elapsed): self.update_button.setEnabled(True) self.status_label.setText('Finished in %s' % elapsed) self.step_label.setText('') def update_failed(self, exception): QMessageBox.critical(self, 'Update failed', str(exception)) def perform_autodetect(self): path = str(self.root_path.text()) if self.autodetect_checkbox.isChecked() and self.server.get_anchor(): try: hash = index.hash( os.path.join(path, self.server.get_anchor()['file']), 'sha1') pos = self.from_combo_box.findText( self.server.autodetect_anchor(hash)) if pos != -1: self.from_combo_box.setCurrentIndex(pos) except FileNotFoundError: pass def on_autodetect_checked(self): self.from_combo_box.setEnabled( not self.autodetect_checkbox.isChecked()) self.perform_autodetect() def on_path_changed(self, path): self.update_button.setEnabled(bool(path)) def on_browse_button(self): dialog = QFileDialog(self, 'Select root path...') dialog.setFileMode(QFileDialog.DirectoryOnly) if dialog.exec_() == QDialog.Accepted: folder = dialog.selectedFiles()[0] if self.server.get_anchor(): for r, d, f in os.walk(folder, topdown=True): if r.count(os.sep) - folder.count(os.sep) == 1: del d[:] if self.server.get_anchor()['file'] in f: folder = os.path.normpath(r) break self.root_path.setText(folder) self.perform_autodetect() def on_update_button(self): root_path = index.win_path(str(self.root_path.text())) if not os.path.isdir(root_path): QMessageBox.critical( self, 'Cannot proceed', 'Please make sure that the root path exists.') return if index.win_path(self.app_path).lower().startswith(root_path.lower()): QMessageBox.critical( self, 'Cannot proceed', 'Flashpoint Updater is found under the root path.\nPlease move it to a different location to proceed.' ) return self.update_button.setEnabled(False) current = str(self.from_combo_box.currentText()) target = str(self.to_combo_box.currentText()) logger.info('Starting update from %s to %s' % (current, target)) reporter = ProgressReporter() self.reporter_thread = ReporterThread(reporter) self.reporter_thread.sig_task.connect(self.set_task) self.reporter_thread.sig_step.connect(self.step) self.reporter_thread.sig_done.connect(self.set_done) self.reporter_thread.start() self.update_thread = UpdateThread(reporter, root_path, self.server, current, target) self.update_thread.sig_exc.connect(self.update_failed) self.update_thread.start() def showEvent(self, event): self.setFixedSize(self.size()) # Make non-resizable if self.win_taskbar: self.win_taskbar.setWindow(updater.windowHandle()) event.accept() def closeEvent(self, event): if self.update_thread and self.update_thread.isRunning(): self.update_thread.reporter.stop() self.update_thread.wait() self.reporter_thread.wait() event.accept()
class ProgressBar(QProgressBar): def __init__(self, parent): super(ProgressBar, self).__init__(parent) self.main_window = parent # taskbar progress if using_windows: self.taskbar_btn = QWinTaskbarButton(parent) self.taskbar_prog = self.taskbar_btn.progress() self.taskbar_prog.setRange(0, 100) self.default_x = 0 self.default_y = 420 self.default_width = 830 self.default_height = 40 self.default_fontsize = 250 self.notification = { "done": QSound(os.path.join(abspath, "res/success.wav")), "error": QSound(os.path.join(abspath, "res/fail.wav")), ".": QSound("blank") } self.setGeometry(self.default_x, self.default_y, self.default_width, self.default_height) self.setAlignment(QtCore.Qt.AlignCenter) self.text = QLabel(self) self.text.setStyleSheet( "QLabel{font-size: %ipt; font-weight: bold; color: white; background-color: transparent;}QToolTip { background-color:white;color: black; }" % self.default_fontsize) self.setStyleSheet(""" QProgressBar { border: 2px solid white; border-radius: 5px; color:white; } QProgressBar::chunk { background-color: rgba(226, 107, 167, 255); }""") def directory_changed(self, path): print('Directory Changed:', path) def file_changed(self, path): f = open(path, "r") content = f.read() if content in self.notification: self.notification[content].play() self.hide() self.setValue(0) return val = max(self.value(), float("0" + content)) self.setValue(val) if using_windows: self.taskbar_prog.setValue(val) f.close() # if self.value() >= 100: # self.hide() # self.setValue(0) def hide(self): self.main_window.startbutton.default_y = 370 self.main_window.startbutton.show() self.main_window.cancelbutton.hide() self.main_window.options.default_y = 430 self.main_window.updatebutton.default_y = 400 self.main_window.resizeEvent(True) if using_windows: self.taskbar_prog.hide() super().hide() def show(self): mapendtime = "Max" if current_config[ 'End time'] == -1 else current_config['End time'] self.text.setToolTip( f"Map start time: {current_config['Start time']}, Map end time: {mapendtime}" ) self.main_window.startbutton.default_y = 330 self.main_window.options.default_y = 390 self.main_window.updatebutton.default_y = 360 self.main_window.resizeEvent(True) self.main_window.startbutton.hide() self.main_window.cancelbutton.show() if using_windows: self.taskbar_prog.show() super().show() def changesize(self): scale = self.main_window.height() / self.main_window.default_height width = self.default_width * scale height = self.default_height * scale x = self.default_x * scale y = self.default_y * scale self.setGeometry(x, y, width, height) self.text.setGeometry(0, 0, width, height)
class Window(QWidget): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) # 获取任务栏按钮 self.taskButton = QWinTaskbarButton(self) # 获取任务栏进度条 self.taskProgress = self.taskButton.progress() # 定时器模拟进度 self.timerProgress = QTimer(self) self.timerProgress.timeout.connect(self.update_progress) self.setup_ui() def showEvent(self, event): super(Window, self).showEvent(event) if not self.taskButton.window(): # 必须等窗口显示后设置才有效,或者通过软件流程在适当的时候设置也可以 self.taskButton.setWindow(self.windowHandle()) self.taskProgress.show() def closeEvent(self, event): self.timerProgress.stop() super(Window, self).closeEvent(event) def setup_ui(self): layout = QGridLayout(self) # 设置最新小值和最大值 self.spinBoxMin = QSpinBox(self) self.spinBoxMax = QSpinBox(self) self.spinBoxMax.setMaximum(100) self.spinBoxMax.setValue(100) layout.addWidget(self.spinBoxMin, 0, 0) layout.addWidget(self.spinBoxMax, 0, 1) layout.addWidget(QPushButton('设置范围值', self, clicked=self.set_range), 0, 2) # 设置当前值 self.spinBoxCur = QSpinBox(self) self.spinBoxCur.setMaximum(100) self.spinBoxCur.setValue(50) layout.addWidget(self.spinBoxCur, 0, 3) layout.addWidget( QPushButton('设置当前值', self, clicked=self.set_current_value), 0, 4) # 功能按钮 layout.addWidget(QPushButton('隐藏', self, clicked=self.set_show_hide), 1, 0) layout.addWidget( QPushButton('暂停', self, clicked=self.set_pause_resume), 1, 1) layout.addWidget(QPushButton('重置', self, clicked=self.set_reset), 1, 2) layout.addWidget(QPushButton('停止', self, clicked=self.set_stop), 1, 3) layout.addWidget(QPushButton('不可见', self, clicked=self.set_visible), 1, 4) # 模拟进度 layout.addWidget( QPushButton('模拟进度动画', self, clicked=self.start_progress), 2, 0, 1, 5) # 状态 layout.addWidget(QLabel('暂停信号 :', self), 3, 0) self.labelPause = QLabel(self) layout.addWidget(self.labelPause, 3, 1) self.taskProgress.pausedChanged.connect( lambda v: self.labelPause.setText(str(v))) layout.addWidget(QLabel('停止信号 :', self), 4, 0) self.labelStop = QLabel(self) layout.addWidget(self.labelStop, 4, 1) self.taskProgress.stoppedChanged.connect( lambda v: self.labelStop.setText(str(v))) layout.addWidget(QLabel('值改变信号:', self), 5, 0) self.labelValue = QLabel(self) layout.addWidget(self.labelValue, 5, 1) self.taskProgress.valueChanged.connect( lambda v: self.labelValue.setText(str(v))) layout.addWidget(QLabel('可见度信号:', self), 6, 0) self.labelVisible = QLabel(self) layout.addWidget(self.labelVisible, 6, 1) self.taskProgress.visibilityChanged.connect( lambda v: self.labelVisible.setText(str(v))) def set_range(self): # 设置进度条范围值 vmin = min(self.spinBoxMin.value(), self.spinBoxMax.value()) vmax = max(self.spinBoxMin.value(), self.spinBoxMax.value()) self.taskProgress.setRange(vmin, vmax) def set_current_value(self): # 设置进度条当前值 self.taskProgress.setValue(self.spinBoxCur.value()) def set_show_hide(self): # 显示/隐藏 visible = self.taskProgress.isVisible() # 也可以使用self.taskProgress.setVisible if visible: self.taskProgress.hide() self.sender().setText('显示') else: self.taskProgress.show() self.sender().setText('隐藏') def set_pause_resume(self): # 暂停/恢复 paused = self.taskProgress.isPaused() # 也可以使用self.taskProgress.setPaused if paused: self.taskProgress.resume() self.timerProgress.start(100) self.sender().setText('暂停') else: self.taskProgress.pause() self.timerProgress.stop() self.sender().setText('恢复') def set_reset(self): # 重置 self.taskProgress.reset() paused = self.taskProgress.isPaused() if not paused: self.timerProgress.stop() self.timerProgress.start(100) def set_stop(self): # 停止 self.timerProgress.stop() self.taskProgress.stop() self.setEnabled(False) def set_visible(self): # 可见/不可见 visible = self.taskProgress.isVisible() self.taskProgress.setVisible(not visible) self.sender().setText('可见' if visible else '不可见') def start_progress(self): # 模拟进度 self.timerProgress.start(100) self.sender().setEnabled(False) def update_progress(self): value = self.taskProgress.value() value += 1 if value > self.taskProgress.maximum(): value = 0 self.taskProgress.setValue(value)
def __init__(self, window): super().__init__() taskbar_button = QWinTaskbarButton(window) taskbar_button.setWindow(window) self._progress = taskbar_button.progress()
class TVLinker(QWidget): def __init__(self, settings: QSettings, parent=None): super(TVLinker, self).__init__(parent) self.firstrun = True self.rows, self.cols = 0, 0 self.parent = parent self.settings = settings self.taskbar = TaskbarProgress(self) self.init_styles() self.init_settings() self.init_icons() if sys.platform.startswith('linux'): notify.init(qApp.applicationName()) layout = QVBoxLayout() layout.setSpacing(0) layout.setContentsMargins(15, 15, 15, 0) form_groupbox = QGroupBox(self, objectName='mainForm') form_groupbox.setLayout(self.init_form()) self.table = TVLinkerTable(0, 4, self) self.table.doubleClicked.connect(self.show_hosters) layout.addWidget(form_groupbox) layout.addWidget(self.table) layout.addLayout(self.init_metabar()) self.setLayout(layout) qApp.setWindowIcon(self.icon_app) self.resize(FixedSettings.windowSize) self.show() self.start_scraping() self.firstrun = False class ProcError(Enum): FAILED_TO_START = 0 CRASHED = 1 TIMED_OUT = 2 READ_ERROR = 3 WRITE_ERROR = 4 UNKNOWN_ERROR = 5 class NotifyIcon(Enum): SUCCESS = ':assets/images/tvlinker.png' DEFAULT = ':assets/images/tvlinker.png' def init_threads(self, threadtype: str = 'scrape') -> None: if threadtype == 'scrape': if hasattr(self, 'scrapeThread'): if not sip.isdeleted( self.scrapeThread) and self.scrapeThread.isRunning(): self.scrapeThread.terminate() del self.scrapeWorker del self.scrapeThread self.scrapeThread = QThread(self) self.scrapeWorker = ScrapeWorker(self.source_url, self.user_agent, self.dl_pagecount) self.scrapeThread.started.connect(self.show_progress) self.scrapeThread.started.connect(self.scrapeWorker.begin) self.scrapeWorker.moveToThread(self.scrapeThread) self.scrapeWorker.addRow.connect(self.add_row) self.scrapeWorker.workFinished.connect(self.scrape_finished) self.scrapeWorker.workFinished.connect( self.scrapeWorker.deleteLater, Qt.DirectConnection) self.scrapeWorker.workFinished.connect(self.scrapeThread.quit, Qt.DirectConnection) self.scrapeThread.finished.connect(self.scrapeThread.deleteLater, Qt.DirectConnection) elif threadtype == 'unrestrict': pass @staticmethod def load_stylesheet(qssfile: str) -> None: if QFileInfo(qssfile).exists(): qss = QFile(qssfile) qss.open(QFile.ReadOnly | QFile.Text) qApp.setStyleSheet(QTextStream(qss).readAll()) def init_styles(self) -> None: if sys.platform == 'darwin': qss_stylesheet = self.get_path('%s_osx.qss' % qApp.applicationName().lower()) else: qss_stylesheet = self.get_path('%s.qss' % qApp.applicationName().lower()) TVLinker.load_stylesheet(qss_stylesheet) QFontDatabase.addApplicationFont(':assets/fonts/opensans.ttf') QFontDatabase.addApplicationFont(':assets/fonts/opensans-bold.ttf') QFontDatabase.addApplicationFont(':assets/fonts/opensans-semibold.ttf') qApp.setFont(QFont('Open Sans', 12 if sys.platform == 'darwin' else 10)) def init_icons(self) -> None: self.icon_app = QIcon( self.get_path('images/%s.png' % qApp.applicationName().lower())) self.icon_faves_off = QIcon(':assets/images/star_off.png') self.icon_faves_on = QIcon(':assets/images/star_on.png') self.icon_refresh = QIcon(':assets/images/refresh.png') self.icon_menu = QIcon(':assets/images/menu.png') self.icon_settings = QIcon(':assets/images/cog.png') self.icon_updates = QIcon(':assets/images/cloud.png') def init_settings(self) -> None: self.provider = 'Scene-RLS' self.select_provider(0) self.user_agent = self.settings.value('user_agent') self.dl_pagecount = self.settings.value('dl_pagecount', 20, int) self.dl_pagelinks = FixedSettings.linksPerPage self.realdebrid_api_token = self.settings.value('realdebrid_apitoken') self.realdebrid_api_proxy = self.settings.value('realdebrid_apiproxy') self.download_manager = self.settings.value('download_manager') self.persepolis_cmd = self.settings.value('persepolis_cmd') self.pyload_host = self.settings.value('pyload_host') self.pyload_username = self.settings.value('pyload_username') self.pyload_password = self.settings.value('pyload_password') self.idm_exe_path = self.settings.value('idm_exe_path') self.kget_cmd = self.settings.value('kget_cmd') self.favorites = self.settings.value('favorites') def init_form(self) -> QHBoxLayout: self.search_field = QLineEdit(self, clearButtonEnabled=True, placeholderText='Enter search criteria') self.search_field.setObjectName('searchInput') self.search_field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.search_field.setFocus() self.search_field.textChanged.connect(self.clear_filters) self.search_field.returnPressed.connect( lambda: self.filter_table(self.search_field.text())) self.favorites_button = QPushButton(parent=self, flat=True, cursor=Qt.PointingHandCursor, objectName='favesButton', toolTip='Favorites', checkable=True, toggled=self.filter_faves, checked=self.settings.value( 'faves_filter', False, bool)) self.refresh_button = QPushButton(parent=self, flat=True, cursor=Qt.PointingHandCursor, objectName='refreshButton', toolTip='Refresh', clicked=self.start_scraping) self.dlpages_field = QComboBox(self, toolTip='Pages', editable=False, cursor=Qt.PointingHandCursor) self.dlpages_field.addItems( ('10', '20', '30', '40', '50', '60', '70', '80')) self.dlpages_field.setCurrentIndex( self.dlpages_field.findText(str(self.dl_pagecount), Qt.MatchFixedString)) self.dlpages_field.currentIndexChanged.connect(self.update_pagecount) self.settings_button = QPushButton(parent=self, flat=True, toolTip='Menu', objectName='menuButton', cursor=Qt.PointingHandCursor) self.settings_button.setMenu(self.settings_menu()) layout = QHBoxLayout(spacing=10) # providerCombo = QComboBox(self, toolTip='Provider', editable=False, cursor=Qt.PointingHandCursor) # providerCombo.setObjectName('providercombo') # providerCombo.addItem(QIcon(':assets/images/provider-scenerls.png'), '') # providerCombo.addItem(QIcon(':assets/images/provider-tvrelease.png'), '') # providerCombo.setIconSize(QSize(146, 36)) # providerCombo.setMinimumSize(QSize(160, 40)) # providerCombo.setStyleSheet(''' # QComboBox, QComboBox::drop-down { background-color: transparent; border: none; margin: 5px; } # QComboBox::down-arrow { image: url(:assets/images/down_arrow.png); } # QComboBox QAbstractItemView { selection-background-color: #DDDDE4; } # ''') # providerCombo.currentIndexChanged.connect(self.select_provider) layout.addWidget( QLabel(pixmap=QPixmap(':assets/images/provider-scenerls.png'))) layout.addWidget(self.search_field) layout.addWidget(self.favorites_button) layout.addWidget(self.refresh_button) layout.addWidget(QLabel('Pages:')) layout.addWidget(self.dlpages_field) layout.addWidget(self.settings_button) return layout @pyqtSlot(int) def select_provider(self, index: int): if index == 0: self.provider = 'Scene-RLS' self.source_url = 'http://scene-rls.net/releases/index.php?p={0}&cat=TV%20Shows' elif index == 1: self.provider = 'TV-Release' self.source_url = 'http://tv-release.pw/?cat=TV' self.setWindowTitle('%s :: %s' % (qApp.applicationName(), self.provider)) def settings_menu(self) -> QMenu: settings_action = QAction(self.icon_settings, 'Settings', self, triggered=self.show_settings) updates_action = QAction(self.icon_updates, 'Check for updates', self, triggered=self.check_update) aboutqt_action = QAction('About Qt', self, triggered=qApp.aboutQt) about_action = QAction('About %s' % qApp.applicationName(), self, triggered=self.about_app) menu = QMenu() menu.addAction(settings_action) menu.addAction(updates_action) menu.addSeparator() menu.addAction(aboutqt_action) menu.addAction(about_action) return menu def init_metabar(self) -> QHBoxLayout: self.meta_template = 'Total number of links retrieved: <b>%i</b> / <b>%i</b>' self.progress = QProgressBar(parent=self, minimum=0, maximum=(self.dl_pagecount * self.dl_pagelinks), visible=False) self.taskbar.setProgress(0.0, True) if sys.platform == 'win32': self.win_taskbar_button = QWinTaskbarButton(self) self.meta_label = QLabel(textFormat=Qt.RichText, alignment=Qt.AlignRight, objectName='totals') self.update_metabar() layout = QHBoxLayout() layout.setContentsMargins(10, 5, 10, 10) layout.addWidget(self.progress, Qt.AlignLeft) layout.addWidget(self.meta_label, Qt.AlignRight) return layout @pyqtSlot() def check_update(self) -> None: QDesktopServices.openUrl(QUrl(FixedSettings.latest_release_url)) @pyqtSlot() def show_settings(self) -> None: settings_win = Settings(self, self.settings) settings_win.exec_() def update_metabar(self) -> bool: rowcount = self.table.rowCount() self.meta_label.setText( self.meta_template % (rowcount, self.dl_pagecount * self.dl_pagelinks)) self.progress.setValue(rowcount) self.taskbar.setProgress(rowcount / self.progress.maximum()) if sys.platform == 'win32': self.win_taskbar_button.progress().setValue(self.progress.value()) return True def start_scraping(self) -> None: self.init_threads('scrape') self.rows = 0 self.table.clearContents() self.table.setRowCount(0) self.table.setSortingEnabled(False) self.update_metabar() self.scrapeThread.start() @pyqtSlot() def about_app(self) -> None: about_html = '''<style> a { color:#441d4e; text-decoration:none; font-weight:bold; } a:hover { text-decoration:underline; } </style> <p style="font-size:24pt; font-weight:bold; color:#6A687D;">%s</p> <p> <span style="font-size:13pt;"><b>Version: %s</b></span> <span style="font-size:10pt;position:relative;left:5px;">( %s )</span> </p> <p style="font-size:13px;"> Copyright © %s <a href="mailto:[email protected]">Pete Alexandrou</a> <br/> Web: <a href="%s">%s</a> </p> <p style="font-size:11px;"> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. </p>''' % (qApp.applicationName(), qApp.applicationVersion(), platform.architecture()[0], datetime.now().year, qApp.organizationDomain(), qApp.organizationDomain()) QMessageBox.about(self, 'About %s' % qApp.applicationName(), about_html) @pyqtSlot(int) def update_pagecount(self, index: int) -> None: self.dl_pagecount = int(self.dlpages_field.itemText(index)) self.scrapeWorker.maxpages = self.dl_pagecount self.progress.setMaximum(self.dl_pagecount * self.dl_pagelinks) self.settings.setValue('dl_pagecount', self.dl_pagecount) if sys.platform == 'win32': self.win_taskbar_button.progress().setMaximum(self.dl_pagecount * self.dl_pagelinks) if self.scrapeThread.isRunning(): self.scrapeThread.requestInterruption() self.start_scraping() @pyqtSlot() def show_progress(self): self.progress.show() self.taskbar.setProgress(0.0, True) if sys.platform == 'win32': self.win_taskbar_button.setWindow(self.windowHandle()) self.win_taskbar_button.progress().setRange( 0, self.dl_pagecount * self.dl_pagelinks) self.win_taskbar_button.progress().setVisible(True) self.win_taskbar_button.progress().setValue(self.progress.value()) @pyqtSlot() def scrape_finished(self) -> None: self.progress.hide() self.taskbar.setProgress(0.0, False) if sys.platform == 'win32': self.win_taskbar_button.progress().setVisible(False) self.table.setSortingEnabled(True) self.filter_table(text='') @pyqtSlot(list) def add_row(self, row: list) -> None: if self.scrapeThread.isInterruptionRequested(): self.scrapeThread.terminate() else: self.cols = 0 self.table.setRowCount(self.rows + 1) if self.table.cursor() != Qt.PointingHandCursor: self.table.setCursor(Qt.PointingHandCursor) for item in row: table_item = QTableWidgetItem(item) table_item.setToolTip( '%s\n\nDouble-click to view hoster links.' % row[1]) table_item.setFont(QFont('Open Sans', weight=QFont.Normal)) if self.cols == 2: if sys.platform == 'win32': table_item.setFont( QFont('Open Sans Semibold', pointSize=10)) elif sys.platform == 'darwin': table_item.setFont( QFont('Open Sans Bold', weight=QFont.Bold)) else: table_item.setFont( QFont('Open Sans', weight=QFont.DemiBold, pointSize=10)) table_item.setText(' ' + table_item.text()) elif self.cols in (0, 3): table_item.setTextAlignment(Qt.AlignCenter) self.table.setItem(self.rows, self.cols, table_item) self.update_metabar() self.cols += 1 self.rows += 1 @pyqtSlot(list) def add_hosters(self, links: list) -> None: self.hosters_win.show_hosters(links) @pyqtSlot(QModelIndex) def show_hosters(self, index: QModelIndex) -> None: qApp.setOverrideCursor(Qt.BusyCursor) self.hosters_win = HosterLinks(self) self.hosters_win.downloadLink.connect(self.download_link) self.hosters_win.copyLink.connect(self.copy_download_link) self.links = HostersThread( self.table.item(self.table.currentRow(), 1).text(), self.user_agent) self.links.setHosters.connect(self.add_hosters) self.links.noLinks.connect(self.no_links) self.links.start() @pyqtSlot() def no_links(self) -> None: self.hosters_win.loading_progress.cancel() self.hosters_win.close() QMessageBox.warning( self, 'No Links Available', 'No links are available yet for the chosen TV show. ' + 'This is most likely due to the files still being uploaded. This is normal if the ' + 'link was published 30-45 mins ago.\n\nPlease check back again in 10-15 minutes.' ) @pyqtSlot(bool) def filter_faves(self, checked: bool) -> None: self.settings.setValue('faves_filter', checked) # if hasattr(self, 'scrapeWorker') and (sip.isdeleted(self.scrapeWorker) or self.scrapeWorker.complete): if not self.firstrun: self.filter_table() @pyqtSlot(str) @pyqtSlot() def filter_table(self, text: str = '') -> None: filters = [] if self.favorites_button.isChecked(): filters = self.favorites self.table.sortItems(2, Qt.AscendingOrder) else: self.table.sortItems(0, Qt.DescendingOrder) if len(text): filters.append(text) if not len(filters) or not hasattr(self, 'valid_rows'): self.valid_rows = [] for search_term in filters: for item in self.table.findItems(search_term, Qt.MatchContains): self.valid_rows.append(item.row()) for row in range(0, self.table.rowCount()): if not len(filters): self.table.showRow(row) else: if row not in self.valid_rows: self.table.hideRow(row) else: self.table.showRow(row) @pyqtSlot() def clear_filters(self): if not len(self.search_field.text()): self.filter_table('') @pyqtSlot(bool) def aria2_confirmation(self, success: bool) -> None: qApp.restoreOverrideCursor() if success: if sys.platform.startswith('linux'): self.notify( title=qApp.applicationName(), msg='Your download link has been unrestricted and now ' + 'queued in Aria2 RPC Daemon', icon=self.NotifyIcon.SUCCESS) else: QMessageBox.information( self, qApp.applicationName(), 'Download link has been queued in Aria2.', QMessageBox.Ok) else: QMessageBox.critical( self, 'Aria2 RPC Daemon', 'Could not connect to Aria2 RPC Daemon. ' + 'Check your %s settings and try again.' % qApp.applicationName(), QMessageBox.Ok) @pyqtSlot(str) def download_link(self, link: str) -> None: if len(self.realdebrid_api_token) > 0 and 'real-debrid.com' not in link \ and 'rdeb.io' not in link: qApp.setOverrideCursor(Qt.BusyCursor) self.unrestrict_link(link, True) else: if self.download_manager == 'aria2': self.aria2 = Aria2Thread(settings=self.settings, link_url=link) self.aria2.aria2Confirmation.connect(self.aria2_confirmation) self.aria2.start() self.hosters_win.close() elif self.download_manager == 'pyload': self.pyload_conn = PyloadConnection(self.pyload_host, self.pyload_username, self.pyload_password) pid = self.pyload_conn.addPackage(name='TVLinker', links=[link]) qApp.restoreOverrideCursor() self.hosters_win.close() if sys.platform.startswith('linux'): self.notify(title='Download added to %s' % self.download_manager, icon=self.NotifyIcon.SUCCESS) else: QMessageBox.information( self, self.download_manager, 'Your link has been queued in %s.' % self.download_manager, QMessageBox.Ok) # open_pyload = msgbox.addButton('Open pyLoad', QMessageBox.AcceptRole) # open_pyload.clicked.connect(self.open_pyload) elif self.download_manager in ('kget', 'persepolis'): provider = self.kget_cmd if self.download_manager == 'kget' else self.persepolis_cmd cmd = '{0} "{1}"'.format(provider, link) if self.cmdexec(cmd): qApp.restoreOverrideCursor() self.hosters_win.close() if sys.platform.startswith('linux'): self.notify(title='Download added to %s' % self.download_manager, icon=self.NotifyIcon.SUCCESS) else: QMessageBox.information( self, self.download_manager, 'Your link has been queued in %s.' % self.download_manager, QMessageBox.Ok) elif self.download_manager == 'idm': cmd = '"%s" /n /d "%s"' % (self.idm_exe_path, link) if self.cmdexec(cmd): qApp.restoreOverrideCursor() self.hosters_win.close() QMessageBox.information( self, 'Internet Download Manager', 'Your link has been queued in IDM.') else: print('IDM QProcess error = %s' % self.ProcError(self.idm.error()).name) qApp.restoreOverrideCursor() self.hosters_win.close() QMessageBox.critical( self, 'Internet Download Manager', '<p>Could not connect to your local IDM application instance. ' + 'Please check your settings and ensure the IDM executable path is correct ' + 'according to your installation.</p><p>Error Code: %s</p>' % self.ProcError(self.idm.error()).name, QMessageBox.Ok) else: dlpath, _ = QFileDialog.getSaveFileName( self, 'Save File', link.split('/')[-1]) if dlpath != '': self.directdl_win = DirectDownload(parent=self) self.directdl = DownloadThread(link_url=link, dl_path=dlpath) self.directdl.dlComplete.connect( self.directdl_win.download_complete) if sys.platform.startswith('linux'): self.directdl.dlComplete.connect( lambda: self.notify(qApp.applicationName( ), 'Download complete', self.NotifyIcon.SUCCESS)) else: self.directdl.dlComplete.connect( lambda: QMessageBox.information( self, qApp.applicationName(), 'Download complete', QMessageBox.Ok)) self.directdl.dlProgressTxt.connect( self.directdl_win.update_progress_label) self.directdl.dlProgress.connect( self.directdl_win.update_progress) self.directdl_win.cancelDownload.connect( self.cancel_download) self.directdl.start() self.hosters_win.close() def _init_notification_icons(self): for icon in self.NotifyIcon: icon_file = QPixmap(icon.value, 'PNG') icon_file.save( os.path.join(FixedSettings.config_path, os.path.basename(icon.value)), 'PNG', 100) def notify(self, title: str, msg: str = '', icon: Enum = None, urgency: int = 1) -> bool: icon_path = icon.value if icon is not None else self.NotifyIcon.DEFAULT.value icon_path = os.path.join(FixedSettings.config_path, os.path.basename(icon_path)) if not os.path.exists(icon_path): self._init_notification_icons() notification = notify.Notification(title, msg, icon_path) notification.set_urgency(urgency) return notification.show() def cmdexec(self, cmd: str) -> bool: self.proc = QProcess() self.proc.setProcessChannelMode(QProcess.MergedChannels) if hasattr(self.proc, 'errorOccurred'): self.proc.errorOccurred.connect(lambda error: print( 'Process error = %s' % self.ProcError(error).name)) if self.proc.state() == QProcess.NotRunning: self.proc.start(cmd) self.proc.waitForFinished(-1) rc = self.proc.exitStatus( ) == QProcess.NormalExit and self.proc.exitCode() == 0 self.proc.deleteLater() return rc return False @pyqtSlot() def cancel_download(self) -> None: self.directdl.cancel_download = True self.directdl.quit() self.directdl.deleteLater() def open_pyload(self) -> None: QDesktopServices.openUrl(QUrl(self.pyload_config.host)) @pyqtSlot(str) def copy_download_link(self, link: str) -> None: if len(self.realdebrid_api_token) > 0 and 'real-debrid.com' not in link \ and 'rdeb.io' not in link: qApp.setOverrideCursor(Qt.BusyCursor) self.unrestrict_link(link, False) else: clip = qApp.clipboard() clip.setText(link) self.hosters_win.close() qApp.restoreOverrideCursor() def unrestrict_link(self, link: str, download: bool = True) -> None: caller = inspect.stack()[1].function self.realdebrid = RealDebridThread( settings=self.settings, api_url=FixedSettings.realdebrid_api_url, link_url=link, action=RealDebridThread.RealDebridAction.UNRESTRICT_LINK) self.realdebrid.errorMsg.connect(self.error_handler) if download: self.realdebrid.unrestrictedLink.connect(self.download_link) else: self.realdebrid.unrestrictedLink.connect(self.copy_download_link) self.realdebrid.start() def closeEvent(self, event: QCloseEvent) -> None: if hasattr(self, 'scrapeThread'): if not sip.isdeleted( self.scrapeThread) and self.scrapeThread.isRunning(): self.scrapeThread.requestInterruption() self.scrapeThread.quit() qApp.quit() def error_handler(self, props: list) -> None: qApp.restoreOverrideCursor() QMessageBox.critical(self, props[0], props[1], QMessageBox.Ok) @staticmethod def get_path(path: str = None, override: bool = False) -> str: if override: if getattr(sys, 'frozen', False): return os.path.join(sys._MEIPASS, path) return os.path.join(QFileInfo(__file__).absolutePath(), path) return ':assets/%s' % path @staticmethod def get_version(filename: str = '__init__.py') -> str: with open(TVLinker.get_path(filename, override=True), 'r') as initfile: for line in initfile.readlines(): m = re.match('__version__ *= *[\'](.*)[\']', line) if m: return m.group(1)
class ArchivViewer(QMainWindow, ArchivviewerUi): def __init__(self, con, parent=None): super(ArchivViewer, self).__init__(parent) self._config = ConfigReader.get_instance() self._con = con self.taskbar_button = None self.taskbar_progress = None self.setupUi(self) iconpath = os.sep.join( [os.path.dirname(os.path.realpath(__file__)), 'icon128.png']) self.setWindowIcon(QIcon(iconpath)) self.setWindowTitle("Archiv Viewer") self.refreshFiles.setIcon(self.style().standardIcon( QStyle.SP_BrowserReload)) self.exportPdf.setIcon(self.style().standardIcon( QStyle.SP_DialogSaveButton)) self.savePreset.setIcon(self.style().standardIcon( QStyle.SP_DialogSaveButton)) self.savePreset.setText('') self.clearPreset.setIcon(self.style().standardIcon( QStyle.SP_TrashIcon)) self.clearPreset.setText('') self.readSettings() self.delegate = FilesTableDelegate() self.documentView.setItemDelegate(self.delegate) self.actionStayOnTop.changed.connect(self.stayOnTopChanged) self.actionShowPDFAfterExport.changed.connect( self.showPDFAfterExportChanged) self.actionUseImg2pdf.changed.connect(self.useImg2pdfChanged) self.actionUseGimpForTiff.changed.connect(self.useGimpForTiffChanged) self.actionOptimizeExport.changed.connect(self.optimizeExportChanged) self.actionFitToA4.changed.connect(self.fitToA4Changed) self.presetModel = PresetModel(self.categoryList) self.presets.setModel(self.presetModel) self.presets.editTextChanged.connect(self.presetsEditTextChanged) self.clearPreset.clicked.connect(self.clearPresetClicked) self.savePreset.clicked.connect(self.savePresetClicked) self.presets.currentIndexChanged.connect(self.presetsIndexChanged) try: self.categoryListModel = CategoryModel(self._con) except Exception as e: displayErrorMessage( "Fehler beim Laden der Kategorien: {}".format(e)) sys.exit() self.categoryList.setModel(self.categoryListModel) self.categoryListModel.dataChanged.connect( self.categoryListModelDataChanged) def displayErrorMessage(self, msg): QMessageBox.critical(self, "Fehler", str(msg)) def stayOnTopChanged(self): ontop = self.actionStayOnTop.isChecked() self._config.setValue('stayontop', ontop) if ontop: self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) else: self.setWindowFlags(self.windowFlags() & (~Qt.WindowStaysOnTopHint)) self.show() def clearPresetClicked(self): buttonReply = QMessageBox.question( self, 'Voreinstellung löschen', "Soll die aktive Voreinstellung wirklich gelöscht werden?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if (buttonReply == QMessageBox.Yes): curidx = self.presets.findText(self.presets.currentText()) self.presets.removeItem(curidx) self.presets.setCurrentIndex(0) else: return def savePresetClicked(self): curidx = self.presets.findText(self.presets.currentText()) if curidx != 0: ids = [ self.categoryList.model().idAtRow(idx.row()) for idx in self.categoryList.selectionModel().selectedRows() ] if curidx > 0: self.presetModel.setSelectedCategories(curidx, ids) else: curidx = self.presetModel.insertPreset( self.presets.currentText(), ids) self.presets.setCurrentIndex(curidx) def presetsEditTextChanged(self, text): idx = self.presets.findText(text) self.savePreset.setEnabled(idx != 0 and len(text) > 0) self.clearPreset.setEnabled(idx > 0) def presetsIndexChanged(self, idx): self.savePreset.setEnabled(idx > 0) self.clearPreset.setEnabled(idx > 0) categories = self.presetModel.categoriesAtIndex(idx) selm = self.categoryList.selectionModel() cmodel = self.categoryList.model() catidxs = [ cmodel.createIndex(row, 0, None) for row in range(cmodel.rowCount(None)) ] qis = QItemSelection() for cidx in catidxs: if cmodel.idAtRow(cidx.row()) in categories: qir = QItemSelectionRange(cidx) qis.append(qir) selm.select(qis, QItemSelectionModel.ClearAndSelect) def categoryListModelDataChanged(self, begin, end): self.presetsIndexChanged(self.presetList.currentIndex()) def showPDFAfterExportChanged(self): show = self.actionShowPDFAfterExport.isChecked() self._config.setValue('showPDFAfterExport', show) def useImg2pdfChanged(self): use = self.actionUseImg2pdf.isChecked() self._config.setValue('useImg2pdf', use) def optimizeExportChanged(self): optimize = self.actionOptimizeExport.isChecked() self._config.setValue('shrinkPDF', optimize) def useGimpForTiffChanged(self): use = self.actionUseGimpForTiff.isChecked() self._config.setValue('useGimpForTiff', use) def fitToA4Changed(self): self._config.setValue('fitToA4', self.actionFitToA4.isChecked()) def event(self, evt): ontop = self._config.getValue('stayontop') if evt.type() == QEvent.WindowActivate or evt.type( ) == QEvent.HoverEnter: self.setWindowOpacity(1.0) elif (evt.type() == QEvent.WindowDeactivate or evt.type() == QEvent.HoverLeave) and not self.isActiveWindow() and ontop: self.setWindowOpacity(0.6) return QMainWindow.event(self, evt) def showEvent(self, evt): self.taskbar_button = QWinTaskbarButton() self.taskbar_progress = self.taskbar_button.progress() self.taskbar_button.setWindow(self.windowHandle()) def closeEvent(self, evt): settings = QSettings(QSettings.UserScope, "cortex", "ArchivViewer") settings.setValue("geometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) settings.setValue("splitter", self.splitter.saveState()) settings.sync() QMainWindow.closeEvent(self, evt) def readSettings(self): settings = QSettings(QSettings.UserScope, "cortex", "ArchivViewer") try: self.restoreGeometry(settings.value("geometry")) self.restoreState(settings.value("windowState")) self.splitter.restoreState(settings.value("splitter")) except Exception as e: pass
class Assist(QWidget): # logsig = pyqtSignal(str) WAITING_MESSAGE = 'AWAITING IMAGE - TAKE SCREENSHOT USING ALT+PRINTSCREEN' VERSION_FILENAME = 'version-number.txt' def __init__(self, config): super().__init__() self.config = config self.initUI() def initUI(self): self.mainIcon = QIcon('AssistIcon.ico') self.setGeometry(100, 100, 1000, 500) self.setWindowTitle('Assist') self.setWindowIcon(self.mainIcon) self.processingLogo = RCLogoView() version_number = '' with open(self.VERSION_FILENAME) as f: version_number = f.readline() output_string_names = [ x['name'] for x in self.config['main']['output_strings'] ] self.formatComboBox = QComboBox() self.formatComboBox.addItems(output_string_names) default_output_string = self.config['main']['default_output_string'] if default_output_string in output_string_names: self.formatComboBox.setCurrentIndex( output_string_names.index(default_output_string)) self.formatComboBox.currentIndexChanged.connect( self.handleOutputStringChanged) self.formatType = QLabel() self.formatText = QTextEdit() self.formatText.setReadOnly(True) self.formatText.document().setDefaultStyleSheet( 'span.sv { color: #CC44FF; }') self.handleOutputStringChanged() self.log_lock = QMutex() self.log = QTextEdit() self.log.setReadOnly(True) self.log.document().setDefaultStyleSheet( '* { font-family: Consolas; } span.logmessage { color: #FFFFFF; white-space: pre; }' ) self.layout = QVBoxLayout(self) # self.layout.addWidget(self.processingLogo) self.statusWidget = QWidget(self) self.statusWidget.setObjectName('statusWidget') # self.statusWidget.setStyleSheet('font-family: "Arame Mono"; ') self.statusWidget.setStyleSheet( '* { font-family: "Arame Mono"; font-size: 14px; } #statusWidget { border: none; }' ) self.statusLayout = QHBoxLayout(self.statusWidget) self.statusLayout.setContentsMargins(3, 3, 3, 3) self.statusLayout.addStretch() self.statusInitialLabel = QLabel('STATUS:') self.statusInitialLabel.setStyleSheet('color: #AAAAAA;') self.statusLayout.addWidget(self.statusInitialLabel) self.statusMessageLabel = QLabel(self.WAITING_MESSAGE) self.statusLayout.addWidget(self.statusMessageLabel) self.statusLayout.addStretch() self.layout.addWidget(self.statusWidget) self.repeatButton = QPushButton('&Re-copy last text', self) # self.repeatButton.setFlat(True) self.repeatButton.setStyleSheet('min-height: 30px; max-width: 100px;') self.repeatButton.clicked.connect(self.handleRepeatButtonClicked) self.taskbarButton = None self.taskbarProgress = None """ self.taskbarButton.setWindow(self.windowHandle()) self.taskbarProgress = self.taskbarButton.progress() self.taskbarProgress.setVisible(True) # self.taskbarProgress.setRange(0, 100) """ self.versionLabel = QLabel(version_number) self.versionLabel.setStyleSheet('font-size: 10px;') self.versionLabel.setAlignment(Qt.AlignRight) self.layout.addWidget(QLabel('Output format:')) self.layout.addWidget(self.formatComboBox) self.layout.addWidget(self.formatType) self.layout.addWidget(self.formatText) self.layout.addWidget(QLabel('Debug log:')) self.layout.addWidget(self.log) self.layout.addWidget(self.repeatButton) self.layout.addWidget(self.versionLabel) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setIcon(self.mainIcon) self.trayIcon.show() QApplication.clipboard().dataChanged.connect( self.handleClipboardChanged) self.image_queue = queue.Queue() self.image_queue_lock = QMutex() self.image_processing_thread = ProcessClipboardImageThread( self, self.config) self.image_processing_thread.message.connect( self.handleProcessThreadMessage) self.image_processing_thread.log.connect(self.handleLogMessage) self.image_processing_thread.clipboard.connect( self.handleClipboardMessage) self.image_processing_thread.processing.connect( self.handleProcessingStateChange) self.image_processing_thread.lines_complete.connect( self.handleLinesComplete) self.image_processing_thread.start() self.header_line_window = None self.last_clipboard_content = '' # self.setStyleSheet("background-color: rgba(53,53,53,255);") self.show() self.rtf_clipboard_code = RegisterClipboardFormat(win32con.CF_RTF) print("RTF clipboard type identified: {}".format( self.rtf_clipboard_code)) closeShortcut = QShortcut(QKeySequence(Qt.Key_Escape), self) closeShortcut.activated.connect(self.shortcutClose) def shortcutClose(self): self.close() def handleLinesComplete(self): def ndarray_to_qlabel(ndarray): data = ndarray.copy() qimage = QImage(data, data.shape[1], data.shape[0], data.strides[0], QImage.Format_Grayscale8) qpixmap = QPixmap(qimage) qlabel = QLabel() qlabel.setPixmap(qpixmap) qlabel.resize(qpixmap.width(), qpixmap.height()) return qlabel self.logMessage("Displaying lines...") auslab_image = self.image_processing_thread.auslab_image self.header_line_window = QWidget() self.header_line_window.setWindowTitle("Header Lines") layout = QVBoxLayout(self.header_line_window) # self.header_line_window.setGeometry(500, 500, 500, 500) for auslab_image_line in auslab_image.header_line_images: layout.addWidget(ndarray_to_qlabel(auslab_image_line.line_image)) layout.addSpacing(5) for auslab_image_line in auslab_image.center_line_images: layout.addWidget(ndarray_to_qlabel(auslab_image_line.line_image)) layout.addSpacing(5) # print(self.header_line_window.layout.sizeHint()) self.header_line_window.adjustSize() self.header_line_window.show() def getCurrentOutputStringEntry(self): output_string_config_entry = [ x for x in self.config['main']['output_strings'] if x['name'] == self.formatComboBox.currentText() ] if len(output_string_config_entry) > 0: return output_string_config_entry[0] else: return None def getCurrentOutputString(self): output_string = [ x['string'] for x in self.config["main"]["output_strings"] if x["name"] == self.formatComboBox.currentText() ] if len(output_string) > 0: output_string = output_string[0] return output_string else: return None def handleOutputStringChanged(self): def highlightOutputString(output_string): return re.sub(r'([^\{]|^)\{([^\{].*?)(\}|$)', r'\1<span class="sv">{\2}</span>', output_string) entry = self.getCurrentOutputStringEntry() print(entry) output_string = '' if entry is None: output_string = '' else: output_string = entry['string'] self.formatType.setText(entry['type']) output_string = output_string.replace('\\n', '\n') output_string = output_string.replace('\n', '<br />') output_html = highlightOutputString(output_string) # print(output_html) self.formatText.setHtml(output_html) def handleProcessingStateChange(self, message_str, step, total_steps): COMPLETED_CHAR = '*' # COMPLETED_CHAR = '█' UNCOMPLETED_CHAR = '.' if message_str == 'start': # self.processingLogo.animationStart() self.statusMessageLabel.setText( 'AUSLAB IMAGE - PROCESSING 000% [{}]'.format(UNCOMPLETED_CHAR * 10)) elif message_str == 'stop': # self.processingLogo.animationStop() self.statusMessageLabel.setText(self.WAITING_MESSAGE) self.taskbarProgress.setValue(0) elif message_str == 'update': percentage = int((step / total_steps) * 100) completed_blocks = percentage // 10 uncompleted_blocks = 10 - completed_blocks completed_text = COMPLETED_CHAR * completed_blocks uncompleted_text = UNCOMPLETED_CHAR * uncompleted_blocks self.statusMessageLabel.setText( 'AUSLAB IMAGE - PROCESSING {:03d}% [{}{}]'.format( percentage, completed_text, uncompleted_text)) self.taskbarProgress.setValue(int((step / total_steps) * 100)) def handleProcessThreadMessage(self, message_str): # self.logMessage('Message signal') self.trayIcon.showMessage('Assist', message_str, 3000) def handleLogMessage(self, message): self.logMessage(message) @pyqtSlot() def handleRepeatButtonClicked(self): self.handleClipboardMessage(self.last_clipboard_content) # print('Repeat button pressed.') @pyqtSlot() def handleClipboardChanged(self): self.logMessage('Clipboard changed') if self.config['main']['log_clipboard_events']: clipboardLogFile = QFile('clipboard-log-{}-{}.txt'.format( datetime.datetime.now().strftime('%Y-%m-%d'), datetime.datetime.now().strftime('%H%M%S%f'))) clipboardLogFile.open(QFile.WriteOnly | QFile.Text) outputStream = QTextStream(clipboardLogFile) mimeData = QApplication.clipboard().mimeData() outputStream << "Begin logging clipboard event. Formats:" << "\n" for mimeFormat in QApplication.clipboard().mimeData().formats(): outputStream << mimeFormat << "\n" if mimeFormat == 'text/html': outputStream << mimeData.html() << "\n" elif mimeFormat == 'text/plain': outputStream << mimeData.text() << "\n" elif mimeFormat == 'application/x-qt-windows-mime;value="Rich Text Format"': outputStream << mimeData.data(mimeFormat).data() << "\n" else: outputStream << "Unsupported data type." outputStream << "\n" # outputStream << QApplication.clipboard().text() clipboardLogFile.close() # self.clipboard.emit('Test') qimage = QApplication.clipboard().image() if qimage.isNull(): return self.image_queue_lock.lock() self.image_queue.put(qimage) self.image_queue_lock.unlock() self.logMessage('Image waiting in queue...') def logMessage(self, message): self.log_lock.lock() dt = datetime.datetime.now() datestr = dt.strftime('%d-%m-%Y %H:%M:%S') # print('At End: {}'.format(self.log.textCursor().atEnd())) textCursor = self.log.textCursor() textCursor.movePosition(QTextCursor.End) self.log.setTextCursor(textCursor) # print('Move successful: {}'.format(self.log.textCursor().movePosition(QTextCursor.End))) self.log.insertHtml( '<span style=""><span style="color: #888888;">[{0}]</span> <span class="logmessage">{1}</span></span><br />' .format(datestr, html.escape(message))) self.log.verticalScrollBar().setValue( self.log.verticalScrollBar().maximum()) self.log_lock.unlock() def handleClipboardMessage(self, content): self.last_clipboard_content = content cp = QApplication.clipboard() cp.setText(content, cp.Clipboard) if content.startswith(r'{\rtf'): OpenClipboard() EmptyClipboard() if self.config['main']['paste_text_with_rtf']: SetClipboardData(CF_TEXT, content.encode('mbcs')) SetClipboardData(self.rtf_clipboard_code, content.encode('ascii')) CloseClipboard() def showEvent(self, event): self.taskbarButton = QWinTaskbarButton(self) self.taskbarButton.setWindow(self.windowHandle()) self.taskbarProgress = self.taskbarButton.progress() self.taskbarProgress.setVisible(True) def closeEvent(self, event): # print('Close Event') if self.header_line_window is not None: self.header_line_window.close() self.trayIcon.hide() def bringFocus(self): current_flags = self.windowFlags() self.setWindowFlags(current_flags | Qt.WindowStaysOnTopHint) # self.activateWindow() # self.windowHandle().requestActivate() self.show() self.setWindowFlags(current_flags) self.show()
class BVMQMainWindow(QMainWindow, main_window_design.Ui_MainWindow): def __init__(self, app): super().__init__() self.app = app self.platform = get_platform() self.setWindowFlags(Qt.FramelessWindowHint) self.setupUi(self) # Read icons self.icon_app = QIcon(":/icons/app.svg") self.icon_star = QIcon(":/icons/star.png") self.icon_trash = QIcon(":/icons/delete.png") self.icon_quit = QIcon(":/icons/quit.png") self.icon_fake = QIcon(":/icons/fake.svg") # Read settings self.settings = QSettings('b3d_version_manager', 'settings') is_register_blend = self.settings.value('is_register_blend', type=bool) self.is_run_minimized = self.settings.value('is_run_minimized', type=bool) is_run_on_startup = self.settings.value('is_run_on_startup', type=bool) root_folder = self.settings.value('root_folder') if (not root_folder) or (not os.path.isdir(root_folder)): exe_path = os.path.dirname(sys.executable) self.settings.setValue('root_folder', exe_path) # Custom title bar self.btnWiki.clicked.connect(lambda: webbrowser.open( "https://github.com/DotBow/Blender-Version-Manager/wiki")) self.title.setText("%s %s" % (QApplication.applicationName(), QApplication.applicationVersion())) self.btnClose.clicked.connect(self.hide) self.btnMinimize.clicked.connect(self.showMinimized) # Custom menu bar self.actionToggleRegisterBlend.setChecked(is_register_blend) self.actionToggleRegisterBlend.triggered.connect( self.toggle_register_blend) self.actionToggleRunMinimized.setChecked(self.is_run_minimized) self.actionToggleRunMinimized.triggered.connect( self.toggle_run_minimized) self.actionToggleRunOnStartup.setChecked(is_run_on_startup) self.actionToggleRunOnStartup.triggered.connect( self.toggle_run_on_startup) self.menubar.hide() self.btnSettings.setMenu(self.menuFile) self.menuFile.installEventFilter(self) # Root folder layout self.labelRootFolder.setText(self.settings.value('root_folder')) self.btnSetRootFolder.clicked.connect(self.set_root_folder) self.btnOpenRootFolder.clicked.connect(self.open_root_folder) # Tray layout self.blender_action = QAction(self.icon_star, "Blender ", self) show_action = QAction("Show", self) hide_action = QAction("Hide", self) quit_action = QAction(self.icon_quit, "Quit", self) self.blender_action.triggered.connect(self.open_latest_b3d) show_action.triggered.connect(self.bring_to_front) hide_action.triggered.connect(self.hide) quit_action.triggered.connect(self.quit) self.tray_menu = QMenu() self.tray_menu.setStyleSheet(self.menuFile.styleSheet()) self.tray_menu.addAction(self.blender_action) self.tray_menu.addAction(show_action) self.tray_menu.addAction(hide_action) self.tray_menu.addAction(quit_action) self.tray_icon = QSystemTrayIcon(self.icon_app, self) self.tray_icon.setToolTip("Blender Version Manager") self.tray_icon.setContextMenu(self.tray_menu) self.tray_icon.messageClicked.connect(self.bring_to_front) self.tray_icon.activated.connect(self.onTrayIconActivated) self.tray_icon.show() # Version layout self.btnUpdate.clicked.connect(self.update) self.set_task_visible(False) self.zeroBuildsWarning.hide() self.layouts = [] self.collect_versions() self.draw_list_versions() # Custom drag behaviour self.old_pos = self.pos() self.pressed = False # Update task self.is_update_running = False self.start_uptodate_thread() self.taskbar_progress = None self.left_click_timer = QTimer(self) self.left_click_timer.setSingleShot(True) self.left_click_timer.timeout.connect(self.bring_to_front) def start_uptodate_thread(self): self.uptodate_thread = CheckForUpdates(self) self.uptodate_thread.new_version_obtained.connect( self.show_new_version) self.uptodate_thread.start() def stop_uptodate_thread(self): self.uptodate_thread.is_running = False self.uptodate_thread.terminate() self.uptodate_thread.wait() def onTrayIconActivated(self, reason): if reason == QSystemTrayIcon.Trigger: self.left_click_timer.start(QApplication.doubleClickInterval()) elif reason == QSystemTrayIcon.DoubleClick: self.left_click_timer.stop() self.open_latest_b3d() def showEvent(self, event): # Setup taskbar if self.platform == 'Windows': if not self.taskbar_progress: self.task_button = QWinTaskbarButton(self) self.task_button.setWindow(self.windowHandle()) self.taskbar_progress = self.task_button.progress() self.taskbar_progress.setVisible(True) self.taskbar_progress.setValue(0) def open_latest_b3d(self): if self.layouts: self.layouts[0].open() def show_new_version(self, display_name): self.set_task_visible(True) self.set_progress_bar(0, 0, display_name) if self.isHidden(): self.tray_icon.showMessage("Blender Version Manager", "New build of Blender is available!", QSystemTrayIcon.Information, 4000) def set_task_visible(self, is_visible): if is_visible: self.progressBar.show() self.btnUpdate.show() self.btnCancel.hide() else: self.progressBar.hide() self.btnUpdate.hide() self.btnCancel.hide() def is_running_task(self): if self.is_update_running: QMessageBox.information(self, "Warning", "Update task in progress!", QMessageBox.Ok) return True else: return False def toggle_run_minimized(self, is_checked): self.settings.setValue('is_run_minimized', is_checked) def toggle_register_blend(self, is_checked): self.settings.setValue('is_register_blend', is_checked) def cleanup_layout(self, layout): while layout.count(): item = layout.takeAt(0) widget = item.widget() if widget is not None: widget.deleteLater() else: self.cleanup_layout(item.layout()) def collect_versions(self): self.layouts.clear() root_folder = self.settings.value('root_folder') dirs = next(os.walk(root_folder))[1] versions = [] if self.platform == 'Windows': blender_exe = "blender.exe" elif self.platform == 'Linux': blender_exe = "blender" for dir in dirs: if os.path.isfile(os.path.join(root_folder, dir, blender_exe)): versions.append(dir) for ver in versions: b3d_item_layout = B3dItemLayout(root_folder, ver, False, self) self.layouts.append(b3d_item_layout) def draw_list_versions(self): if len(self.layouts) > 0: self.zeroBuildsWarning.hide() self.layouts.sort(key=lambda ver: ver.mtime, reverse=True) self.blender_action.setVisible(True) for b3d_item_layout in self.layouts: self.layoutListVersions.removeItem(b3d_item_layout) b3d_item_layout.set_is_latest(False) self.layouts[0].set_is_latest(True) for b3d_item_layout in self.layouts: self.layoutListVersions.addLayout(b3d_item_layout) else: self.zeroBuildsWarning.show() self.blender_action.setVisible(False) def set_root_folder(self): root_folder = self.settings.value('root_folder') dir = QFileDialog.getExistingDirectory(self, "Choose Root Folder", root_folder) if dir and (dir != root_folder): self.stop_uptodate_thread() self.zeroBuildsWarning.hide() self.set_task_visible(False) self.settings.setValue('root_folder', dir) self.labelRootFolder.setText(dir) self.cleanup_layout(self.layoutListVersions) self.collect_versions() self.draw_list_versions() self.start_uptodate_thread() def open_root_folder(self): root_folder = self.settings.value('root_folder') if self.platform == 'Windows': os.startfile(root_folder) elif self.platform == 'Linux': subprocess.call(["xdg-open", root_folder]) def update(self): self.is_update_running = True self.stop_uptodate_thread() self.btnUpdate.hide() self.btnCancel.show() self.btnSetRootFolder.hide() self.set_progress_bar(0, 0, "Downloading: %p%") self.build_loader = BuildLoader(self, self.uptodate_thread.download_url, self.uptodate_thread.strptime) self.build_loader.finished.connect(self.finished) self.build_loader.progress_changed.connect(self.set_progress_bar) self.build_loader.block_abortion.connect(lambda: self.btnCancel.hide()) self.btnCancel.clicked.connect(self.build_loader.stop) self.build_loader.start() def finished(self, version): self.build_loader.terminate() self.build_loader.wait() self.btnSetRootFolder.show() self.set_task_visible(False) self.is_update_running = False if version: root_folder = self.settings.value('root_folder') b3d_item_layout = B3dItemLayout(root_folder, version, True, self) self.layouts.append(b3d_item_layout) self.tray_icon.showMessage("Blender Version Manager", "Download finished!", QSystemTrayIcon.Information, 4000) self.draw_list_versions() self.set_progress_bar(0, 0, "") self.start_uptodate_thread() def set_progress_bar(self, progress_bar_val, taskbar_val, format): self.progressBar.setFormat(format) self.progressBar.setValue(progress_bar_val * 100) if self.taskbar_progress: self.taskbar_progress.setValue(taskbar_val * 100) def quit(self): if not self.is_running_task(): self.tray_icon.hide() self.app.quit() def closeEvent(self, event): self.hide() event.ignore() def mousePressEvent(self, event): self.old_pos = event.globalPos() self.pressing = True def mouseMoveEvent(self, event): if self.pressing: delta = QPoint(event.globalPos() - self.old_pos) self.move(self.x() + delta.x(), self.y() + delta.y()) self.old_pos = event.globalPos() def mouseReleaseEvent(self, QMouseEvent): self.pressing = False def toggle_run_on_startup(self, is_checked): if (self.platform == 'Windows'): key = winreg.OpenKey( winreg.HKEY_CURRENT_USER, r'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run', 0, winreg.KEY_SET_VALUE) if (is_checked): path = sys.executable winreg.SetValueEx(key, 'Blender Version Manager', 0, winreg.REG_SZ, path) else: try: winreg.DeleteValue(key, 'Blender Version Manager') except: pass key.Close() self.settings.setValue('is_run_on_startup', is_checked) def bring_to_front(self): self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.show() self.activateWindow() # Prevent QMenu from closing def eventFilter(self, obj, event): if event.type() in [QEvent.MouseButtonRelease]: if isinstance(obj, QMenu): if obj.activeAction(): if not obj.activeAction().menu(): obj.activeAction().trigger() return True return super(BVMQMainWindow, self).eventFilter(obj, event)
class MainWindow(QtWidgets.QMainWindow): """ Window of the RenderKnecht Preset Editor """ # Report user inactivity idle_timer = QtCore.QTimer() idle_timer.setSingleShot(True) idle_timer.setTimerType(QtCore.Qt.VeryCoarseTimer) idle_timer.setInterval(10000) def __init__(self, app_class): super(MainWindow, self).__init__() # Avoid UIC Debug messages log_level = LOGGER.getEffectiveLevel() logging.root.setLevel(40) # Load Ui file loadUi(UI_FILE_PRESET_EDITOR, self) # Add LED Overlay to tabWidget # Spams the log with Debug messages, so initialize in this block self.led_ovr = LedCornerWidget(parent=self.tabWidget) logging.root.setLevel(log_level) # Windows taskbar progress indicator self.taskbar_btn = QWinTaskbarButton(self) self.progress = QWinTaskbarProgress() # Clipboard self.clipboard = [] self.clipboard_src = None # Undo Redo Menu self.actionUndo.setText('Rückgängig\tCtrl+Z') self.actionRedo.setText('Wiederherstellen\tCtrl+Y') self.actionUndo.setEnabled(False) self.actionRedo.setEnabled(False) # Add version to Window title self.ver = InfoMessage.ver self.title = self.windowTitle() self.set_window_title() self.load_settings() # Unsaved changes status self.unsaved_changes_present = False self.unsaved_changes_auto_save = True # App class so we can override the window exit method [x] with the exit method of the app class self.app_class = app_class self.tree_widget_list = [ self.treeWidget_SrcPreset, self.treeWidget_DestPreset, self.treeWidget_Variants, self.treeWidget_render ] # Set tree column width to content resize and sort ascending by order tree_setup_header_format(self.tree_widget_list) # Icons self.icon = dict() for icon_name, icon_path in Itemstyle.ICON_PATH.items(): self.icon[icon_name] = QIcon(icon_path) # Tree iterator instance self.iter_tree = iterate_item_childs(self.treeWidget_DestPreset) # Make reference modules instance available to the working classes self.find_reference_items = FindReferenceItems(self, self.iter_tree) self.add_selected_top_level_items = CopySelectedToDest(self) # Worker class instances self.sort_tree_widget = SortTree(self, self.treeWidget_SrcPreset) self.context_menus = [] self.widget_pairs = [ (self.treeWidget_SrcPreset, self.pushButton_Src_sort, self.lineEdit_Src_filter, [1, 2]), (self.treeWidget_DestPreset, self.pushButton_Dest_sort, self.lineEdit_Dest_filter, [1, 2]), (self.treeWidget_Variants, self.pushButton_Var_sort, self.lineEdit_Var_filter, 1), (self.treeWidget_render, self.pushButton_Ren_sort, self.lineEdit_Ren_filter, 1) ] # Init widget pairs for w_tuple in self.widget_pairs: tree_widget, sort_button, line_edit, filter_col = w_tuple # Sorting and reference highlight tree_widget.sortBtn = sort_button # Store Id's to missing items as strings # so we avoid to assign them to new items tree_widget.missing_ids = set() # Store topLevelItem names tree_widget.unique_names = set() # Default Filter column tree_widget.filter_column = filter_col # Store LineEdit text filter widget as tree attribute tree_widget.filter_txt_widget = line_edit # Init Filter tree_widget.filter = filter_on_timer(line_edit, tree_widget, filter_column=filter_col) line_edit.textChanged.connect(tree_widget.filter.start_timer) # Default preset visibility tree_widget.toggle_preset_vis = False # Info Overlay tree_widget.info_overlay = InfoOverlay(tree_widget) # Animated overlay tree_widget.overlay = Overlay(tree_widget) # Update overlay position on event tree_widget.overlayPos.connect(tree_widget.overlay.update_position) # Context menu tree_widget.context = TreeContextMenu(tree_widget, self) # Bind keys tree_widget.keys = TreeKeyEvents(tree_widget, self, self.app_class) tree_widget.keys.add_event_filter() # Report conflict shortcut tree_widget.report_conflict = self.report_conflict # Forbid editing in SrcWidget self.treeWidget_SrcPreset.keys.no_edit = True # Search Replace dialog self.search_replace = SearchReplace(self, [ self.treeWidget_SrcPreset, self.treeWidget_DestPreset, self.treeWidget_Variants ], current_tree=1) # Copy variants key events variant_keys_tree = CopyVariantsKeyEvents(self.treeWidget_Variants, self) variants_keys_txt = CopyVariantsKeyEvents( self.plainTextEdit_addVariant_Setname, self) variant_keys_add = CopyVariantsKeyEvents( self.plainTextEdit_addVariant_Variant, self) # Internal drag_drop worker self.treeWidget_DestPreset.internal_drag_drop.setup_ui(self) self.treeWidget_Variants.internal_drag_drop.setup_ui(self) self.treeWidget_render.internal_drag_drop.setup_ui(self, True) # hide variants type column self.treeWidget_Variants.header().hideSection(ItemColumn.TYPE) # Reset treeWidgets splitter to equal sizes self.presetTreeSplitter.setSizes([100, 100]) # Detect inactivity for automatic session save self.idle_timer.timeout.connect(self.set_inactive) self.__idle = False self.app_class.installEventFilter(self) @property def idle(self): return self.__idle @idle.setter def idle(self, val: bool = False): self.__idle = val def set_active(self): self.idle = False self.idle_timer.stop() def set_inactive(self): self.idle = True def eventFilter(self, obj, eve): if eve is None or obj is None: return False if eve.type() == QtCore.QEvent.KeyPress or \ eve.type() == QtCore.QEvent.MouseMove or \ eve.type() == QtCore.QEvent.MouseButtonPress: self.set_active() return False if not self.idle_timer.isActive(): self.idle_timer.start() return False def enable_load_actions(self, enabled: bool = True): self.menuImport.setEnabled(enabled) self.actionOpen.setEnabled(enabled) def load_settings(self): # Load Viewer Size for idx in range(0, self.comboBox_ViewerSize.count()): self.comboBox_ViewerSize.setCurrentIndex(idx) if knechtSettings.dg[ 'viewer_size'] == self.comboBox_ViewerSize.currentText(): # Set current viewer size SendToDeltaGen.viewer_size = self.comboBox_ViewerSize.currentText( ) LOGGER.debug('Setting viewer combo box to index: %s', idx) break @staticmethod def add_file_string(message, file_path): if file_path: message = message + '<br><i>' + str(file_path) + '</i>' return message def generic_error_msg(self, msg: str = ''): __msg = Msg.GENERIC_ERROR + msg self.warning_box(Msg.GENERIC_ERROR_TITLE, __msg) def warning_box(self, title_txt: str = Msg.ERROR_BOX_TITLE, message: str = '', file_path=False, parent=None): message = self.add_file_string(message, file_path) if not parent: parent = self QtWidgets.QMessageBox.warning(parent, title_txt, message) def question_box(self, title_txt: str = Msg.ERROR_BOX_TITLE, message: str = '', file_path=False, parent=None): """ Pop-Up Warning Box ask to continue or break, returns True on abort """ message = self.add_file_string(message, file_path) if not parent: parent = self msg_box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Question, title_txt, message, parent=parent) msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Abort) msg_box.button(QtWidgets.QMessageBox.Ok).setText(Msg.QUESTION_OK) msg_box.button(QtWidgets.QMessageBox.Abort).setText(Msg.QUESTION_ABORT) answer = msg_box.exec() if answer == QtWidgets.QMessageBox.Abort: # User selected abort return True # User selected -Ok-, continue return False def info_box(self, title_txt: str = Msg.INFO_TITLE, message: str = '', file_path=False, parent=None): message = self.add_file_string(message, file_path) if not parent: parent = self QtWidgets.QMessageBox.information(parent, title_txt, message) # noinspection PyArgumentList def unsaved_changes(self, file_path=False, title_txt: str = Msg.UNSAVED_CHANGES_TITLE, message: str = Msg.UNSAVED_CHANGES): """ Creates Question Box if unchanged changes in Dest widget detected """ if not self.unsaved_changes_present: return QtWidgets.QMessageBox.No message = self.add_file_string(message, file_path) msg_box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Warning, title_txt, message, parent=self) msg_box.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel) msg_box.button(QtWidgets.QMessageBox.Yes).setText( Msg.UNSAVED_CHANGES_YES) msg_box.button(QtWidgets.QMessageBox.Yes).setIcon( self.icon[Itemstyle.MAIN['save']]) msg_box.button(QtWidgets.QMessageBox.No).setText( Msg.UNSAVED_CHANGES_NO) msg_box.button(QtWidgets.QMessageBox.No).setIcon( self.icon[Itemstyle.MAIN['sad']]) msg_box.button(QtWidgets.QMessageBox.Cancel).setText( Msg.UNSAVED_CHANGES_CANCEL) answer = msg_box.exec_() LOGGER.debug('Asked for unsaved changes, user answered: %s', answer) return answer def set_window_title(self, file_info: str = ''): self.setWindowTitle(self.title + ' - ' + self.ver) if file_info != '': if self.unsaved_changes_present: file_info = '*' + file_info + '*' self.setWindowTitle('*' + self.title + ' ' + self.ver + '*') self.label_Dest_File.setText(file_info) else: self.label_Dest_File.setText(Msg.NO_FILE_INFO) def report_conflict(self, item=False, new_id=False): """ Report ID Conflict """ if item: err_msg = Msg.OVERLAY_MISSING_REF[0] + item.text( ItemColumn.NAME) + Msg.OVERLAY_MISSING_REF[1] err_msg += item.text(ItemColumn.ID) if not new_id: # Copy not created err_msg += Msg.OVERLAY_MISSING_REF[2] else: # New Id created instead err_msg += Msg.OVERLAY_MISSING_REF[3] + new_id else: err_msg = Msg.OVERLAY_MISSING_REF[4] self.treeWidget_DestPreset.info_overlay.display_confirm(err_msg, ('[X]', None), immediate=True) def report_name_clash(self, name, id, new_id): self.treeWidget_DestPreset.info_overlay.display( Msg.OVERLAY_NAME_CLASH.format(name=name, id=id, new_id=new_id), 3500) def get_tree_name(self, widget): """ Return user readable tree names """ if widget is self.treeWidget_SrcPreset: return 'Preset Vorgaben' if widget is self.treeWidget_DestPreset: return 'Benutzer Vorgaben' if widget is self.treeWidget_Variants: return 'Varianten Liste' if widget is self.treeWidget_render: return 'Render Liste' return '' def tree_with_focus(self): """ Return the current QTreeWidget in focus """ widget_in_focus = self.focusWidget() if widget_in_focus in self.tree_widget_list: return widget_in_focus return False class send_to_dg: """ Pseudeo clas.s if send_to_dg was never called from GUI """ def thread_running(self=None): """ This will return true from overwritten clas.s if rendering / sending to deltagen is in progress. """ return False def check_item_name(self, item, item_name, __new_item_name: str = None): """ If item name already in unique names, return new name """ unique_names = item.treeWidget().unique_names if not unique_names: return if item_name in unique_names: __new_item_name = create_unique_item_name(item_name, unique_names) if __new_item_name: item.setText(ItemColumn.NAME, __new_item_name) self.sort_tree_widget.sort_current_level(item, item.treeWidget()) return __new_item_name def init_taskbar(self): """ Initializes the MS Windows taskbar button""" # Needs to be called after window is created/shown self.taskbar_btn.setWindow(self.windowHandle()) self.progress = self.taskbar_btn.progress() self.progress.setRange(0, 100) self.progress.valueChanged.connect(self.progress.show) def end_threads(self): for w_tuple in self.widget_pairs: tree_widget, sort_button, line_edit, filter_col = w_tuple tree_widget.filter.end_thread() def closeEvent(self, QCloseEvent): LOGGER.debug( 'Preset Editor SchnuffiWindow close event triggered. Closing SchnuffiWindow.' ) QCloseEvent.ignore() self.app_class.exit_preset_editor()
class _Taskbar: def __init__(self): self.window = None self.taskbarButton = None self.taskbarProgress = None def setWindow(self, window): self.window = window self.resetTaskbar() def clearWindow(self): self.window = None def resetTaskbar(self): self.taskbarButton = QWinTaskbarButton() self.taskbarButton.setWindow(self.window.windowHandle()) self.taskbarProgress = self.taskbarButton.progress() def show(self, indeterminate=False): self.resetTaskbar() if indeterminate: self.taskbarProgress.setMaximum(0) else: self.taskbarProgress.setMaximum(100) self.taskbarProgress.show() def hide(self): self.taskbarProgress.hide() def isVisible(self): return self.taskbarProgress.isVisible() def pause(self): if self.taskbarProgress.maximum() == 0: self.taskbarProgress.setMaximum(100) self.taskbarProgress.setValue(100) self.taskbarProgress.pause() def isPaused(self): return self.taskbarProgress.isPaused() def stop(self): if self.taskbarProgress.maximum() == 0: self.taskbarProgress.setMaximum(100) self.taskbarProgress.setValue(100) self.taskbarProgress.stop() def isStopped(self): return self.taskbarProgress.isStopped() def resume(self): self.taskbarProgress.resume() def setValue(self, value): if self.isPaused() or self.isStopped(): self.resume() self.taskbarProgress.setValue(value) def complete(self): self.hide() self.alert() def alert(self): QtCore.QCoreApplication.instance().alert(self.window)
class DownloadManager(QDialog, Ui_DownloadManager): """ Class implementing the download manager. @signal downloadsCountChanged() emitted to indicate a change of the count of download items """ RemoveNever = 0 RemoveExit = 1 RemoveSuccessFullDownload = 2 UpdateTimerTimeout = 1000 downloadsCountChanged = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(DownloadManager, self).__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.Window) self.__winTaskbarButton = None self.__saveTimer = AutoSaver(self, self.save) self.__model = DownloadModel(self) self.__manager = WebBrowserWindow.networkManager() self.__iconProvider = None self.__downloads = [] self.__downloadDirectory = "" self.__loaded = False self.__rowHeightMultiplier = 1.1 self.setDownloadDirectory(Preferences.getUI("DownloadPath")) self.downloadsView.setShowGrid(False) self.downloadsView.verticalHeader().hide() self.downloadsView.horizontalHeader().hide() self.downloadsView.setAlternatingRowColors(True) self.downloadsView.horizontalHeader().setStretchLastSection(True) self.downloadsView.setModel(self.__model) self.downloadsView.setContextMenuPolicy(Qt.CustomContextMenu) self.downloadsView.customContextMenuRequested.connect( self.__customContextMenuRequested) self.__clearShortcut = QShortcut(QKeySequence("Ctrl+L"), self) self.__clearShortcut.activated.connect(self.on_cleanupButton_clicked) self.__load() self.__updateTimer = QBasicTimer() def __customContextMenuRequested(self, pos): """ Private slot to handle the context menu request for the bookmarks tree. @param pos position the context menu was requested (QPoint) """ menu = QMenu() selectedRowsCount = len( self.downloadsView.selectionModel().selectedRows()) if selectedRowsCount == 1: row = self.downloadsView.selectionModel().selectedRows()[0].row() itm = self.__downloads[row] if itm.downloadedSuccessfully(): menu.addAction(UI.PixmapCache.getIcon("open.png"), self.tr("Open"), self.__contextMenuOpen) elif itm.downloading(): menu.addAction(UI.PixmapCache.getIcon("stopLoading.png"), self.tr("Cancel"), self.__contextMenuCancel) menu.addSeparator() menu.addAction(self.tr("Open Containing Folder"), self.__contextMenuOpenFolder) menu.addSeparator() menu.addAction(self.tr("Go to Download Page"), self.__contextMenuGotoPage) menu.addAction(self.tr("Copy Download Link"), self.__contextMenuCopyLink) menu.addSeparator() menu.addAction(self.tr("Select All"), self.__contextMenuSelectAll) if (selectedRowsCount > 1 or (selectedRowsCount == 1 and not self.__downloads[self.downloadsView.selectionModel( ).selectedRows()[0].row()].downloading())): menu.addSeparator() menu.addAction(self.tr("Remove From List"), self.__contextMenuRemoveSelected) menu.exec_(QCursor.pos()) def shutdown(self): """ Public method to stop the download manager. """ self.save() self.close() def activeDownloadsCount(self): """ Public method to get the number of active downloads. @return number of active downloads (integer) """ count = 0 for download in self.__downloads: if download.downloading(): count += 1 return count def allowQuit(self): """ Public method to check, if it is ok to quit. @return flag indicating allowance to quit (boolean) """ if self.activeDownloadsCount() > 0: res = E5MessageBox.yesNo( self, self.tr(""), self.tr( """There are %n downloads in progress.\n""" """Do you want to quit anyway?""", "", self.activeDownloadsCount()), icon=E5MessageBox.Warning) if not res: self.show() return False self.close() return True def __testWebBrowserView(self, view, url): """ Private method to test a web browser view against an URL. @param view reference to the web browser view to be tested @type WebBrowserView @param url URL to test against @type QUrl @return flag indicating, that the view is the one for the URL @rtype bool """ if view.tabWidget().count() < 2: return False page = view.page() if page.history().count() != 0: return False if (not page.url().isEmpty() and page.url().host() == url.host()): return True requestedUrl = page.requestedUrl() if requestedUrl.isEmpty(): requestedUrl = QUrl(view.tabWidget().urlBarForView(view).text()) return requestedUrl.isEmpty() or requestedUrl.host() == url.host() def __closeDownloadTab(self, url): """ Private method to close an empty tab, that was opened only for loading the download URL. @param url download URL @type QUrl """ if self.__testWebBrowserView( WebBrowserWindow.getWindow().currentBrowser(), url): WebBrowserWindow.getWindow().closeCurrentBrowser() return for window in WebBrowserWindow.mainWindows(): for browser in window.browsers(): if self.__testWebBrowserView(browser, url): window.closeBrowser(browser) return def download(self, downloadItem): """ Public method to download a file. @param downloadItem reference to the download object containing the download data. @type QWebEngineDownloadItem """ url = downloadItem.url() if url.isEmpty(): return self.__closeDownloadTab(url) # Safe Browsing from WebBrowser.SafeBrowsing.SafeBrowsingManager import ( SafeBrowsingManager) if SafeBrowsingManager.isEnabled(): threatLists = ( WebBrowserWindow.safeBrowsingManager().lookupUrl(url)[0]) if threatLists: threatMessages = (WebBrowserWindow.safeBrowsingManager(). getThreatMessages(threatLists)) res = E5MessageBox.warning( WebBrowserWindow.getWindow(), self.tr("Suspicuous URL detected"), self.tr("<p>The URL <b>{0}</b> was found in the Safe" " Browsing database.</p>{1}").format( url.toString(), "".join(threatMessages)), E5MessageBox.StandardButtons(E5MessageBox.Abort | E5MessageBox.Ignore), E5MessageBox.Abort) if res == E5MessageBox.Abort: downloadItem.cancel() return window = WebBrowserWindow.getWindow() if window: pageUrl = window.currentBrowser().url() else: pageUrl = QUrl() from .DownloadItem import DownloadItem itm = DownloadItem(downloadItem=downloadItem, pageUrl=pageUrl, parent=self) self.__addItem(itm) if Preferences.getWebBrowser("DownloadManagerAutoOpen"): self.show() else: self.__startUpdateTimer() def show(self): """ Public slot to show the download manager dialog. """ self.__startUpdateTimer() super(DownloadManager, self).show() self.activateWindow() self.raise_() def __addItem(self, itm, append=False): """ Private method to add a download to the list of downloads. @param itm reference to the download item @type DownloadItem @param append flag indicating to append the item @type bool """ itm.statusChanged.connect(lambda: self.__updateRow(itm)) itm.downloadFinished.connect(self.__finished) # insert at top of window if append: row = self.downloadsCount() else: row = 0 self.__model.beginInsertRows(QModelIndex(), row, row) if append: self.__downloads.append(itm) else: self.__downloads.insert(0, itm) self.__model.endInsertRows() self.downloadsView.setIndexWidget(self.__model.index(row, 0), itm) icon = self.style().standardIcon(QStyle.SP_FileIcon) itm.setIcon(icon) self.downloadsView.setRowHeight( row, itm.sizeHint().height() * self.__rowHeightMultiplier) # just in case the download finished before the constructor returned self.__updateRow(itm) self.changeOccurred() self.downloadsCountChanged.emit() def __updateRow(self, itm): """ Private slot to update a download item. @param itm reference to the download item @type DownloadItem """ if itm not in self.__downloads: return row = self.__downloads.index(itm) if self.__iconProvider is None: self.__iconProvider = QFileIconProvider() icon = self.__iconProvider.icon(QFileInfo(itm.fileName())) if icon.isNull(): icon = self.style().standardIcon(QStyle.SP_FileIcon) itm.setIcon(icon) self.downloadsView.setRowHeight( row, itm.minimumSizeHint().height() * self.__rowHeightMultiplier) remove = False if (itm.downloadedSuccessfully() and self.removePolicy() == DownloadManager.RemoveSuccessFullDownload): remove = True if remove: self.__model.removeRow(row) self.cleanupButton.setEnabled( (self.downloadsCount() - self.activeDownloadsCount()) > 0) # record the change self.changeOccurred() def removePolicy(self): """ Public method to get the remove policy. @return remove policy (integer) """ return Preferences.getWebBrowser("DownloadManagerRemovePolicy") def setRemovePolicy(self, policy): """ Public method to set the remove policy. @param policy policy to be set (DownloadManager.RemoveExit, DownloadManager.RemoveNever, DownloadManager.RemoveSuccessFullDownload) """ assert policy in (DownloadManager.RemoveExit, DownloadManager.RemoveNever, DownloadManager.RemoveSuccessFullDownload) if policy == self.removePolicy(): return Preferences.setWebBrowser("DownloadManagerRemovePolicy", self.policy) def save(self): """ Public method to save the download settings. """ if not self.__loaded: return Preferences.setWebBrowser("DownloadManagerSize", self.size()) Preferences.setWebBrowser("DownloadManagerPosition", self.pos()) if self.removePolicy() == DownloadManager.RemoveExit: return from WebBrowser.WebBrowserWindow import WebBrowserWindow if WebBrowserWindow.isPrivate(): return downloads = [] for download in self.__downloads: downloads.append(download.getData()) Preferences.setWebBrowser("DownloadManagerDownloads", downloads) def __load(self): """ Private method to load the download settings. """ if self.__loaded: return size = Preferences.getWebBrowser("DownloadManagerSize") if size.isValid(): self.resize(size) pos = Preferences.getWebBrowser("DownloadManagerPosition") self.move(pos) from WebBrowser.WebBrowserWindow import WebBrowserWindow if not WebBrowserWindow.isPrivate(): downloads = Preferences.getWebBrowser("DownloadManagerDownloads") for download in downloads: if (not download["URL"].isEmpty() and bool(download["Location"])): from .DownloadItem import DownloadItem itm = DownloadItem(parent=self) itm.setData(download) self.__addItem(itm, append=True) self.cleanupButton.setEnabled( (self.downloadsCount() - self.activeDownloadsCount()) > 0) self.__loaded = True self.downloadsCountChanged.emit() def closeEvent(self, evt): """ Protected event handler for the close event. @param evt reference to the close event @type QCloseEvent """ self.save() def cleanup(self): """ Public slot to cleanup the downloads. """ self.on_cleanupButton_clicked() @pyqtSlot() def on_cleanupButton_clicked(self): """ Private slot to cleanup the downloads. """ if self.downloadsCount() == 0: return self.__model.removeRows(0, self.downloadsCount()) if (self.downloadsCount() == 0 and self.__iconProvider is not None): self.__iconProvider = None self.changeOccurred() self.downloadsCountChanged.emit() def __finished(self, success): """ Private slot to handle a finished download. @param success flag indicating a successful download @type bool """ if self.isVisible(): QApplication.alert(self) self.downloadsCountChanged.emit() if self.activeDownloadsCount() == 0: # all active downloads are done if success and e5App().activeWindow() is not self: if WebBrowserWindow.notificationsEnabled(): WebBrowserWindow.showNotification( UI.PixmapCache.getPixmap("downloads48.png"), self.tr("Downloads finished"), self.tr("All files have been downloaded.")) if not Preferences.getWebBrowser("DownloadManagerAutoClose"): self.raise_() self.activateWindow() self.__stopUpdateTimer() self.infoLabel.clear() self.setWindowTitle(self.tr("Download Manager")) if Globals.isWindowsPlatform(): self.__taskbarButton().progress().hide() if Preferences.getWebBrowser("DownloadManagerAutoClose"): self.close() def setDownloadDirectory(self, directory): """ Public method to set the current download directory. @param directory current download directory (string) """ self.__downloadDirectory = directory if self.__downloadDirectory != "": self.__downloadDirectory += "/" def downloadDirectory(self): """ Public method to get the current download directory. @return current download directory (string) """ return self.__downloadDirectory def downloadsCount(self): """ Public method to get the number of downloads. @return number of downloads @rtype int """ return len(self.__downloads) def downloads(self): """ Public method to get a reference to the downloads. @return reference to the downloads (list of DownloadItem) """ return self.__downloads def changeOccurred(self): """ Public method to signal a change. """ self.__saveTimer.changeOccurred() def __taskbarButton(self): """ Private method to get a reference to the task bar button (Windows only). @return reference to the task bar button @rtype QWinTaskbarButton or None """ if Globals.isWindowsPlatform(): from PyQt5.QtWinExtras import QWinTaskbarButton if self.__winTaskbarButton is None: window = WebBrowserWindow.mainWindow() self.__winTaskbarButton = QWinTaskbarButton( window.windowHandle()) self.__winTaskbarButton.progress().setRange(0, 100) return self.__winTaskbarButton def timerEvent(self, evt): """ Protected event handler for timer events. @param evt reference to the timer event @type QTimerEvent """ if evt.timerId() == self.__updateTimer.timerId(): if self.activeDownloadsCount() == 0: self.__stopUpdateTimer() self.infoLabel.clear() self.setWindowTitle(self.tr("Download Manager")) if Globals.isWindowsPlatform(): self.__taskbarButton().progress().hide() else: progresses = [] for itm in self.__downloads: if (itm is None or itm.downloadCanceled() or not itm.downloading()): continue progresses.append( (itm.downloadProgress(), itm.remainingTime(), itm.currentSpeed())) if not progresses: return remaining = 0 progress = 0 speed = 0.0 for progressData in progresses: if progressData[1] > remaining: remaining = progressData[1] progress += progressData[0] speed += progressData[2] progress = progress / len(progresses) if self.isVisible(): self.infoLabel.setText( self.tr("{0}% of %n file(s) ({1}) {2}", "", len(progresses)).format( progress, speedString(speed), timeString(remaining), )) self.setWindowTitle(self.tr("{0}% - Download Manager")) if Globals.isWindowsPlatform(): self.__taskbarButton().progress().show() self.__taskbarButton().progress().setValue(progress) super(DownloadManager, self).timerEvent(evt) def __startUpdateTimer(self): """ Private slot to start the update timer. """ if self.activeDownloadsCount() and not self.__updateTimer.isActive(): self.__updateTimer.start(DownloadManager.UpdateTimerTimeout, self) def __stopUpdateTimer(self): """ Private slot to stop the update timer. """ self.__updateTimer.stop() ########################################################################### ## Context menu related methods below ########################################################################### def __currentItem(self): """ Private method to get a reference to the current item. @return reference to the current item (DownloadItem) """ index = self.downloadsView.currentIndex() if index and index.isValid(): row = index.row() return self.__downloads[row] return None def __contextMenuOpen(self): """ Private method to open the downloaded file. """ itm = self.__currentItem() if itm is not None: itm.openFile() def __contextMenuOpenFolder(self): """ Private method to open the folder containing the downloaded file. """ itm = self.__currentItem() if itm is not None: itm.openFolder() def __contextMenuCancel(self): """ Private method to cancel the current download. """ itm = self.__currentItem() if itm is not None: itm.cancelDownload() def __contextMenuGotoPage(self): """ Private method to open the download page. """ itm = self.__currentItem() if itm is not None: url = itm.getPageUrl() WebBrowserWindow.mainWindow().openUrl(url, "") def __contextMenuCopyLink(self): """ Private method to copy the download link to the clipboard. """ itm = self.__currentItem() if itm is not None: url = itm.getPageUrl().toDisplayString(QUrl.FullyDecoded) QApplication.clipboard().setText(url) def __contextMenuSelectAll(self): """ Private method to select all downloads. """ self.downloadsView.selectAll() def __contextMenuRemoveSelected(self): """ Private method to remove the selected downloads from the list. """ self.downloadsView.removeSelected()
class MainWindow(QMainWindow): EXIT_CODE_REBOOT = 666 TEMP_PROJECT_FILE = 'vidcutter_reboot.vcp' WORKING_FOLDER = os.path.join(QDir.tempPath(), 'vidcutter') def __init__(self): super(MainWindow, self).__init__() self.video, self.resizeTimer = '', 0 self.parse_cmdline() self.init_settings() self.init_logger() self.init_scale() self.init_cutter() self.setWindowTitle(qApp.applicationName()) self.setContentsMargins(0, 0, 0, 0) self.statusBar().showMessage('Ready') self.statusBar().setStyleSheet('border: none; padding: 0; margin: 0;') self.setAcceptDrops(True) self.show() if sys.platform == 'win32' and TaskbarProgress.isValidWinVer(): self.win_taskbar_button = QWinTaskbarButton(self) self.win_taskbar_button.setWindow(self.windowHandle()) self.win_taskbar_button.progress().setVisible(True) self.win_taskbar_button.progress().setValue(0) self.console.setGeometry(int(self.x() - (self.width() / 2)), self.y() + int(self.height() / 3), 750, 300) if not self.video and os.path.isfile( os.path.join(QDir.tempPath(), MainWindow.TEMP_PROJECT_FILE)): self.video = os.path.join(QDir.tempPath(), MainWindow.TEMP_PROJECT_FILE) if self.video: self.file_opener(self.video) def init_scale(self) -> None: screen_size = qApp.desktop().availableGeometry(-1) self.scale = 'LOW' if screen_size.width() <= 1024 else 'NORMAL' self.setMinimumSize(self.get_size(self.scale)) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) @pyqtSlot(str) def file_opener(self, filename: str) -> None: try: if QFileInfo(filename).suffix() == 'vcp': self.cutter.openProject(project_file=filename) if filename == os.path.join(QDir.tempPath(), MainWindow.TEMP_PROJECT_FILE): os.remove( os.path.join(QDir.tempPath(), MainWindow.TEMP_PROJECT_FILE)) else: self.cutter.loadMedia(filename) except (FileNotFoundError, PermissionError): QMessageBox.critical(self, 'Error loading file', sys.exc_info()[0]) logging.exception('Error loading file') qApp.restoreOverrideCursor() self.restart() @staticmethod def get_size(mode: str = 'NORMAL') -> QSize: modes = { 'LOW': QSize(800, 425), 'NORMAL': QSize(930, 680), 'HIGH': QSize(1850, 1300) } return modes[mode] def init_logger(self) -> None: try: log_path = self.get_app_config_path() except AttributeError: if sys.platform == 'win32': log_path = os.path.join(QDir.homePath(), 'AppData', 'Local', qApp.applicationName().lower()) elif sys.platform == 'darwin': log_path = os.path.join(QDir.homePath(), 'Library', 'Preferences', qApp.applicationName().lower()) else: log_path = os.path.join(QDir.homePath(), '.config', qApp.applicationName().lower()) os.makedirs(log_path, exist_ok=True) self.console = ConsoleWidget(self) self.consoleLogger = ConsoleHandler(self.console) handlers = [ logging.handlers.RotatingFileHandler(os.path.join( log_path, '%s.log' % qApp.applicationName().lower()), maxBytes=1000000, backupCount=1), self.consoleLogger ] if self.parser.isSet(self.debug_option) or self.verboseLogs: # noinspection PyTypeChecker handlers.append(logging.StreamHandler()) logging.setLoggerClass(VideoLogger) logging.basicConfig( handlers=handlers, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M', level=logging.INFO) logging.captureWarnings(capture=True) sys.excepthook = MainWindow.log_uncaught_exceptions if os.getenv('DEBUG', False): logging.info('appconfig folder: {}'.format(log_path)) def init_settings(self) -> None: try: settings_path = self.get_app_config_path() except AttributeError: if sys.platform == 'win32': settings_path = os.path.join(QDir.homePath(), 'AppData', 'Local', qApp.applicationName().lower()) elif sys.platform == 'darwin': settings_path = os.path.join(QDir.homePath(), 'Library', 'Preferences', qApp.applicationName().lower()) else: settings_path = os.path.join(QDir.homePath(), '.config', qApp.applicationName().lower()) os.makedirs(settings_path, exist_ok=True) settings_file = '{}.ini'.format(qApp.applicationName().lower()) self.settings = QSettings(os.path.join(settings_path, settings_file), QSettings.IniFormat) if self.settings.value('geometry') is not None: self.restoreGeometry(self.settings.value('geometry')) if self.settings.value('windowState') is not None: self.restoreState(self.settings.value('windowState')) self.theme = self.settings.value('theme', 'light', type=str) self.startupvol = self.settings.value('volume', 100, type=int) self.verboseLogs = self.settings.value('verboseLogs', 'off', type=str) in {'on', 'true'} @staticmethod def log_uncaught_exceptions(cls, exc, tb) -> None: logging.critical(''.join(traceback.format_tb(tb))) logging.critical('{0}: {1}'.format(cls, exc)) def parse_cmdline(self) -> None: self.parser = QCommandLineParser() self.parser.setApplicationDescription( '\nVidCutter - the simplest + fastest media cutter & joiner') self.parser.addPositionalArgument('video', 'Preload video file', '[video]') self.parser.addPositionalArgument( 'project', 'Open VidCutter project file (.vcp)', '[project]') self.debug_option = QCommandLineOption( ['debug'], 'debug mode; verbose console output & logging. ' 'This will basically output what is being logged to file to the ' 'console stdout. Mainly useful for debugging problems with your ' 'system video and/or audio stack and codec configuration.') self.parser.addOption(self.debug_option) self.parser.addVersionOption() self.parser.addHelpOption() self.parser.process(qApp) self.args = self.parser.positionalArguments() if self.parser.isSet(self.debug_option): os.environ['DEBUG'] = '1' if len(self.args) > 0: file_path = QFileInfo(self.args[0]).absoluteFilePath() if not os.path.exists(file_path): sys.stderr.write('\nERROR: File not found: %s\n' % file_path) self.close() qApp.exit(1) self.video = file_path def init_cutter(self) -> None: self.cutter = VideoCutter(self) self.cutter.errorOccurred.connect(self.errorHandler) self.setCentralWidget(self.cutter) qApp.setWindowIcon(VideoCutter.getAppIcon(encoded=False)) @staticmethod def get_bitness() -> int: from struct import calcsize return calcsize('P') * 8 @pyqtSlot() def reboot(self) -> None: if self.cutter.mediaAvailable: self.cutter.saveProject(reboot=True) self.save_settings() qApp.exit(MainWindow.EXIT_CODE_REBOOT) def save_settings(self) -> None: self.settings.setValue('lastFolder', self.cutter.lastFolder) self.settings.setValue('geometry', self.saveGeometry()) self.settings.setValue('windowState', self.saveState()) self.settings.sync() @pyqtSlot(bool) def lock_gui(self, locked: bool = True) -> None: if locked: qApp.setOverrideCursor(Qt.WaitCursor) self.cutter.cliplist.setEnabled(False) self.setEnabled(False) else: self.setEnabled(True) self.cutter.cliplist.setEnabled(True) qApp.restoreOverrideCursor() qApp.processEvents() @property def flatpak(self) -> bool: return sys.platform.startswith('linux') and QFileInfo( __file__).absolutePath().startswith('/app/') def get_app_config_path(self) -> str: if self.flatpak: confpath = QProcessEnvironment.systemEnvironment().value( 'XDG_CONFIG_HOME', '') if len(confpath): return confpath else: return os.path.join(QDir.homePath(), '.var', 'app', vidcutter.__desktopid__, 'config') return QStandardPaths.writableLocation( QStandardPaths.AppConfigLocation).replace( qApp.applicationName(), qApp.applicationName().lower()) @staticmethod def get_path(path: str = None, override: bool = False) -> str: if override: if getattr(sys, 'frozen', False) and getattr( sys, '_MEIPASS', False): # noinspection PyProtectedMember, PyUnresolvedReferences return os.path.join(sys._MEIPASS, path) return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), path) return ':{}'.format(path) @pyqtSlot(str) def errorHandler(self, msg: str, title: str = None) -> None: qApp.restoreOverrideCursor() QMessageBox.critical(self, 'An error occurred' if title is None else title, msg, QMessageBox.Ok) logging.error(msg) @staticmethod @pyqtSlot() def cleanup(): shutil.rmtree(MainWindow.WORKING_FOLDER, ignore_errors=True) def contextMenuEvent(self, event: QContextMenuEvent) -> None: if event.reason() in { QContextMenuEvent.Mouse, QContextMenuEvent.Keyboard }: self.cutter.appmenu.popup(event.globalPos()) super(MainWindow, self).contextMenuEvent(event) def mousePressEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.LeftButton and self.cutter.mediaAvailable: self.cutter.cliplist.clearSelection() self.cutter.timeCounter.clearFocus() self.cutter.frameCounter.clearFocus() # noinspection PyBroadException try: if hasattr(self.cutter, 'notify'): self.cutter.notify.close() except BaseException: pass event.accept() def dragEnterEvent(self, event: QDragEnterEvent) -> None: if event.mimeData().hasUrls(): event.accept() def dropEvent(self, event: QDropEvent) -> None: filename = event.mimeData().urls()[0].toLocalFile() self.file_opener(filename) event.accept() def resizeEvent(self, event: QResizeEvent) -> None: try: if self.isEnabled( ) and self.cutter.mediaAvailable and self.cutter.thumbnailsButton.isChecked( ): if self.cutter.seekSlider.thumbnailsOn: self.cutter.sliderWidget.setLoader(True) self.cutter.sliderWidget.hideThumbs() if self.resizeTimer: self.killTimer(self.resizeTimer) self.resizeTimer = self.startTimer(500) except AttributeError: pass def timerEvent(self, event: QTimerEvent) -> None: try: self.cutter.seekSlider.reloadThumbs() self.killTimer(self.resizeTimer) self.resizeTimer = 0 except AttributeError: pass def closeEvent(self, event: QCloseEvent) -> Optional[Callable]: event.accept() try: if not self.isEnabled(): exitwarn = VCMessageBox('Warning', 'Media is currently being processed', 'Are you sure you want to exit now?', parent=self) exitwarn.addButton('Yes', QMessageBox.NoRole) cancelbutton = exitwarn.addButton('No', QMessageBox.RejectRole) exitwarn.exec_() res = exitwarn.clickedButton() if res == cancelbutton: event.ignore() return noexit, callback = self.cutter.saveWarning() if noexit: event.ignore() if callback is not None: return callback() else: return except AttributeError: logging.exception('warning dialogs on app exit exception', exc_info=True) self.console.deleteLater() if hasattr(self, 'cutter'): self.save_settings() try: if hasattr(self.cutter.videoService, 'smartcut_jobs'): [ self.cutter.videoService.cleanup(job.files.values()) for job in self.cutter.videoService.smartcut_jobs ] if hasattr(self.cutter, 'mpvWidget'): self.cutter.mpvWidget.shutdown() except AttributeError: pass try: qApp.exit(0) except mpv.MPVError: pass
class B3dVersionMangerMainWindow(QMainWindow, main_window_design.Ui_MainWindow): def __init__(self, app): super().__init__() self.app = app self.setupUi(self) # Read icons self.app_icon = QIcon(":/icons/app.svg") self.star_icon = QIcon(":/icons/star.svg") self.star_inv_icon = QIcon(":/icons/star_inv.svg") self.trash_icon = QIcon(":/icons/delete.svg") self.quit_icon = QIcon(":/icons/quit_inv.svg") self.fake_icon = QIcon(":/icons/fake.svg") # Read settings self.settings = QSettings('b3d_version_manager', 'settings') is_register_blend = self.settings.value('is_register_blend', type=bool) self.is_run_minimized = self.settings.value('is_run_minimized', type=bool) is_run_on_startup = self.settings.value('is_run_on_startup', type=bool) root_folder = self.settings.value('root_folder') if (not root_folder) or (not os.path.isdir(root_folder)): exe_path = os.path.dirname(sys.executable) self.settings.setValue('root_folder', exe_path) # Custom title bar self.title.setText("%s %s" % (QApplication.applicationName(), QApplication.applicationVersion())) self.btnClose.clicked.connect(self.close) self.btnMinimize.clicked.connect(self.showMinimized) # Custom menu bar self.actionToggleRegisterBlend.setChecked(is_register_blend) self.actionToggleRegisterBlend.triggered.connect( self.toggle_register_blend) self.actionToggleRunMinimized.setChecked(self.is_run_minimized) self.actionToggleRunMinimized.triggered.connect( self.toggle_run_minimized) self.actionToggleRunOnStartup.setChecked(is_run_on_startup) self.actionToggleRunOnStartup.triggered.connect( self.toggle_run_on_startup) self.actionQuit.triggered.connect(self.quit) self.menubar.hide() self.btnFile.setMenu(self.menuFile) # Root folder layout self.labelRootFolder.setText(self.settings.value('root_folder')) self.btnSetRootFolder.clicked.connect(self.set_root_folder) self.btnOpenRootFolder.clicked.connect( lambda: os.startfile(self.settings.value('root_folder'))) # Tray layout self.blender_action = QAction(self.star_inv_icon, "Blender", self) show_action = QAction("Show", self) hide_action = QAction("Hide", self) quit_action = QAction(self.quit_icon, "Quit", self) self.blender_action.triggered.connect(self.open_latest_b3d) show_action.triggered.connect(self.bring_to_front) hide_action.triggered.connect(self.hide) quit_action.triggered.connect(self.quit) self.tray_menu = QMenu() self.tray_menu.addAction(self.blender_action) self.tray_menu.addSeparator() self.tray_menu.addAction(show_action) self.tray_menu.addAction(hide_action) self.tray_menu.addAction(quit_action) self.tray_icon = QSystemTrayIcon(self.app_icon, self) self.tray_icon.setContextMenu(self.tray_menu) self.tray_icon.messageClicked.connect(self.bring_to_front) self.tray_icon.activated.connect(lambda btn: self.bring_to_front() if btn == 3 else False) self.tray_icon.show() # Version layout self.btnUpdate.clicked.connect(self.update) self.set_task_visible(False) self.layouts = [] self.latest_local = None self.collect_versions() self.draw_list_versions() # Custom drag behaviour self.old_pos = self.pos() self.pressed = False # Update task self.is_update_running = False self.uptodate_silent = False self.uptodate_thread = CheckForUpdates(self) self.uptodate_thread.new_version_obtained.connect( self.show_new_version) self.uptodate_thread.start() self.taskbar_progress = None def showEvent(self, event): # Setup taskbar if not self.taskbar_progress: self.task_button = QWinTaskbarButton(self) self.task_button.setWindow(self.windowHandle()) self.taskbar_progress = self.task_button.progress() self.taskbar_progress.setVisible(True) self.taskbar_progress.setValue(0) def open_latest_b3d(self): if self.layouts: self.layouts[0].open() def show_new_version(self, display_name): if (display_name == self.progressBar.text()): return self.set_task_visible(True) self.set_progress_bar(0, 0, display_name) if self.isHidden() and not self.uptodate_silent: self.tray_icon.showMessage( "Blender Version Manager", "New version of Blender 2.8 is avaliable!", QSystemTrayIcon.Information, 4000) def set_task_visible(self, is_visible): if is_visible: self.progressBar.show() self.btnUpdate.show() self.btnCancel.hide() else: self.progressBar.hide() self.btnUpdate.hide() self.btnCancel.hide() def is_running_task(self): if self.is_update_running: QMessageBox.information(self, "Warning", "Update task in progress!", QMessageBox.Ok) return True else: return False def toggle_run_minimized(self, is_checked): self.settings.setValue('is_run_minimized', is_checked) def toggle_register_blend(self, is_checked): self.settings.setValue('is_register_blend', is_checked) def cleanup_layout(self, layout): while layout.count(): item = layout.takeAt(0) widget = item.widget() if widget is not None: widget.deleteLater() else: self.cleanup_layout(item.layout()) def collect_versions(self): self.layouts.clear() root_folder = self.settings.value('root_folder') dirs = next(os.walk(root_folder))[1] versions = [] for dir in dirs: if os.path.isfile(os.path.join(root_folder, dir, "blender.exe")): versions.append(dir) for ver in versions: b3d_item_layout = B3dItemLayout(root_folder, ver, False, self) self.layouts.append(b3d_item_layout) def draw_list_versions(self): if len(self.layouts) > 0: self.layouts.sort(key=lambda ver: ver.mtime, reverse=True) self.latest_local = self.layouts[0].git self.blender_action.setVisible(True) for b3d_item_layout in self.layouts: self.layoutListVersions.removeItem(b3d_item_layout) b3d_item_layout.set_is_latest(False) self.layouts[0].set_is_latest(True) for b3d_item_layout in self.layouts: self.layoutListVersions.addLayout(b3d_item_layout) else: self.latest_local = None label = QLabel("No Local Versions Found!") label.setStyleSheet("color: white; font-size: 10pt;") label.setAlignment(Qt.AlignCenter) label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.layoutListVersions.addWidget(label) self.blender_action.setVisible(False) def set_root_folder(self): root_folder = self.settings.value('root_folder') dir = QFileDialog.getExistingDirectory(self, "Choose Root Folder", root_folder) if dir and (dir != root_folder): self.settings.setValue('root_folder', dir) self.labelRootFolder.setText(dir) self.cleanup_layout(self.layoutListVersions) self.collect_versions() self.draw_list_versions() def update(self): self.is_update_running = True self.uptodate_thread.is_running = False self.uptodate_thread.terminate() self.uptodate_thread.wait() self.btnUpdate.hide() self.btnCancel.show() self.btnSetRootFolder.hide() self.set_progress_bar(0, 0, "Downloading: %p%") self.build_loader = BuildLoader(self, self.uptodate_thread.download_url) self.build_loader.finished.connect(self.finished) self.build_loader.progress_changed.connect(self.set_progress_bar) self.build_loader.block_abortion.connect(lambda: self.btnCancel.hide()) self.btnCancel.clicked.connect(self.build_loader.stop) self.build_loader.start() def finished(self, version): self.build_loader.terminate() self.build_loader.wait() self.btnSetRootFolder.show() self.set_task_visible(False) self.is_update_running = False if version: root_folder = self.settings.value('root_folder') b3d_item_layout = B3dItemLayout(root_folder, version, True, self) self.layouts.append(b3d_item_layout) self.tray_icon.showMessage("Blender Version Manager", "Update finished!", QSystemTrayIcon.Information, 4000) self.draw_list_versions() self.uptodate_thread = CheckForUpdates(self) self.uptodate_thread.new_version_obtained.connect( self.show_new_version) self.set_progress_bar(0, 0, "") self.uptodate_thread.start() def set_progress_bar(self, progress_bar_val, taskbar_val, format): self.progressBar.setFormat(format) self.progressBar.setValue(progress_bar_val * 100) if self.taskbar_progress: self.taskbar_progress.setValue(taskbar_val * 100) def quit(self): if not self.is_running_task(): self.tray_icon.hide() self.app.quit() def closeEvent(self, event): if self.actionToggleRunMinimized.isChecked(): event.ignore() self.hide() elif self.is_running_task(): event.ignore() else: self.tray_icon.hide() event.accept() def mousePressEvent(self, event): self.old_pos = event.globalPos() self.pressing = True def mouseMoveEvent(self, event): if self.pressing: delta = QPoint(event.globalPos() - self.old_pos) self.move(self.x() + delta.x(), self.y() + delta.y()) self.old_pos = event.globalPos() def mouseReleaseEvent(self, QMouseEvent): self.pressing = False def toggle_run_on_startup(self, is_checked): key = winreg.OpenKey( winreg.HKEY_CURRENT_USER, r'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run', 0, winreg.KEY_SET_VALUE) if (is_checked): path = sys.executable winreg.SetValueEx(key, 'Blender Version Manager', 0, winreg.REG_SZ, path) else: try: winreg.DeleteValue(key, 'Blender Version Manager') except: pass key.Close() self.settings.setValue('is_run_on_startup', is_checked) def bring_to_front(self): self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) self.activateWindow() self.show()