class ExtractAudioDialog(QDialog, Ui_extractAudioDialog): ''' Allows user to load a signal, processing it if necessary. ''' def __init__(self, parent, preferences, signal_model, default_signal=None, is_remux=False): super(ExtractAudioDialog, self).__init__(parent) self.setupUi(self) for f in COMPRESS_FORMAT_OPTIONS: self.audioFormat.addItem(f) self.showProbeButton.setIcon(qta.icon('fa5s.info')) self.showRemuxCommand.setIcon(qta.icon('fa5s.info')) self.inputFilePicker.setIcon(qta.icon('fa5s.folder-open')) self.targetDirPicker.setIcon(qta.icon('fa5s.folder-open')) self.calculateGainAdjustment.setIcon(qta.icon('fa5s.sliders-h')) self.limitRange.setIcon(qta.icon('fa5s.cut')) self.statusBar = QStatusBar() self.statusBar.setSizeGripEnabled(False) self.boxLayout.addWidget(self.statusBar) self.__preferences = preferences self.__signal_model = signal_model self.__default_signal = default_signal self.__executor = None self.__sound = None self.__extracted = False self.__stream_duration_micros = [] self.__is_remux = is_remux if self.__is_remux: self.setWindowTitle('Remux Audio') self.showRemuxCommand.setVisible(self.__is_remux) defaultOutputDir = self.__preferences.get(EXTRACTION_OUTPUT_DIR) if os.path.isdir(defaultOutputDir): self.targetDir.setText(defaultOutputDir) self.__reinit_fields() self.filterMapping.itemDoubleClicked.connect(self.show_mapping_dialog) self.inputDrop.callback = self.__handle_drop def __handle_drop(self, file): if file.startswith('file:/'): file = url2pathname(urlparse(file).path) if os.path.exists(file) and os.path.isfile(file): self.inputFile.setText(file) self.__probe_file() def show_remux_cmd(self): ''' Pops the ffmpeg command into a message box ''' if self.__executor is not None and self.__executor.filter_complex_script_content is not None: msg_box = QMessageBox() font = QFont() font.setFamily("Consolas") font.setPointSize(8) msg_box.setFont(font) msg_box.setText( self.__executor.filter_complex_script_content.replace( ';', ';\n')) msg_box.setIcon(QMessageBox.Information) msg_box.setWindowTitle('Remux Script') msg_box.exec() def show_mapping_dialog(self, item): ''' Shows the edit mapping dialog ''' if len(self.__signal_model) > 0 or self.__default_signal is not None: channel_idx = self.filterMapping.indexFromItem(item).row() mapped_filter = self.__executor.channel_to_filter.get( channel_idx, None) EditMappingDialog(self, channel_idx, self.__signal_model, self.__default_signal, mapped_filter, self.filterMapping.count(), self.map_filter_to_channel).exec() def map_filter_to_channel(self, channel_idx, signal): ''' updates the mapping of the given signal to the specified channel idx ''' if self.audioStreams.count() > 0 and self.__executor is not None: self.__executor.map_filter_to_channel(channel_idx, signal) self.__display_command_info() def selectFile(self): self.__reinit_fields() dialog = QFileDialog(parent=self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setWindowTitle('Select Audio or Video File') if dialog.exec(): selected = dialog.selectedFiles() if len(selected) > 0: self.inputFile.setText(selected[0]) self.__probe_file() def __reinit_fields(self): ''' Resets various fields and temporary state. ''' if self.__sound is not None: if not self.__sound.isFinished(): self.__sound.stop() self.__sound = None self.audioStreams.clear() self.videoStreams.clear() self.statusBar.clearMessage() self.__executor = None self.__extracted = False self.__stream_duration_micros = [] self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) self.buttonBox.button(QDialogButtonBox.Ok).setText( 'Remux' if self.__is_remux else 'Extract') if self.__is_remux: self.signalName.setVisible(False) self.signalNameLabel.setVisible(False) self.filterMapping.setVisible(True) self.filterMappingLabel.setVisible(True) self.includeOriginalAudio.setVisible(True) self.includeSubtitles.setVisible(True) self.gainOffset.setVisible(True) self.gainOffsetLabel.setVisible(True) self.gainOffset.setEnabled(False) self.gainOffsetLabel.setEnabled(False) self.calculateGainAdjustment.setVisible(True) self.calculateGainAdjustment.setEnabled(False) self.adjustRemuxedAudio.setVisible(True) self.remuxedAudioOffset.setVisible(True) self.adjustRemuxedAudio.setEnabled(False) self.remuxedAudioOffset.setEnabled(False) else: self.signalName.setText('') self.filterMapping.setVisible(False) self.filterMappingLabel.setVisible(False) self.includeOriginalAudio.setVisible(False) self.includeSubtitles.setVisible(False) self.gainOffset.setVisible(False) self.gainOffsetLabel.setVisible(False) self.calculateGainAdjustment.setVisible(False) self.adjustRemuxedAudio.setVisible(False) self.remuxedAudioOffset.setVisible(False) self.eacBitRate.setVisible(False) self.monoMix.setChecked(self.__preferences.get(EXTRACTION_MIX_MONO)) self.bassManage.setChecked(False) self.decimateAudio.setChecked( self.__preferences.get(EXTRACTION_DECIMATE)) self.includeOriginalAudio.setChecked( self.__preferences.get(EXTRACTION_INCLUDE_ORIGINAL)) self.includeSubtitles.setChecked( self.__preferences.get(EXTRACTION_INCLUDE_SUBTITLES)) if self.__preferences.get(EXTRACTION_COMPRESS): self.audioFormat.setCurrentText(COMPRESS_FORMAT_FLAC) else: self.audioFormat.setCurrentText(COMPRESS_FORMAT_NATIVE) self.monoMix.setEnabled(False) self.bassManage.setEnabled(False) self.decimateAudio.setEnabled(False) self.audioFormat.setEnabled(False) self.eacBitRate.setEnabled(False) self.includeOriginalAudio.setEnabled(False) self.includeSubtitles.setEnabled(False) self.inputFilePicker.setEnabled(True) self.audioStreams.setEnabled(False) self.videoStreams.setEnabled(False) self.channelCount.setEnabled(False) self.lfeChannelIndex.setEnabled(False) self.targetDirPicker.setEnabled(True) self.outputFilename.setEnabled(False) self.showProbeButton.setEnabled(False) self.filterMapping.setEnabled(False) self.filterMapping.clear() self.ffmpegCommandLine.clear() self.ffmpegCommandLine.setEnabled(False) self.ffmpegOutput.clear() self.ffmpegOutput.setEnabled(False) self.ffmpegProgress.setEnabled(False) self.ffmpegProgressLabel.setEnabled(False) self.ffmpegProgress.setValue(0) self.rangeFrom.setEnabled(False) self.rangeSeparatorLabel.setEnabled(False) self.rangeTo.setEnabled(False) self.limitRange.setEnabled(False) self.signalName.setEnabled(False) self.signalNameLabel.setEnabled(False) self.showRemuxCommand.setEnabled(False) def __probe_file(self): ''' Probes the specified file using ffprobe in order to discover the audio streams. ''' file_name = self.inputFile.text() self.__executor = Executor( file_name, self.targetDir.text(), mono_mix=self.monoMix.isChecked(), decimate_audio=self.decimateAudio.isChecked(), audio_format=self.audioFormat.currentText(), audio_bitrate=self.eacBitRate.value(), include_original=self.includeOriginalAudio.isChecked(), include_subtitles=self.includeSubtitles.isChecked(), signal_model=self.__signal_model if self.__is_remux else None, decimate_fs=self.__preferences.get(ANALYSIS_TARGET_FS), bm_fs=self.__preferences.get(BASS_MANAGEMENT_LPF_FS)) self.__executor.progress_handler = self.__handle_ffmpeg_process from app import wait_cursor with wait_cursor(f"Probing {file_name}"): self.__executor.probe_file() self.showProbeButton.setEnabled(True) if self.__executor.has_audio(): for a in self.__executor.audio_stream_data: text, duration_micros = parse_audio_stream( self.__executor.probe, a) self.audioStreams.addItem(text) self.__stream_duration_micros.append(duration_micros) self.videoStreams.addItem('No Video') for a in self.__executor.video_stream_data: self.videoStreams.addItem( parse_video_stream(self.__executor.probe, a)) if self.__is_remux and self.videoStreams.count() > 1: if self.audioFormat.findText(COMPRESS_FORMAT_EAC3) == -1: self.audioFormat.addItem(COMPRESS_FORMAT_EAC3) if self.__preferences.get(EXTRACTION_COMPRESS): self.audioFormat.setCurrentText(COMPRESS_FORMAT_EAC3) self.eacBitRate.setVisible(True) else: self.audioFormat.setCurrentText(COMPRESS_FORMAT_NATIVE) self.eacBitRate.setVisible(False) self.videoStreams.setCurrentIndex(1) self.adjustRemuxedAudio.setEnabled(True) self.remuxedAudioOffset.setEnabled(True) self.gainOffsetLabel.setEnabled(True) self.calculateGainAdjustment.setEnabled(True) self.audioStreams.setEnabled(True) self.videoStreams.setEnabled(True) self.channelCount.setEnabled(True) self.lfeChannelIndex.setEnabled(True) self.monoMix.setEnabled(True) self.bassManage.setEnabled(True) self.decimateAudio.setEnabled(True) self.audioFormat.setEnabled(True) self.eacBitRate.setEnabled(True) self.includeOriginalAudio.setEnabled(True) self.outputFilename.setEnabled(True) self.ffmpegCommandLine.setEnabled(True) self.filterMapping.setEnabled(True) self.limitRange.setEnabled(True) self.showRemuxCommand.setEnabled(True) self.__fit_options_to_selected() else: self.statusBar.showMessage( f"{file_name} contains no audio streams!") def onVideoStreamChange(self, idx): if idx == 0: eac_idx = self.audioFormat.findText(COMPRESS_FORMAT_EAC3) if eac_idx > -1: self.audioFormat.removeItem(eac_idx) if self.__preferences.get(EXTRACTION_COMPRESS): self.audioFormat.setCurrentText(COMPRESS_FORMAT_FLAC) else: self.audioFormat.setCurrentText(COMPRESS_FORMAT_NATIVE) else: if self.audioFormat.findText(COMPRESS_FORMAT_EAC3) == -1: self.audioFormat.addItem(COMPRESS_FORMAT_EAC3) if self.__preferences.get(EXTRACTION_COMPRESS): self.audioFormat.setCurrentText(COMPRESS_FORMAT_EAC3) else: self.audioFormat.setCurrentText(COMPRESS_FORMAT_NATIVE) self.updateFfmpegSpec() def updateFfmpegSpec(self): ''' Creates a new ffmpeg command for the specified channel layout. ''' if self.__executor is not None: self.__executor.update_spec(self.audioStreams.currentIndex(), self.videoStreams.currentIndex() - 1, self.monoMix.isChecked()) self.__init_channel_count_fields(self.__executor.channel_count, lfe_index=self.__executor.lfe_idx) self.__fit_options_to_selected() self.__display_command_info() self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) def __fit_options_to_selected(self): # if we have no video then the output cannot contain multiple streams if self.videoStreams.currentIndex() == 0: self.includeOriginalAudio.setChecked(False) self.includeOriginalAudio.setEnabled(False) self.includeSubtitles.setChecked(False) self.includeSubtitles.setEnabled(False) else: self.includeOriginalAudio.setEnabled(True) self.includeSubtitles.setEnabled(True) # don't allow mono mix option if the stream is mono if self.channelCount.value() == 1: self.monoMix.setChecked(False) self.monoMix.setEnabled(False) self.bassManage.setChecked(False) self.bassManage.setEnabled(False) else: self.monoMix.setEnabled(True) # only allow bass management if we have an LFE channel if self.__executor.lfe_idx == 0: self.bassManage.setChecked(False) self.bassManage.setEnabled(False) else: self.bassManage.setEnabled(True) def __display_command_info(self): self.outputFilename.setText(self.__executor.output_file_name) self.ffmpegCommandLine.setPlainText(self.__executor.ffmpeg_cli) self.filterMapping.clear() for channel_idx, signal in self.__executor.channel_to_filter.items(): self.filterMapping.addItem( f"Channel {channel_idx + 1} -> {signal.name if signal else 'Passthrough'}" ) def updateOutputFilename(self): ''' Updates the output file name. ''' if self.__executor is not None: self.__executor.output_file_name = self.outputFilename.text() self.__display_command_info() def overrideFfmpegSpec(self, _): if self.__executor is not None: self.__executor.override('custom', self.channelCount.value(), self.lfeChannelIndex.value()) self.__fit_options_to_selected() self.__display_command_info() def toggle_decimate_audio(self): ''' Reacts to the change in decimation. ''' if self.audioStreams.count() > 0 and self.__executor is not None: self.__executor.decimate_audio = self.decimateAudio.isChecked() self.__display_command_info() def toggle_bass_manage(self): ''' Reacts to the change in bass management. ''' if self.audioStreams.count() > 0 and self.__executor is not None: self.__executor.bass_manage = self.bassManage.isChecked() self.__display_command_info() def change_audio_format(self, audio_format): ''' Reacts to the change in audio format. ''' if self.audioStreams.count() > 0 and self.__executor is not None: self.__executor.audio_format = audio_format if audio_format == COMPRESS_FORMAT_EAC3: self.eacBitRate.setVisible(True) self.__executor.audio_bitrate = self.eacBitRate.value() else: self.eacBitRate.setVisible(False) self.__display_command_info() def change_audio_bitrate(self, bitrate): ''' Allows the bitrate to be updated ''' if self.__executor is not None: self.__executor.audio_bitrate = bitrate self.__display_command_info() def update_original_audio(self): ''' Reacts to the change in original audio selection. ''' if self.audioStreams.count() > 0 and self.__executor is not None: if self.includeOriginalAudio.isChecked(): self.__executor.include_original_audio = True self.__executor.original_audio_offset = self.gainOffset.value() self.gainOffset.setEnabled(True) self.gainOffsetLabel.setEnabled(True) else: self.__executor.include_original_audio = False self.__executor.original_audio_offset = 0.0 self.gainOffset.setEnabled(False) self.gainOffsetLabel.setEnabled(False) self.__display_command_info() def toggle_include_subtitles(self): ''' Reacts to the change in subtitles selection. ''' if self.audioStreams.count() > 0 and self.__executor is not None: self.__executor.include_subtitles = self.includeSubtitles.isChecked( ) self.__display_command_info() def toggleMonoMix(self): ''' Reacts to the change in mono vs multichannel target. ''' if self.audioStreams.count() > 0 and self.__executor is not None: self.__executor.mono_mix = self.monoMix.isChecked() self.__display_command_info() def toggle_range(self): ''' toggles whether the range is enabled or not ''' if self.limitRange.isChecked(): self.limitRange.setText('Cut') if self.audioStreams.count() > 0: duration_ms = int(self.__stream_duration_micros[ self.audioStreams.currentIndex()] / 1000) if duration_ms > 1: from model.report import block_signals with block_signals(self.rangeFrom): self.rangeFrom.setTimeRange( QTime.fromMSecsSinceStartOfDay(0), QTime.fromMSecsSinceStartOfDay(duration_ms - 1)) self.rangeFrom.setTime( QTime.fromMSecsSinceStartOfDay(0)) self.rangeFrom.setEnabled(True) self.rangeSeparatorLabel.setEnabled(True) with block_signals(self.rangeTo): self.rangeTo.setEnabled(True) self.rangeTo.setTimeRange( QTime.fromMSecsSinceStartOfDay(1), QTime.fromMSecsSinceStartOfDay(duration_ms)) self.rangeTo.setTime( QTime.fromMSecsSinceStartOfDay(duration_ms)) else: self.limitRange.setText('Enable') self.rangeFrom.setEnabled(False) self.rangeSeparatorLabel.setEnabled(False) self.rangeTo.setEnabled(False) if self.__executor is not None: self.__executor.start_time_ms = 0 self.__executor.end_time_ms = 0 def update_start_time(self, time): ''' Reacts to start time changes ''' self.__executor.start_time_ms = time.msecsSinceStartOfDay() self.__display_command_info() def update_end_time(self, time): ''' Reacts to end time changes ''' msecs = time.msecsSinceStartOfDay() duration_ms = int( self.__stream_duration_micros[self.audioStreams.currentIndex()] / 1000) self.__executor.end_time_ms = msecs if msecs != duration_ms else 0 self.__display_command_info() def __init_channel_count_fields(self, channels, lfe_index=0): from model.report import block_signals with block_signals(self.lfeChannelIndex): self.lfeChannelIndex.setMaximum(channels) self.lfeChannelIndex.setValue(lfe_index) with block_signals(self.channelCount): self.channelCount.setMaximum(channels) self.channelCount.setValue(channels) def reject(self): ''' Stops any sound that is playing and exits. ''' if self.__sound is not None and not self.__sound.isFinished(): self.__sound.stop() self.__sound = None QDialog.reject(self) def accept(self): ''' Executes the ffmpeg command. ''' if self.__extracted is False: self.__extract() if not self.__is_remux: self.signalName.setEnabled(True) self.signalNameLabel.setEnabled(True) else: if self.__create_signals(): QDialog.accept(self) def __create_signals(self): ''' Creates signals from the output file just created. :return: True if we created the signals. ''' loader = AutoWavLoader(self.__preferences) output_file = self.__executor.get_output_path() if os.path.exists(output_file): from app import wait_cursor with wait_cursor(f"Creating signals for {output_file}"): logger.info(f"Creating signals for {output_file}") name_provider = lambda channel, channel_count: get_channel_name( self.signalName.text(), channel, channel_count, channel_layout_name=self.__executor.channel_layout_name) loader.load(output_file) signal = loader.auto_load(name_provider, self.decimateAudio.isChecked()) self.__signal_model.add(signal) return True else: msg_box = QMessageBox() msg_box.setText( f"Extracted audio file does not exist at: \n\n {output_file}") msg_box.setIcon(QMessageBox.Critical) msg_box.setWindowTitle('Unexpected Error') msg_box.exec() return False def __extract(self): ''' Triggers the ffmpeg command. ''' if self.__executor is not None: logger.info( f"Extracting {self.outputFilename.text()} from {self.inputFile.text()}" ) self.__executor.execute() def __handle_ffmpeg_process(self, key, value): ''' Handles progress reports from ffmpeg in order to communicate status via the progress bar. Used as a slot connected to a signal emitted by the AudioExtractor. :param key: the key. :param value: the value. ''' if key == SIGNAL_CONNECTED: self.__extract_started() elif key == 'out_time_ms': out_time_ms = int(value) if self.__executor.start_time_ms > 0 and self.__executor.end_time_ms > 0: total_micros = (self.__executor.end_time_ms - self.__executor.start_time_ms) * 1000 elif self.__executor.end_time_ms > 0: total_micros = self.__executor.end_time_ms * 1000 elif self.__executor.start_time_ms > 0: total_micros = self.__stream_duration_micros[ self.audioStreams.currentIndex()] - ( self.__executor.start_time_ms * 1000) else: total_micros = self.__stream_duration_micros[ self.audioStreams.currentIndex()] logger.debug( f"{self.inputFile.text()} -- {key}={value} vs {total_micros}") if total_micros > 0: progress = (out_time_ms / total_micros) * 100.0 self.ffmpegProgress.setValue(math.ceil(progress)) self.ffmpegProgress.setTextVisible(True) self.ffmpegProgress.setFormat(f"{round(progress, 2):.2f}%") elif key == SIGNAL_ERROR: self.__extract_complete(value, False) elif key == SIGNAL_COMPLETE: self.__extract_complete(value, True) def __extract_started(self): ''' Changes the UI to signal that extraction has started ''' self.inputFilePicker.setEnabled(False) self.audioStreams.setEnabled(False) self.videoStreams.setEnabled(False) self.channelCount.setEnabled(False) self.lfeChannelIndex.setEnabled(False) self.monoMix.setEnabled(False) self.bassManage.setEnabled(False) self.decimateAudio.setEnabled(False) self.audioFormat.setEnabled(False) self.eacBitRate.setEnabled(False) self.includeOriginalAudio.setEnabled(False) self.includeSubtitles.setEnabled(False) self.targetDirPicker.setEnabled(False) self.outputFilename.setEnabled(False) self.filterMapping.setEnabled(False) self.gainOffset.setEnabled(False) self.ffmpegOutput.setEnabled(True) self.ffmpegProgress.setEnabled(True) self.ffmpegProgressLabel.setEnabled(True) self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) palette = QPalette(self.ffmpegProgress.palette()) palette.setColor(QPalette.Highlight, QColor(Qt.green)) self.ffmpegProgress.setPalette(palette) def __extract_complete(self, result, success): ''' triggered when the extraction thread completes. ''' if self.__executor is not None: if success: logger.info( f"Extraction complete for {self.outputFilename.text()}") self.ffmpegProgress.setValue(100) self.__extracted = True if not self.__is_remux: self.signalName.setEnabled(True) self.signalNameLabel.setEnabled(True) self.signalName.setText( Path(self.outputFilename.text()).resolve().stem) self.buttonBox.button( QDialogButtonBox.Ok).setText('Create Signals') else: logger.error( f"Extraction failed for {self.outputFilename.text()}") palette = QPalette(self.ffmpegProgress.palette()) palette.setColor(QPalette.Highlight, QColor(Qt.red)) self.ffmpegProgress.setPalette(palette) self.statusBar.showMessage('Extraction failed', 5000) self.ffmpegOutput.setPlainText(result) self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) audio = self.__preferences.get(EXTRACTION_NOTIFICATION_SOUND) if audio is not None: logger.debug(f"Playing {audio}") self.__sound = QSound(audio) self.__sound.play() def showProbeInDetail(self): ''' shows a tree widget containing the contents of the probe to allow the raw probe info to be visible. ''' if self.__executor is not None: ViewProbeDialog(self.inputFile.text(), self.__executor.probe, parent=self).exec() def setTargetDirectory(self): ''' Sets the target directory based on the user selection. ''' dialog = QFileDialog(parent=self) dialog.setFileMode(QFileDialog.DirectoryOnly) dialog.setWindowTitle(f"Select Output Directory") if dialog.exec(): selected = dialog.selectedFiles() if len(selected) > 0: self.targetDir.setText(selected[0]) if self.__executor is not None: self.__executor.target_dir = selected[0] self.__display_command_info() def override_filtered_gain_adjustment(self, val): ''' forces the gain adjustment to a specific value. ''' if self.__executor is not None: self.__executor.filtered_audio_offset = val def calculate_gain_adjustment(self): ''' Based on the filters applied, calculates the gain adjustment that is required to avoid clipping. ''' filts = list(set(self.__executor.channel_to_filter.values())) if len(filts) > 1 or filts[0] is not None: from app import wait_cursor with wait_cursor(): headroom = min([ min( self.__calc_headroom( x.filter_signal(filt=True, clip=False).samples), 0.0) for x in filts if x is not None ]) self.remuxedAudioOffset.setValue(headroom) @staticmethod def __calc_headroom(samples): return 20 * math.log(1.0 / np.nanmax(np.abs(samples)), 10)
def _main(args): # To avoid problems when testing without screen if args.test or args.screenshots: os.environ['QT_QPA_PLATFORM'] = 'offscreen' # Set QT_API variable before importing QtPy if args.qt_from in ['pyqt', 'pyqt5', 'pyside', 'pyside2']: os.environ['QT_API'] = args.qt_from elif args.qt_from == 'pyqtgraph': os.environ['QT_API'] = os.environ['PYQTGRAPH_QT_LIB'] elif args.qt_from in ['qt.py', 'qt']: try: import Qt except ImportError: print('Could not import Qt (Qt.Py)') else: os.environ['QT_API'] = Qt.__binding__ # QtPy imports from qtpy import API_NAME, QT_VERSION, PYQT_VERSION, PYSIDE_VERSION from qtpy import __version__ as QTPY_VERSION from qtpy.QtWidgets import (QApplication, QMainWindow, QDockWidget, QStatusBar, QLabel, QMenu) from qtpy.QtCore import QTimer, Qt, QSettings # Set API_VERSION variable API_VERSION = '' if PYQT_VERSION: API_VERSION = PYQT_VERSION elif PYSIDE_VERSION: API_VERSION = PYSIDE_VERSION else: API_VERSION = 'Not found' # Import examples UI from mw_menus_ui import Ui_MainWindow as ui_main from dw_buttons_ui import Ui_DockWidget as ui_buttons from dw_displays_ui import Ui_DockWidget as ui_displays from dw_inputs_fields_ui import Ui_DockWidget as ui_inputs_fields from dw_inputs_no_fields_ui import Ui_DockWidget as ui_inputs_no_fields from dw_widgets_ui import Ui_DockWidget as ui_widgets from dw_views_ui import Ui_DockWidget as ui_views from dw_containers_tabs_ui import Ui_DockWidget as ui_containers_tabs from dw_containers_no_tabs_ui import Ui_DockWidget as ui_containers_no_tabs # qrainbowstyle.useDarwinButtons() QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # create the application if not QApplication.instance(): app = QApplication(sys.argv) else: app = QApplication.instance() app.setOrganizationName('QRainbowStyle') app.setApplicationName('QRainbowStyle Example') styles = qrainbowstyle.getAvailableStyles() style = args.style if not args.style: style = styles[random.randint(0, len(styles)) - 1] app.setStyleSheet(qrainbowstyle.load_stylesheet(style=str(style))) # create main window window = qrainbowstyle.windows.FramelessWindow() window.setTitlebarHeight(30) widget = QMainWindow(window) widget.setWindowFlags(Qt.Widget) widget.setObjectName('mainwindow') ui = ui_main() ui.setupUi(widget) window.addContentWidget(widget) title = ("QRainbowStyle Example - " + "(QRainbowStyle=v" + qrainbowstyle.__version__ + ", QtPy=v" + QTPY_VERSION + ", " + API_NAME + "=v" + API_VERSION + ", Qt=v" + QT_VERSION + ", Python=v" + platform.python_version() + ")") _logger.info(title) window.setWindowTitle(title) # Create docks for buttons dw_buttons = QDockWidget() dw_buttons.setObjectName('buttons') ui_buttons = ui_buttons() ui_buttons.setupUi(dw_buttons) widget.addDockWidget(Qt.RightDockWidgetArea, dw_buttons) # Add actions on popup toolbuttons menu = QMenu() for action in ['Action A', 'Action B', 'Action C']: menu.addAction(action) ui_buttons.toolButtonDelayedPopup.setMenu(menu) ui_buttons.toolButtonInstantPopup.setMenu(menu) ui_buttons.toolButtonMenuButtonPopup.setMenu(menu) # Create docks for buttons dw_displays = QDockWidget() dw_displays.setObjectName('displays') ui_displays = ui_displays() ui_displays.setupUi(dw_displays) widget.addDockWidget(Qt.RightDockWidgetArea, dw_displays) # Create docks for inputs - no fields dw_inputs_no_fields = QDockWidget() dw_inputs_no_fields.setObjectName('inputs_no_fields') ui_inputs_no_fields = ui_inputs_no_fields() ui_inputs_no_fields.setupUi(dw_inputs_no_fields) widget.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_no_fields) # Create docks for inputs - fields dw_inputs_fields = QDockWidget() dw_inputs_fields.setObjectName('inputs_fields') ui_inputs_fields = ui_inputs_fields() ui_inputs_fields.setupUi(dw_inputs_fields) widget.addDockWidget(Qt.RightDockWidgetArea, dw_inputs_fields) # Create docks for widgets dw_widgets = QDockWidget() dw_widgets.setObjectName('widgets') ui_widgets = ui_widgets() ui_widgets.setupUi(dw_widgets) widget.addDockWidget(Qt.LeftDockWidgetArea, dw_widgets) # Create docks for views dw_views = QDockWidget() dw_views.setObjectName('views') ui_views = ui_views() ui_views.setupUi(dw_views) widget.addDockWidget(Qt.LeftDockWidgetArea, dw_views) # Create docks for containers - no tabs dw_containers_no_tabs = QDockWidget() dw_containers_no_tabs.setObjectName('containers_no_tabs') ui_containers_no_tabs = ui_containers_no_tabs() ui_containers_no_tabs.setupUi(dw_containers_no_tabs) widget.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_no_tabs) # Create docks for containters - tabs dw_containers_tabs = QDockWidget() dw_containers_tabs.setObjectName('containers_tabs') ui_containers_tabs = ui_containers_tabs() ui_containers_tabs.setupUi(dw_containers_tabs) widget.addDockWidget(Qt.LeftDockWidgetArea, dw_containers_tabs) # Tabify right docks widget.tabifyDockWidget(dw_buttons, dw_displays) widget.tabifyDockWidget(dw_displays, dw_inputs_fields) widget.tabifyDockWidget(dw_inputs_fields, dw_inputs_no_fields) # Tabify left docks widget.tabifyDockWidget(dw_containers_no_tabs, dw_containers_tabs) widget.tabifyDockWidget(dw_containers_tabs, dw_widgets) widget.tabifyDockWidget(dw_widgets, dw_views) # Issues #9120, #9121 on Spyder qstatusbar = QStatusBar() qstatusbar.addWidget(QLabel('Style')) qstatusbarbutton = qrainbowstyle.widgets.StylePickerHorizontal() qstatusbar.addWidget(qstatusbarbutton) qstatusbar.setSizeGripEnabled(False) # Add info also in status bar for screenshots get it qstatusbar.addWidget(QLabel('INFO: ' + title)) widget.setStatusBar(qstatusbar) # Todo: add report info and other info in HELP graphical # Auto quit after 2s when in test mode if args.test: QTimer.singleShot(2000, app.exit) _read_settings(widget, args.reset, QSettings) window.show() # window.showMaximized() app.exec_() _write_settings(widget, QSettings)