Merge pull request #219 from nyanpasu64/remove-ui-xml

Replace XML .ui files with stack-based Python code
pull/357/head
nyanpasu64 2019-03-08 05:30:12 -08:00 zatwierdzone przez GitHub
commit 6c3bd82f58
8 zmienionych plików z 702 dodań i 805 usunięć

Wyświetl plik

@ -3,6 +3,7 @@ import os
import shutil
import subprocess
from pathlib import Path
from typing import List, Tuple
from PyInstaller.building.api import PYZ, EXE, COLLECT
from PyInstaller.building.build_main import Analysis
@ -19,10 +20,12 @@ def keep(dir, wildcard):
return [(include, dir) for include in includes]
datas = keep("corrscope/gui", "*.ui")
InFile = str
OutFolder = str
datas: List[Tuple[InFile, OutFolder]] = []
version = v.pyinstaller_write_version()
datas.append((v.version_txt, "."))
datas += [(str(v.version_txt), ".")]
app_name = "corrscope"
app_name_version = f"{app_name}-{version}"

Wyświetl plik

@ -9,7 +9,6 @@ from typing import Optional, List, Any, Tuple, Callable, Union, Dict, Sequence
import PyQt5.QtCore as qc
import PyQt5.QtWidgets as qw
import attr
from PyQt5 import uic
from PyQt5.QtCore import QModelIndex, Qt
from PyQt5.QtCore import QVariant
from PyQt5.QtGui import QKeySequence, QFont, QCloseEvent
@ -21,7 +20,7 @@ from corrscope import cli
from corrscope.channel import ChannelConfig
from corrscope.config import CorrError, copy_config, yaml
from corrscope.corrscope import CorrScope, Config, Arguments, default_config
from corrscope.gui.data_bind import (
from corrscope.gui.model_bind import (
PresentationModel,
map_gui,
behead,
@ -34,6 +33,7 @@ from corrscope.gui.history_file_dlg import (
get_open_file_list,
get_save_file_path,
)
from corrscope.gui.view_mainwindow import MainWindow as Ui_MainWindow
from corrscope.gui.util import color2hex, Locked, find_ranges, TracebackDialog
from corrscope.layout import Orientation, StereoOrientation
from corrscope.outputs import IOutputConfig, FFplayOutputConfig, FFmpegOutputConfig
@ -74,7 +74,7 @@ def gui_main(cfg_or_path: Union[Config, Path]):
sys.exit(app.exec_())
class MainWindow(qw.QMainWindow):
class MainWindow(qw.QMainWindow, Ui_MainWindow):
"""
Main window.
@ -94,7 +94,7 @@ class MainWindow(qw.QMainWindow):
self.pref = gp.load_prefs()
# Load UI.
uic.loadUi(res("mainwindow.ui"), self) # sets windowTitle
self.setupUi(self) # sets windowTitle
# Bind UI buttons, etc. Functions block main thread, avoiding race conditions.
self.master_audio_browse.clicked.connect(self.on_master_audio_browse)
@ -151,7 +151,7 @@ class MainWindow(qw.QMainWindow):
self._update_unsaved_title()
# GUI layout widgets
tabWidget: qw.QTabWidget
left_tabs: qw.QTabWidget
# Config models
model: Optional["ConfigModel"] = None
@ -232,7 +232,7 @@ class MainWindow(qw.QMainWindow):
self._cfg_path = cfg_path
self._any_unsaved = False
self.load_title()
self.tabWidget.setCurrentIndex(0)
self.left_tabs.setCurrentIndex(0)
if self.model is None:
self.model = ConfigModel(cfg)

Wyświetl plik

@ -1,795 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1160</width>
<height>0</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabGeneral">
<attribute name="title">
<string>&amp;General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="optionGlobal">
<property name="title">
<string>Global</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="fpsL">
<property name="text">
<string>FPS</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="BoundSpinBox" name="fps">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="trigger_msL">
<property name="text">
<string>Trigger Width</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="BoundSpinBox" name="trigger_ms">
<property name="minimum">
<number>5</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="render_msL">
<property name="text">
<string>Render Width</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="BoundSpinBox" name="render_ms">
<property name="minimum">
<number>5</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="amplificationL">
<property name="text">
<string>Amplification</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="BoundDoubleSpinBox" name="amplification">
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="begin_timeL">
<property name="text">
<string>Begin Time</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="BoundDoubleSpinBox" name="begin_time">
<property name="maximum">
<double>9999.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="optionAppear">
<property name="title">
<string>Appearance</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="render_resolutionL">
<property name="text">
<string>Resolution</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="BoundLineEdit" name="render_resolution">
<property name="text">
<string>vs</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="render__bg_colorL">
<property name="text">
<string>Background</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="BoundColorWidget" name="render__bg_color" native="true"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="render__init_line_colorL">
<property name="text">
<string>Line Color</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="BoundColorWidget" name="render__init_line_color" native="true"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="render__line_widthL">
<property name="text">
<string>Line Width</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="BoundDoubleSpinBox" name="render__line_width">
<property name="minimum">
<double>0.500000000000000</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="render__grid_colorL">
<property name="text">
<string>Grid Color</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="OptionalColorWidget" name="render__grid_color" native="true"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="render__midline_colorL">
<property name="text">
<string>Midline Color</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="OptionalColorWidget" name="render__midline_color" native="true"/>
</item>
<item row="6" column="0">
<widget class="BoundCheckBox" name="render__v_midline">
<property name="text">
<string>Vertical</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="BoundCheckBox" name="render__h_midline">
<property name="text">
<string>Horizontal Midline</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="optionLayout">
<property name="title">
<string>Layout</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="layout__orientationL">
<property name="text">
<string>Orientation</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="BoundComboBox" name="layout__orientation"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="layout__ncolsL">
<property name="text">
<string>Columns</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="layoutDims">
<item>
<widget class="BoundSpinBox" name="layout__ncols">
<property name="specialValueText">
<string notr="true"> </string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="layout__nrowsL">
<property name="text">
<string>Rows</string>
</property>
</widget>
</item>
<item>
<widget class="BoundSpinBox" name="layout__nrows">
<property name="specialValueText">
<string notr="true"> </string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabStereo">
<attribute name="title">
<string>&amp;Stereo</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="optionStereo">
<property name="title">
<string>Stereo Enable</string>
</property>
<layout class="QFormLayout" name="formLayout_8">
<item row="0" column="0">
<widget class="QLabel" name="trigger_stereoL">
<property name="text">
<string>Trigger Stereo</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="BoundComboBox" name="trigger_stereo"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="render_stereoL">
<property name="text">
<string>Render Stereo</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="BoundComboBox" name="render_stereo"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="dockStereo_2">
<property name="title">
<string>Stereo Appearance</string>
</property>
<layout class="QFormLayout" name="formLayout_7">
<item row="0" column="0">
<widget class="QLabel" name="layout__stereo_orientationL">
<property name="text">
<string>Stereo Orientation</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="BoundComboBox" name="layout__stereo_orientation"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="render__stereo_grid_opacityL">
<property name="text">
<string>Grid Opacity</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="BoundDoubleSpinBox" name="render__stereo_grid_opacity">
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.250000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabPerf">
<attribute name="title">
<string>&amp;Performance</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="perfAll">
<property name="title">
<string>Preview and Render</string>
</property>
<layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="trigger_subsamplingL">
<property name="text">
<string>Trigger Subsampling</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="BoundSpinBox" name="trigger_subsampling">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="render_subsamplingL">
<property name="text">
<string>Render Subsampling</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="BoundSpinBox" name="render_subsampling">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="perfPreview">
<property name="title">
<string>Preview Only</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="render_subfpsL">
<property name="text">
<string>Render FPS Divisor</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="BoundSpinBox" name="render_subfps">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="render__res_divisorL">
<property name="text">
<string>Resolution Divisor</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="BoundDoubleSpinBox" name="render__res_divisor">
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="audioColumn">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QGroupBox" name="audioGroup">
<property name="title">
<string>Master Audio</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="BoundLineEdit" name="master_audio">
<property name="text">
<string>/</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="master_audio_browse">
<property name="text">
<string>&amp;Browse...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="optionAudio">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLabel" name="trigger__edge_strengthL">
<property name="text">
<string>Edge Strength</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="trigger__responsivenessL">
<property name="text">
<string>Responsiveness</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="trigger__buffer_falloffL">
<property name="text">
<string>Buffer Falloff</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="BoundDoubleSpinBox" name="trigger__edge_strength">
<property name="minimum">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="BoundDoubleSpinBox" name="trigger__responsiveness">
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="BoundDoubleSpinBox" name="trigger__buffer_falloff">
<property name="singleStep">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item row="0" column="4" rowspan="2">
<widget class="BoundCheckBox" name="trigger__pitch_tracking">
<property name="text">
<string>Pitch Tracking</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="trigger__edge_directionL">
<property name="text">
<string>Edge Direction</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="BoundComboBox" name="trigger__edge_direction"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="channelsGroup">
<property name="title">
<string>Oscilloscope Channels</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="channelBar">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="ShortcutButton" name="channelAdd">
<property name="text">
<string>&amp;Add...</string>
</property>
</widget>
</item>
<item>
<widget class="ShortcutButton" name="channelDelete">
<property name="text">
<string>&amp;Delete</string>
</property>
</widget>
</item>
<item>
<widget class="ShortcutButton" name="channelUp">
<property name="text">
<string>Up</string>
</property>
</widget>
</item>
<item>
<widget class="ShortcutButton" name="channelDown">
<property name="text">
<string>Down</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="ChannelTableView" name="channel_view"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<widget class="QMenu" name="menuFile">
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="actionNew"/>
<addaction name="actionOpen"/>
<addaction name="actionSave"/>
<addaction name="actionSaveAs"/>
<addaction name="separator"/>
<addaction name="actionPreview"/>
<addaction name="actionRender"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>&amp;Tools</string>
</property>
<addaction name="action_separate_render_dir"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuTools"/>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionNew"/>
<addaction name="actionOpen"/>
<addaction name="actionSave"/>
<addaction name="actionSaveAs"/>
<addaction name="separator"/>
<addaction name="actionPreview"/>
<addaction name="actionRender"/>
</widget>
<action name="actionOpen">
<property name="text">
<string>&amp;Open</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
<action name="actionSave">
<property name="text">
<string>&amp;Save</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="actionNew">
<property name="text">
<string>&amp;New</string>
</property>
<property name="shortcut">
<string>Ctrl+N</string>
</property>
</action>
<action name="actionSaveAs">
<property name="text">
<string>Save &amp;As</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+S</string>
</property>
</action>
<action name="actionExit">
<property name="text">
<string>E&amp;xit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionPreview">
<property name="text">
<string>&amp;Preview</string>
</property>
<property name="shortcut">
<string>Ctrl+P</string>
</property>
</action>
<action name="actionRender">
<property name="text">
<string>&amp;Render to Video</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<action name="action_separate_render_dir">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Separate Render Folder</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>BoundLineEdit</class>
<extends>QLineEdit</extends>
<header>corrscope/gui/data_bind.h</header>
</customwidget>
<customwidget>
<class>BoundSpinBox</class>
<extends>QSpinBox</extends>
<header>corrscope/gui/data_bind.h</header>
</customwidget>
<customwidget>
<class>BoundDoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>corrscope/gui/data_bind.h</header>
</customwidget>
<customwidget>
<class>BoundComboBox</class>
<extends>QComboBox</extends>
<header>corrscope/gui/data_bind.h</header>
</customwidget>
<customwidget>
<class>BoundCheckBox</class>
<extends>QCheckBox</extends>
<header>corrscope/gui/data_bind.h</header>
</customwidget>
<customwidget>
<class>ShortcutButton</class>
<extends>QPushButton</extends>
<header>corrscope/gui/__init__.h</header>
</customwidget>
<customwidget>
<class>ChannelTableView</class>
<extends>QTableView</extends>
<header>corrscope/gui/__init__.h</header>
</customwidget>
<customwidget>
<class>BoundColorWidget</class>
<extends>QWidget</extends>
<header>corrscope/gui/data_bind.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>OptionalColorWidget</class>
<extends>QWidget</extends>
<header>corrscope/gui/data_bind.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

Wyświetl plik

@ -71,6 +71,9 @@ class PresentationModel(qc.QObject):
updater()
SKIP_BINDING = "skip"
def map_gui(view: "MainWindow", model: PresentationModel) -> None:
"""
Binding:
@ -90,7 +93,8 @@ def map_gui(view: "MainWindow", model: PresentationModel) -> None:
# Exclude nameless ColorText inside BoundColorWidget wrapper,
# since bind_widget(path="") will crash.
# BoundColorWidget.bind_widget() handles binding children.
if path:
if path != SKIP_BINDING:
assert path != ""
widget.bind_widget(model, path)
@ -366,6 +370,7 @@ class _ColorText(BoundLineEdit):
def __init__(self, parent: QWidget, optional: bool):
super().__init__(parent)
self.setObjectName(SKIP_BINDING)
self.optional = optional
hex_color = qc.pyqtSignal(str)

Wyświetl plik

@ -0,0 +1,2 @@
SOURCES += view_mainwindow.py
TRANSLATIONS += corrscope_xa.ts

Wyświetl plik

@ -0,0 +1,402 @@
# -*- coding: utf-8 -*-
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from corrscope.gui.view_stack import (
LayoutStack,
set_layout,
central_widget,
append_widget,
add_row,
add_tab,
set_attr_objectName,
append_stretch,
add_grid_col,
Both,
set_menu_bar,
append_menu,
add_toolbar,
)
NBSP = "\xa0"
def fixed_size_policy():
return QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
# noinspection PyAttributeOutsideInit
class MainWindow(QWidget):
@staticmethod
def tr(*args, **kwargs) -> str:
"""Only at runtime, not at pylupdate5 time."""
# noinspection PyCallByClass,PyTypeChecker
return QCoreApplication.translate("MainWindow", *args, **kwargs)
def setupUi(self, MainWindow: QMainWindow):
MainWindow.resize(1160, 0)
s = LayoutStack(MainWindow)
# Window contents
with central_widget(s, QWidget) as self.centralWidget:
horizontalLayout = set_layout(s, QHBoxLayout)
# Left-hand config tabs
with append_widget(s, QTabWidget) as self.left_tabs:
self.tabGeneral = self.add_general_tab(s)
self.tabStereo = self.add_stereo_tab(s)
self.tabPerf = self.add_performance_tab(s)
# Right-hand channel list
with append_widget(s, QVBoxLayout) as self.audioColumn:
# Top bar (master audio, trigger)
self.add_top_bar(s)
# Channel list (group box)
self.channelsGroup = self.add_channels_list(s)
# Right-hand channel list expands to fill space.
horizontalLayout.setStretch(1, 1)
self.add_actions(s, MainWindow)
# Creates references to labels
set_attr_objectName(self, s)
# Initializes labels by reference
self.retranslateUi(MainWindow)
self.left_tabs.setCurrentIndex(0)
# Depends on objectName
QMetaObject.connectSlotsByName(MainWindow)
def add_general_tab(self, s: LayoutStack) -> QWidget:
tr = self.tr
with self._add_tab(s, tr("&General")) as tab:
set_layout(s, QVBoxLayout)
# Global group
with append_widget(s, QGroupBox) as self.optionGlobal:
set_layout(s, QFormLayout)
with add_row(s, BoundSpinBox) as self.fps:
self.fps.setMinimum(1)
self.fps.setMaximum(999)
self.fps.setSingleStep(10)
with add_row(s, BoundSpinBox) as self.trigger_ms:
self.trigger_ms.setMinimum(5)
self.trigger_ms.setSingleStep(5)
with add_row(s, BoundSpinBox) as self.render_ms:
self.render_ms.setMinimum(5)
self.render_ms.setSingleStep(5)
with add_row(s, BoundDoubleSpinBox) as self.amplification:
self.amplification.setSingleStep(0.1)
with add_row(s, BoundDoubleSpinBox) as self.begin_time:
self.begin_time.setMaximum(9999.0)
with append_widget(s, QGroupBox) as self.optionAppearance:
set_layout(s, QFormLayout)
with add_row(s, BoundLineEdit) as self.render_resolution:
pass
with add_row(s, BoundColorWidget) as self.render__bg_color:
pass
with add_row(s, BoundColorWidget) as self.render__init_line_color:
pass
with add_row(s, BoundDoubleSpinBox) as self.render__line_width:
self.render__line_width.setMinimum(0.5)
self.render__line_width.setSingleStep(0.5)
with add_row(s, OptionalColorWidget) as self.render__grid_color:
pass
with add_row(s, OptionalColorWidget) as self.render__midline_color:
pass
with add_row(s, BoundCheckBox, BoundCheckBox) as (
self.render__v_midline,
self.render__h_midline,
):
pass
with append_widget(s, QGroupBox) as self.optionLayout:
set_layout(s, QFormLayout)
with add_row(s, BoundComboBox) as self.layout__orientation:
pass
with add_row(s, QLabel, QHBoxLayout) as (
self.layout__ncolsL,
self.layoutDims,
):
with append_widget(s, BoundSpinBox) as self.layout__ncols:
self.layout__ncols.setSpecialValueText(NBSP)
with append_widget(s, QLabel) as self.layout__nrowsL:
pass
with append_widget(s, BoundSpinBox) as self.layout__nrows:
self.layout__nrows.setSpecialValueText(NBSP)
append_stretch(s)
return tab
def add_stereo_tab(self, s: LayoutStack) -> QWidget:
tr = self.tr
with self._add_tab(s, tr("&Stereo")) as tab:
set_layout(s, QVBoxLayout)
with append_widget(s, QGroupBox) as self.optionStereo:
set_layout(s, QFormLayout)
with add_row(s, BoundComboBox) as self.trigger_stereo:
pass
with add_row(s, BoundComboBox) as self.render_stereo:
pass
with append_widget(s, QGroupBox) as self.dockStereo_2:
set_layout(s, QFormLayout)
with add_row(s, BoundComboBox) as self.layout__stereo_orientation:
pass
with add_row(s, BoundDoubleSpinBox) as (
self.render__stereo_grid_opacity
):
self.render__stereo_grid_opacity.setMaximum(1.0)
self.render__stereo_grid_opacity.setSingleStep(0.25)
append_stretch(s)
return tab
def add_performance_tab(self, s: LayoutStack) -> QWidget:
tr = self.tr
with self._add_tab(s, tr("&Performance")) as tab:
set_layout(s, QVBoxLayout)
with append_widget(s, QGroupBox) as self.perfAll:
set_layout(s, QFormLayout)
with add_row(s, BoundSpinBox) as self.trigger_subsampling:
self.trigger_subsampling.setMinimum(1)
with add_row(s, BoundSpinBox) as self.render_subsampling:
self.render_subsampling.setMinimum(1)
with append_widget(s, QGroupBox) as self.perfPreview:
set_layout(s, QFormLayout)
with add_row(s, BoundSpinBox) as self.render_subfps:
self.render_subfps.setMinimum(1)
with add_row(s, BoundDoubleSpinBox) as self.render__res_divisor:
self.render__res_divisor.setMinimum(1.0)
self.render__res_divisor.setSingleStep(0.5)
append_stretch(s)
return tab
@staticmethod
def _add_tab(s: LayoutStack, label: str = ""):
return add_tab(s, QWidget, label)
def add_top_bar(self, s):
with append_widget(s, QHBoxLayout):
# Master audio
with append_widget(s, QGroupBox) as self.masterAudioGroup:
set_layout(s, QHBoxLayout)
with append_widget(s, BoundLineEdit) as self.master_audio:
pass
with append_widget(s, QPushButton) as self.master_audio_browse:
pass
# Trigger config
with append_widget(s, QGroupBox) as self.optionAudio:
# Prevent expansion (does nothing even if removed :| )
self.optionAudio.setSizePolicy(fixed_size_policy())
set_layout(s, QGridLayout)
with add_grid_col(s, BoundComboBox) as (self.trigger__edge_direction):
pass
with add_grid_col(s, BoundDoubleSpinBox) as (
self.trigger__edge_strength
):
self.trigger__edge_strength.setMinimum(0.0)
with add_grid_col(s, BoundDoubleSpinBox) as (
self.trigger__responsiveness
):
self.trigger__responsiveness.setMaximum(1.0)
self.trigger__responsiveness.setSingleStep(0.1)
with add_grid_col(s, BoundDoubleSpinBox) as (
self.trigger__buffer_falloff
):
self.trigger__buffer_falloff.setSingleStep(0.5)
with add_grid_col(s, BoundCheckBox, Both) as (
self.trigger__pitch_tracking
):
assert isinstance(self.trigger__pitch_tracking, QWidget)
def add_channels_list(self, s):
with append_widget(s, QGroupBox) as group:
set_layout(s, QVBoxLayout)
# Button toolbar
with append_widget(s, QHBoxLayout) as self.channelBar:
append_stretch(s)
with append_widget(s, ShortcutButton) as self.channelAdd:
pass
with append_widget(s, ShortcutButton) as self.channelDelete:
pass
with append_widget(s, ShortcutButton) as self.channelUp:
pass
with append_widget(s, ShortcutButton) as self.channelDown:
pass
# Spreadsheet grid
with append_widget(s, ChannelTableView) as self.channel_view:
pass
return group
def add_actions(self, s: LayoutStack, MainWindow):
# Setup actions
self.actionOpen = QAction(MainWindow)
self.actionSave = QAction(MainWindow)
self.actionNew = QAction(MainWindow)
self.actionSaveAs = QAction(MainWindow)
self.actionExit = QAction(MainWindow)
self.actionPreview = QAction(MainWindow)
self.actionRender = QAction(MainWindow)
self.action_separate_render_dir = QAction(MainWindow)
self.action_separate_render_dir.setCheckable(True)
# Setup menu_bar
assert s.widget is MainWindow
with set_menu_bar(s) as self.menuBar:
with append_menu(s) as self.menuFile:
self.menuFile.addAction(self.actionNew)
self.menuFile.addAction(self.actionOpen)
self.menuFile.addAction(self.actionSave)
self.menuFile.addAction(self.actionSaveAs)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionPreview)
self.menuFile.addAction(self.actionRender)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionExit)
with append_menu(s) as self.menuTools:
self.menuTools.addAction(self.action_separate_render_dir)
# Setup toolbar
with add_toolbar(s, Qt.TopToolBarArea) as self.toolBar:
self.toolBar.addAction(self.actionNew)
self.toolBar.addAction(self.actionOpen)
self.toolBar.addAction(self.actionSave)
self.toolBar.addAction(self.actionSaveAs)
self.toolBar.addSeparator()
self.toolBar.addAction(self.actionPreview)
self.toolBar.addAction(self.actionRender)
# noinspection PyUnresolvedReferences
def retranslateUi(self, MainWindow):
tr = self.tr
MainWindow.setWindowTitle(tr("MainWindow"))
self.optionGlobal.setTitle(tr("Global"))
self.fpsL.setText(tr("FPS"))
self.trigger_msL.setText(tr("Trigger Width"))
self.render_msL.setText(tr("Render Width"))
self.amplificationL.setText(tr("Amplification"))
self.begin_timeL.setText(tr("Begin Time"))
self.optionAppearance.setTitle(tr("Appearance"))
self.render_resolutionL.setText(tr("Resolution"))
self.render_resolution.setText(tr("vs"))
self.render__bg_colorL.setText(tr("Background"))
self.render__init_line_colorL.setText(tr("Line Color"))
self.render__line_widthL.setText(tr("Line Width"))
self.render__grid_colorL.setText(tr("Grid Color"))
self.render__midline_colorL.setText(tr("Midline Color"))
self.render__v_midline.setText(tr("Vertical"))
self.render__h_midline.setText(tr("Horizontal Midline"))
self.optionLayout.setTitle(tr("Layout"))
self.layout__orientationL.setText(tr("Orientation"))
self.layout__ncolsL.setText(tr("Columns"))
self.layout__nrowsL.setText(tr("Rows"))
self.optionStereo.setTitle(tr("Stereo Enable"))
self.trigger_stereoL.setText(tr("Trigger Stereo"))
self.render_stereoL.setText(tr("Render Stereo"))
self.dockStereo_2.setTitle(tr("Stereo Appearance"))
self.layout__stereo_orientationL.setText(tr("Stereo Orientation"))
self.render__stereo_grid_opacityL.setText(tr("Grid Opacity"))
self.perfAll.setTitle(tr("Preview and Render"))
self.trigger_subsamplingL.setText(tr("Trigger Subsampling"))
self.render_subsamplingL.setText(tr("Render Subsampling"))
self.perfPreview.setTitle(tr("Preview Only"))
self.render_subfpsL.setText(tr("Render FPS Divisor"))
self.render__res_divisorL.setText(tr("Resolution Divisor"))
self.masterAudioGroup.setTitle(tr("Master Audio"))
self.master_audio.setText(tr("/"))
self.master_audio_browse.setText(tr("&Browse..."))
self.optionAudio.setTitle(tr("Trigger"))
self.trigger__edge_strengthL.setText(tr("Edge Strength"))
self.trigger__responsivenessL.setText(tr("Responsiveness"))
self.trigger__buffer_falloffL.setText(tr("Buffer Falloff"))
self.trigger__pitch_tracking.setText(tr("Pitch Tracking"))
self.trigger__edge_directionL.setText(tr("Edge Direction"))
self.channelsGroup.setTitle(tr("Oscilloscope Channels"))
self.channelAdd.setText(tr("&Add..."))
self.channelDelete.setText(tr("&Delete"))
self.channelUp.setText(tr("Up"))
self.channelDown.setText(tr("Down"))
self.menuFile.setTitle(tr("&File"))
self.menuTools.setTitle(tr("&Tools"))
self.toolBar.setWindowTitle(tr("toolBar"))
self.actionOpen.setText(tr("&Open"))
self.actionOpen.setShortcut(tr("Ctrl+O"))
self.actionSave.setText(tr("&Save"))
self.actionSave.setShortcut(tr("Ctrl+S"))
self.actionNew.setText(tr("&New"))
self.actionNew.setShortcut(tr("Ctrl+N"))
self.actionSaveAs.setText(tr("Save &As"))
self.actionSaveAs.setShortcut(tr("Ctrl+Shift+S"))
self.actionExit.setText(tr("E&xit"))
self.actionExit.setShortcut(tr("Ctrl+Q"))
self.actionPreview.setText(tr("&Preview"))
self.actionPreview.setShortcut(tr("Ctrl+P"))
self.actionRender.setText(tr("&Render to Video"))
self.actionRender.setShortcut(tr("Ctrl+R"))
self.action_separate_render_dir.setText(tr("&Separate Render Folder"))
from corrscope.gui.__init__ import ChannelTableView, ShortcutButton
from corrscope.gui.model_bind import (
BoundCheckBox,
BoundColorWidget,
BoundComboBox,
BoundDoubleSpinBox,
BoundLineEdit,
BoundSpinBox,
OptionalColorWidget,
)

Wyświetl plik

@ -0,0 +1,280 @@
from contextlib import contextmanager
from typing import *
import attr
from PyQt5.QtCore import QObject, Qt
from PyQt5.QtWidgets import *
from corrscope.util import obj_name
T = TypeVar("T")
ctx = Iterator
SomeQW = TypeVar("SomeQW", bound=QWidget)
WidgetOrLayout = TypeVar("WidgetOrLayout", bound=Union[QWidget, QLayout])
def new_widget_or_layout(
item_type: Type[WidgetOrLayout], parent: QWidget
) -> WidgetOrLayout:
"""Creates a widget or layout, for insertion into an existing layout.
Do NOT use for filling a widget with a layout!"""
if issubclass(item_type, QWidget):
right = item_type(parent)
else:
right = item_type(None)
return right
@attr.dataclass
class StackFrame:
widget: Optional[QWidget]
layout: Optional[QLayout] = None
def with_layout(self, layout: Optional[QLayout]):
return attr.evolve(self, layout=layout)
class LayoutStack:
def __init__(self, root: Optional[QWidget]):
self._items = [StackFrame(root)]
self.widget_to_label: Dict[QWidget, QLabel] = {}
@contextmanager
def push(self, item: T) -> ctx[T]:
if isinstance(item, StackFrame):
frame = item
elif isinstance(item, QWidget):
frame = StackFrame(item)
elif isinstance(item, QLayout):
frame = self.peek().with_layout(item)
else:
raise TypeError(obj_name(item))
self._items.append(frame)
try:
yield item
finally:
self._items.pop()
def peek(self) -> StackFrame:
return self._items[-1]
@property
def widget(self):
return self.peek().widget
@property
def layout(self):
return self.peek().layout
def set_layout(stack: LayoutStack, layout_type: Type[QLayout]) -> QLayout:
layout = layout_type(stack.peek().widget)
stack.peek().layout = layout
return layout
def assert_peek(stack: LayoutStack, cls):
assert isinstance(stack.widget, cls)
def central_widget(stack: LayoutStack, widget_type: Type[SomeQW] = QWidget):
assert_peek(stack, QMainWindow)
# do NOT orphan=True
return _add_widget(stack, widget_type, exit_action="setCentralWidget")
def orphan_widget(stack: LayoutStack, widget_type: Type[SomeQW] = QWidget):
return _add_widget(stack, widget_type, orphan=True)
@contextmanager
def append_widget(
stack: LayoutStack, item_type: Type[WidgetOrLayout]
) -> ctx[WidgetOrLayout]:
with _add_widget(stack, item_type) as item:
yield item
add_widget_or_layout(stack.layout, item)
# noinspection PyArgumentList
def add_widget_or_layout(layout: QLayout, item: WidgetOrLayout, *args, **kwargs):
if isinstance(item, QWidget):
layout.addWidget(item, *args, **kwargs)
elif isinstance(item, QLayout):
# QLayout and some subclasses (like QFormLayout) omit this method,
# and will crash at runtime.
layout.addLayout(item, *args, **kwargs)
else:
raise TypeError(item)
# Main window toolbars/menus
def set_menu_bar(stack: LayoutStack):
assert_peek(stack, QMainWindow)
return _add_widget(stack, QMenuBar, exit_action="setMenuBar")
# won't bother adding type hints that pycharm is too dumb to understand
def append_menu(stack: LayoutStack):
assert_peek(stack, QMenuBar)
return _add_widget(stack, QMenu, exit_action="addMenu")
def add_toolbar(stack: LayoutStack, area=Qt.TopToolBarArea):
assert_peek(stack, QMainWindow)
def _add_toolbar(parent: QMainWindow, toolbar):
parent.addToolBar(area, toolbar)
return _add_widget(stack, QToolBar, exit_action=_add_toolbar)
# Implementation
@contextmanager
def _add_widget(
stack: LayoutStack,
item_type: Type[WidgetOrLayout],
orphan=False,
exit_action: Union[Callable[[Any, Any], Any], str] = "",
) -> ctx[WidgetOrLayout]:
"""
- Constructs item_type using parent.
- Yields item_type.
"""
if not orphan:
parent = stack.widget
else:
parent = None
with stack.push(new_widget_or_layout(item_type, parent)) as item:
yield item
real_parent = stack.widget
if callable(exit_action):
exit_action(real_parent, item)
elif exit_action:
getattr(real_parent, exit_action)(item)
def append_stretch(stack: LayoutStack):
cast(QBoxLayout, stack.layout).addStretch()
Left = TypeVar("Left", bound=QWidget)
Right = TypeVar("Right", bound=Union[QWidget, QLayout]) # same as WidgetOrLayout
Both = object()
def widget_pair_inserter(append_widgets: Callable):
@contextmanager
def add_row_col(stack: LayoutStack, arg1, arg2=None):
left_type: Type[Left]
right_type: Type[Right]
if arg2 is None:
left_type, right_type = QLabel, arg1
auto_left_label = True
else:
left_type, right_type = arg1, arg2
auto_left_label = False
parent = stack.widget
left = new_widget_or_layout(left_type, parent) # TODO support str
if right_type is Both:
right = Both
push = left
else:
right = new_widget_or_layout(right_type, parent)
push = right
with stack.push(push):
if right is Both:
yield left
elif auto_left_label:
yield right
else:
yield left, right
append_widgets(stack.layout, left, right)
if auto_left_label:
assert isinstance(left, QLabel)
stack.widget_to_label[right] = left
return add_row_col
def _add_row(layout: QFormLayout, left, right):
assert isinstance(layout, QFormLayout), layout
if right is Both:
raise TypeError("Cannot add_row(QFormLayout, span=Both)")
return layout.addRow(left, right)
add_row = widget_pair_inserter(_add_row)
def _add_grid_col(layout: QGridLayout, up, down):
assert isinstance(layout, QGridLayout), layout
col = layout.columnCount()
"""
void QGridLayout::addWidget(
QWidget *widget,
int fromRow, int fromColumn, [int rowSpan, int columnSpan],
Qt::Alignment alignment = Qt::Alignment()
)
"""
if down is Both:
shape = lambda: [0, col, -1, 1]
add_widget_or_layout(layout, up, *shape())
else:
shape = lambda row: [row, col]
add_widget_or_layout(layout, up, *shape(0))
add_widget_or_layout(layout, down, *shape(1))
add_grid_col = widget_pair_inserter(_add_grid_col)
@contextmanager
def add_tab(stack, widget_type: Type[SomeQW] = QWidget, label: str = "") -> ctx[SomeQW]:
"""
- Constructs widget using parent.
- Yields widget.
"""
tabs: QTabWidget = stack.widget
assert isinstance(tabs, QTabWidget), tabs
with orphan_widget(stack, widget_type) as w:
yield w
tabs.addTab(w, label)
# After building a tree...
def set_attr_objectName(ui, stack: LayoutStack):
"""
- Set objectName of all objects referenced by ui.
- For all object $name added by add_row() or add_grid_col(),
if $label was generated but not yielded
setattr(ui.$name + "L" = $label)
"""
widget_to_label = stack.widget_to_label
for name, obj in dict(ui.__dict__).items():
if not isinstance(obj, QObject):
continue
obj.setObjectName(name)
if obj in widget_to_label:
label = widget_to_label[obj]
label_name = name + "L"
label.setObjectName(label_name)
ui.__dict__[label_name] = label

Wyświetl plik

@ -1,6 +1,6 @@
import pytest
from corrscope.gui.data_bind import rgetattr, rsetattr, rhasattr, flatten_attr
from corrscope.gui.model_bind import rgetattr, rsetattr, rhasattr, flatten_attr
class Person(object):